Table Of Contents

Previous topic

Model

Next topic

Feature and Requirement

This Page

Basic Type

basicType           ::=  ( basicTypeTrait | basicTypeClass ) [ basicTypeObject ]
basicTypeTrait      ::=  "trait" ID_basic
                           "extends" ( "BasicType" | ID_basic ( "with" ID_basic )* )
                         [ basicTypeBody ]
basicTypeClass      ::=  [ "final" ] "class" ID_basic "(" "val" "value" ":" <basicInternalType> ")"
                           "extends" ( "BasicType" | ID_basic ( "with" ID_basic )* )
                         [ basicTypeBody ]
basicTypeBody       ::=  "{" [ basicAsStringMethod ] [ basicToStringMethod ] <basicOpMethod>* "}"
basicAsStringMethod ::=  "override" "def" "asString" "=" <scalaExp : java.lang.String>
basicToStringMethod ::=  "override" "def" "toString" "=" <scalaExp : java.lang.String>
basicTypeObject     ::=  "object" ID_basic "{" basicTypeApply <basicTypeApplyOther>* "}"
basicTypeApply      ::=  "implicit" "def" "apply" "(" ID_apply ":" <basicInternalType> ")" "="
                           "new" ID_basic "(" ID_apply ")"

Basic types are primitive types for model device attributes whose values are immutable. In DML, all basic types have to be explicitly declared before other model entities can refer to it. In DMS, explicit declaration basic types separate the types in the modeling type universe with that of Scala’s; in other words, they draw the boundaries of Scala types available that are imported as primitive types for modeling.

Built-in Type

DML (DMS) provides only one built-in basic type and that is Boolean (dms.Boolean) – representing the typical true and false values; this is provided because invariant dms.Predicate uses it. Note that it has the same simple name to scala.Boolean that is imported by default in any Scala code; thus, they should not be confused. Using import edu.ksu.cis.santos.mdcf.dms._ as recommended in the Model Section overrides the default import and made all references to Boolean refer to dms.Boolean instead of scala.Boolean. Similarly, this default overriding is recommended for dms.example.Number (overriding java.lang.Number) and dms.example.Int (overriding scala.Int).

Hierarchy

Basic types can be organized in a sub-type (<:) hierarchy such as dms.example.Nat <: dms.example.Int <: dms.example.IntegralType <: dms.example.Number <: dms.BasicType. In general, basic types can be organized to form a sub-type hierarchy lattice instead of just a tree rooted at dms.BasicType. That is, user-defined basic types can inherit from one or more basic types.

Declaration

In DMS, a basic type can be declared as a Scala trait (e.g., dms.example.Number) or class (e.g., dms.example.Int), and optionally, accompanied by a companion object. (One can optionally use an abstract class instead of trait to enforce more type structure.) Regardless, the declared basic type should inherit from either dms.BasicType directly or other basic types. As a class, the basic type is required to implement (or inherit implementation of) “abstract” methods declared in dms.BasicType such as value, which can hold any Scala type internally. In the grammar, this is satisfied by declaring a val named value in a class‘ constructor. Thus, a basic declaration can be viewed as simply a wrapper type for existing type (<basicInternalType>) that is exported to the modeling type universe.

Operations

In addition, a basic type declaration can override dms.BasicType methods such as:

  • asString, which produces a String representation (<scalaExp: java.lang.String>) of the “wrapped” internal value of the basic type instance. By default, it is implemented as simply calling the toString method of the internal value. One can override this method if the value‘s toString method does not give the expected behavior such as the case for dms.example.Int.
  • toString, which produces a String representation for, for example, debugging purposes. By default, it gives a String consisting of the basic type name appended with the result of its asString wrapped in a parenthesis (e.g., ...Int(0)).

Furthermore, one can additionally declare pure operations on basic types as Scala methods (<basicOpMethod>) to export available operations on basic type values that are useful, for example, for stating invariant over device model attribute values. The pure qualifier means that they are non-side-effecting value operations (recall that DML basic type values are immutable). The reader is referred to dms.Boolean, dms.example.Number, dms.example.Int, and dms.example.Nat for examples of <basicOpMethod>.

Factory and Implicit Conversion

In the companion object for a basic type, one can provide implicit factory methods as apply methods to ease attribute initialization (basicTypeApply and <basicTypeApplyOther>).

To illustrate implicit conversion, consider a feature example below:

1
2
3
4
5
6
import edu.ksu.cis.santos.mdcf.dms._
import edu.ksu.cis.santos.mdcf.dms.example._

trait ExsBasicType extends Feature {
  val foo : Int = 5
}

At line 5, the val foo is declared to be of type dms.example.Int; note that the type of the literal 5 is scala.Int, thus, there seems to be a type mismatch. In this situation, the Scala compiler looks up an implicit method that can convert scala.Int to dms.example.Int in the dms.example.Int companion object; in this case, it found that it can use dms.example.Int apply. Thus, the compiler automatically inserts the call to apply before assigning to foo so the resulting compiled code is equivalent to the following example:

1
2
3
4
5
6
import edu.ksu.cis.santos.mdcf.dms._
import edu.ksu.cis.santos.mdcf.dms.example._

trait ExsBasicTypeApply extends Feature {
  val foo : Int = Int.apply(5)
}

One needs to be careful to always explicitly type attribute val; failing to do so gives result to an unexpected type as illustrated in the example below.

1
2
3
4
5
6
import edu.ksu.cis.santos.mdcf.dms._
import edu.ksu.cis.santos.mdcf.dms.example._

trait ExsBasicTypeFail extends Feature {
  val foo = 5
}

At line 5, the Scala compiler infers the type of foo to be scala.Int. When extracted, the dms.ModelExtractor will gives an error indicating that foo‘s type is not allowed as it is not in the modeling type universe.

The grammar recommendeds that there is at least one implicit factory method that converts the basic type’s <basicInternalType> to the basic type. Alternatively, in the lack of such implicit factory method, one can create the basic type value explicitly as shown in the example below (note that when explicitly creating a basic type value, one can optionally leave out to explicitly give a type of an attribute val):

1
2
3
4
5
6
import edu.ksu.cis.santos.mdcf.dms._
import edu.ksu.cis.santos.mdcf.dms.example._

trait ExsBasicTypeExplicit extends Feature {
  val foo = new String("foo")
}

Representation Classes

Basic type declarations are represented using the dml.ast.BasicType AST class. A basic type can be referred by its fully qualified named stored in BasicType.name (inherited from Declaration.name) using the dml.ast.NamedType AST class as can be observed how dml.ast.BasicType refers to its super basic types in supers. Attribute initialization whose type is a basic type is represented using the dml.ast.BasicInit AST class, which stores the basic type internal String value in dml.ast.BasicInit value (the String value is retrieved from dms.BasicType asString discussed previously).

Symbol Table

There are several methods provided by the symbol table API related to basic types. Below is the list of relevant methods (please see the documentation in the dml.symbol.SymbolTable that describes the methods):

Well-Formedness

The set of basic types in models are well-formed if:

  1. For each basic type, each of its super types is either another basic type or dms.BasicType.
  2. There is no circularity in the basic type subtype hierarchy.

In DMS, the dms.ModelExtractor enforces the first rule; more precisely, it only recoqnizes a Scala type as a basic type if it is a descendant of dms.BasicType (and not a descendant of dms.Feature). The second rule is enforced by the Scala compiler.