4. Syntax

A DNAL program consists of a series of statements. Note that statements do not end with “;”.

type CelciusTemperature number end
type FarenheitTemperature number end

Comments are anything after “//” and extend to the end of the line.

DNAL is case-sensitive.

4.1. Naming Conventions

Type names start with upper-case letter and are camel case

ProductCode

Value names start with lower-case letter and are camel case

productCode

4.2. Scope

Types must be defined before values.  They can be defined with the same source file or imported from another file using import (See Package below).

import com.example.ProductCode

let pc5 ProductCode = 'XC5023M'

Types may refer to themselves.

  type Node struct {
     next Node,
     payload string
  }

5. Types

5.1. Primitive Types

DNAL has primitive types. These can be used directly or as base types for custom types.

boolean

Represents true or false

int

A 32-bit integer (equivalent to Java int)

long

A 64-bit integer (equivalent to Java long)

number

A floating point number (equivalent to Java Double)

date

A date and time (equivalent to Java java.util.Date)

list

a sequence of values (of the same type)

struct

a tuple of name, value pairs

enum

a single value from a named set of values.

5.1.1. Shape

Unlike other languages, there is no common ultimate base class such as Java’s Object class. Each of the primitive types is a base class for all sub-types of that data type. This is called the type’s shape. For example, the following DNAL defines a type Temperatue
which has shape “number”.

 type Temperature number end

 let x Temperature = 32.5

An important aspect of shapes is that values can only be assigned an explicit value of the same shape. In the above example, the value “x” must be assigned a number shape such as “32.5”. A boolean type requires a boolean shape such as true, etc.

Shape is also important when rules are defined. See Rules below.

5.2. Scalar and Compound Types

Scalar types hold a single value. The scalar types are boolean, int, long, number, stringdate, and enum.

Compound types hold more than one value. The compound types are list and struct.

5.3. Custom Types

Custom types can be used to represent domain types, such as ZipCode or ProductSerialNumber. This makes the data more meaningful (and easier to validate).

Custom types are created using the type keyword, a name, a base type, and an optional list of rules.

type SchoolMark int
  >= 0,
  <= 100
end

This defines a type named SchoolMark. It is based on int and has a value between 0 and 100. The base type can be a primitive type or another custom type. Types inherit the rules of their base type.

5.3.1. Struct Types

A struct represents a tuple of values

type Person {
  firstName string,
  lastName string,
  birthDate date
}
 !empty(firstName) or !empty(lastName) //one can be empty, not both
end

The rules of a struct may refer to the members of the struct

5.3.2. List Types

A list is a sequence of values of a given type.

type Employees list<Employee> end

5.3.3. Enum

An enumeration is used to represent one of a fixed set of possible values

type enum Color {
  RED,
  BLUE,
  GREEN
}

(The following is not yet supported in DNAL) An enum may have an underlying value. This can be useful when generating code from DNAL types.

type enum Color int {
  RED = 10,
  BLUE = 12,
  GREEN = 12
}

Enums cannot inherit. All enums must be based on enum.

5.4. Flags

5.4.1. Optional

The optional keyword indicates the value is allowed to be missing (i.e. null). If optional is not specified then a value must be supplied. optional can only be used on the fields of struct types.

type Person struct {
  voucher Voucher optional,
  //..other members..
}

5.4.2. Unique

The unique keyword indicates that the value is unique within all instances of that type.
It is similar to a primary key in a database (see Via below)

type Person struct {
  id int unique,
  //..other members..
}
Note
unique is not yet supported.

6. Packages

To make types easier to find and use, to avoid naming conflicts, programmers bundle groups of related types into packages.

  • packages are optional. If you do not use a package statement, the types are put in a default package
  • packages are of the form:
  package com.example.mypackage
  • a source file can have at most one package statement. If present, it must be the first statement in a source file.

6.1. import

The import statement is used to load the types and values of another DNAL source file. The effect of an import statement is equivalent to replacing it with the contents of the imported file.

import com.example.mypackage.AirportTypes

Unlike Java, the package includes the DNAL filename as the last element. The above statement would import the contents of file ./com/example/mypackage/AirportTypes.dnal

7. Rules

See Rules

8. Values

Named values are specified using the let keyword. The following defines a named value called numRetries.

  let numRetries int = 5

Values consist of a name, a type, and a value.

8.1. Name

The name needs to be unique within the current package. It cannot be the name of a type or primitive type or reserved word.
Names have the same syntax as Java names — starting with “_” or alpha and then a series of  “_”, alpha, or digits.

8.2. Type

The value’s type must have been declared. It can be a type in the current package or an imported package.

8.3. Value

The value must be an one of the following: an explicit value, a reference to another value of compatible type, or a “via” statement (see below).

 let x int = 45  //an explicit value
 let y int = x   //a reference of another value

Assignments are type-compatible. If the type Circle is a sub-type of Shape, then circle values can be assigned to shapes, but not vice versa.

type Shape struct {
 x int,
 y int
} end

type Circle Shape {
 radius int
} end

let myShape Shape = { x:100, y:100 }
let myCircle Circle = { x:100, y:100, radius:40 }

let shape1 Shape = myCircle   //this is OK
let shape2 Circle = myShape   //not allowed ERROR

8.4. Explicit Values

8.4.1. int

int values are Java int values. int represents a 32-bit signed integer.

Note
Hex format is not currently supported. Only base-10 format values such as 1984.

8.4.2. long

long values are Java long values. long represents a 64-bit signed integer.

8.4.3. number

number values are Java double values. double represents a double-precision 64-bit IEEE 754 floating point.

Note
Both floating point values such as -145.237 and scientific format such as 3.553e12 are supported.

8.4.4. boolean

boolean values are Java boolean values. boolean has only two possible values: true and false.

8.4.5. string

string values are Java String values.

Sample String

Description

“the team’s score”

Double-quotes. Can contain single quotes.

the “full” Monty

Single-quotes. Can contain double quotes.

“the \”full\” Monty”

Double-quotes with escaping

the team’s score

Single-quotes with escaping.

first line\nsecond line

Java control characters are supported.

TBD

Unicode or \uxxxx.

8.4.6. date

date values are Java java.util.Date values. The text format for dates is based on ISO 8601.

Sample Date

Description

“2015-12-31T23:40:05.345”

Full time in local time

“2015-12-31T23:40:05.345UTC”

Full time in UTC time zone

“2015-12-31T23:40:05”

without milliseconds. Missing values are defaulted to zero.

“2015-12-31T23:40”

without milliseconds or seconds. Equivalent to “2015-12-31T23:40:00.00”

“2015-12-31T23”

yyyy-MM-dd’T’HH Equivalent to “2015-12-31T23:00:00.00”

“2015-12-31”

yyyy-MM-dd Equivalent to “2015-12-31T00:0:00.00”

“2015-12”

yyyy-MM Equivalent to “2015-12-01T00:00:00.00”

“2015”

yyyy Equivalent to “2015-01-01T00:00:00.00”

8.4.7. enum

An enum value must be one of the defined symbols in its enum type.

8.4.8. list

list values are Java java.util.List values. The elements of the list must be type-compatible.

Sample List

Description

[ ]

the empty list

[10, 11, 12]

list elements are separated by commas

[{100, 100}, {120, 155), {130, 182}]

list of structs. When using explicit values the values must exactly match the declared type of the list (eg. list).

Note
Lists of structs can contain a mixture of type-compatible values. See Virtual Data below.

8.4.9. struct

struct values are Java java.util.Map values.

Sample List

Description

{ }

an empty struct. Only allowed if the struct type has no fields, or all fields are optional

{ Bob, Smith }

field values are separated by commas. If field names are not supplied then they represent the fields in their declared order in the struct.

{ lastName:’Smith’ firstName:’Bob’ }

If field names are supplied, the values can be out of order

{ null, Smith }

null can be used if the field has been declared optional.

{ firstName:null, Smith }

null can be used if the field has been declared optional.

8.5. Relations using Via and isa

An essential part of data is relations between data values. Relations can represent One-to-Many or Many-to-Many relations. DNAL can validate relations according to the rules you provide. isa and via can be used together or apart. There are six combinations. Let’s have a look at them.

8.5.1. via

Consider a Person type that has a relation to Address

type Address struct {
  id int,
  street String,
  //other fields...
} end

type Person struct {
  firstName string,
  addr  Address,
  //other fields
} end

We could simply declare the addess data inside each Person value. However if several people share the same address (such as members of a family), it’s better to use a relation.

let addresses list<Address> = [
  { 101, '450 Main St.' },
  { 102, '12 Holmwood St.' },
  { 103, '55 Pine Ave.' },
  //etc...
]

let persons list<Person> = [
 { 'bob', 'via Address.id '103'},
 { 'sue', 'via Address.id '101'},
 { 'art', 'via Address.id '103'}, //art lives with bob
 //etc..
]

Here the via statement specifies an Address id to find. DNAL will validate that one and only one such Address exists. If not, a validation errors occurs (and the compilation will fail).

8.5.2. isa

Consider the same Address type but now we store address id in Person, instead of a full Address reference.

type Person struct {
  firstName string,
  addrId  int,
  email string,
  //other fields
}
 addrId isa Address.id
end

Now we supply the address id values for each Person, and DNAL will validate that an Address exists for each. Again their must only be one matching Address or a validation error occurs.

let addresses list<Address> = [
  { 101, '450 Main St.','sue@c.com' },
  { 102, '12 Holmwood St.','robert@c.com' },
  { 103, '55 Pine Ave.','art.and.bob@d.com' },
  //etc...
]

let persons list<Person> = [
 { 'bob', '103'},
 { 'sue', '101'},
 { 'art', '103'},  //art lives with bob
 //etc..
]

8.5.3. isa and via

We can use both isa and via together. Here Person has addrId as the previous example.

type Person struct {
  firstName string,
  addrId  int,
  //other fields,
}
 addrId isa Address.id
end

However, we use a via to load the data using a query on a different field. The isa rule will still execute and validate all the addrId values.

let addresses list<Address> = [
  { 101, '450 Main St.','sue@c.com' },
  { 102, '12 Holmwood St.','robert@c.com' },
  { 103, '55 Pine Ave.','art.and.bob@d.com' },
  //etc...
]

let persons list<Person> = [
 { 'bob', 'via Address.email 'robert@b.com'},
 { 'sue', 'via Address.id 'sue@c.com'},
 { 'art', 'via Address.id 'art@d.com'},
 //etc..
]

8.5.4. via with a List

Consider a Person type that has a relation to multiple Address values.

type Address struct {
  id int,
  street String,
  //other fields...
} end

type Person struct {
  firstName string,
  addresses list
//other fields } end

Now if Address id is not unique, using a via can result in zero, one, or more matching values. These are stored in the list addresses.

let addresses list<Address> = [
  { 101, '450 Main St.','sue@c.com' },
  { 102, '12 Holmwood St.','robert@c.com' },
  { 103, '55 Pine Ave.','art.and.bob@d.com' },
  { 103, '2189 Carling Ave.', 'emily@e.com' },
  //etc...
]

let persons list<Person> = [
 { 'bob', 'via Address.id '102'},
 { 'sue', 'via Address.id '101'},
 { 'art', 'via Address.id '103'}, //will find 2 matching addresses
 //etc..
]

8.5.5. isa with a List

Consider a Person type that has a relation to multiple addresses, using a list of Address id values.

type Person struct {
  firstName string,
  addrIds  list,
  //other fields
}
 addrIds isa Address.id
end

Now we supply the address id values for each Person, and DNAL will validate that an Address exists for each. Again their must only be one matching Address for each id value or a validation error occurs.

let addresses list<Address> = [
  { 101, '450 Main St.','sue@c.com' },
  { 102, '12 Holmwood St.','robert@c.com' },
  { 103, '55 Pine Ave.','art.and.bob@d.com' },
  //etc...
]

let persons list<Person> = [
 { 'bob', ['102', '103'},
 { 'sue', ['101']},
 { 'art', []},  //has no addresses
 //etc..
]

8.5.6. isa and via with a List

We can use both isa and via together. Here Person has addrId as the previous example.

type Person struct {
  firstName string,
  addrIds  list,
  //other fields
}
 addrIds isa Address.id
end

We use a via to load the data using a query on a different field. The isa rule will still execute and validate, but since addrIds is list, then matches of zero, one, or more Address values are valid.

let addresses list<Address> = [
  { 101, '450 Main St.','sue@c.com' },
  { 102, '12 Holmwood St.','robert@c.com' },
  { 103, '55 Pine Ave.','art.and.bob@d.com' },
  //etc...
]

let persons list<Person> = [
 { 'bob', 'via Address.email 'robert@b.com'}, //finds 102
 { 'sue', 'via Address.id 'sue@c.com'},  //finds 101
 { 'art', 'via Address.id 'zeke@d.com'}, //finds nothing
 //etc..
]

8.6. Virtual Data

One aspect of the type system is that values can be assigned using a reference to a matching type or to a sub-type Let’s consider several types

type PersonName string
 len > 0,
 len < 50
end

type NickName PersonName
 len < 10
end

type Employee struct {
  name PersonName
}

let person1 PersonName = 'Robert'
let person2 NickName = "Bobby"

let employees list<Employee> = [
 { person1 },
 { person2 },
 { "Zoe" }
]

While Employee.name has type PersonName, the type of the three values specified above are

  • PersonName
  • NickName
  • PersonName

That is, when using references, the value assigned may be different (but compatible) to the let or member’s declared type.
When using explicit values, the value assigned is always the same as the let or member’s declared type.