Skip to content

Language Rules

paulgo edited this page Nov 6, 2018 · 8 revisions

Language Rules

Plurals

Anonymous Types Go In, Not On Plurals

Anonymous types are disallowed directly on plurals. And so the following is an error:

Number

Type...
	// Error is generated
	: Number

Resolving In vs On Ambiguity

Although a highly unlikely to exist in the real world, there exists a case where a type is added directly to a plural that is the same type as the plural itself. This causes an ambiguity when trying to populate the plural, because it is unclear whether the intent is to populate the plural, or to add a place a type on the plural itself. For example, assume the following code:

Foo
FooA, FooB : Foo
Foos : Foo...

Now, let's assume that we want to attach another statement to Foo... plurals, and the type of that statement is another Foo:

Foo...
	AnotherFoo : Foo

Without any additional language rules, this would create an ambiguity:

Foo...
	foo : Foo

Foos : Foo...
	// Is this:
   // A) adding 'AnotherFoo' to Foos? Or,
	// B) subtyping the AnotherFoo type defined on Foo as a FooA?
	AnotherFoo : FooA

In this case, scenario A would apply (the AnotherFoo would just be another element of the Foos plural). If this were to not be the desired behavior, the work around is to simply to assign AnotherFoo separately:

Foo...
	foo : Foo

Foos : Foo...
	
Foos...
	AnotherFoo : FooA

No Is-a Sides For On-Plural Types

On-Plural types cannot have an Is-a side. For example, the following generates an error:

Foo
Item

Items : Item...
	: Item
	: Item

// Error
Item... : Foo

No Multi-Dimensional Plurals

Pluralizing an already pluralized type isn't allowed. For example:

Animal
Animals : Animal...

// Error
Animalses : Animals...

Instead, the same effect of a multi-dimensional plural can be achieved via an intermediate type. For example:

Cell

Row
	Cells : Cell...

Table
	Rows : Row...

An open proposal to support multi-dimensional plurals is found [here](../Proposals/Multi-Dimensional Arrays.md).

Regular Expressions

Colons Inside Regular Expressions Must Be Escaped

Colons inside regular expressions must be escaped, otherwise, this colon will be recognized as the has-a / is-a separator for the the statement:

/regular\:expression/ : Thing

More formally, the actual parsing rule is that the first non-escaped colon character acts as the statement separator (called the "Joint").

Regular Expressions Can't Match Plurals

// Error
/[0-9]/ : Number...

Regular Expressions Match Types Exactly As Stated

A regular expression only matches the exact types stated in it's is-a side of the statement, and nothing further. For example:

String
Number
/[0-9]/ : Number

Foo
	Value : String, Number

Bar : Foo
	// Error
	Value : 123

Remember that the comma operator is an AND operation. String, Number means String and Number. The value of 123 does not fit the definition because it was defined as a String and a Number, whereas the regular expression was defined as matching only numbers.

Regular Expressions Match Everything After the :

Regular expressions always need to match the entire is-a side of a statement after being trimmed. Therefore, the ^ and $ markers within a regular expression don't have any special meaning (because they're implied), and so matching them directly doesn't require them to be escaped. This also means that regular expressions can't match across multiple lines.

Regular Expressions Create Aliases, Not Subtypes

Consider the following example:

Number
/\d+/ : Number

Employee
	Salary : Number

// 50000 is considered a Number, not a subtype of number
Developer : Employee
	Salary : 50000

// And so this is still valid
LeadDeveloper : Developer
	Salary : 100000

Regular expressions are special in that they don't create new types. They only provide new ways of refer to a existing types. From a type-compliance point of view, the salary "overrides" from the example above are all the same as saying Salary : Number. For this reason, content matched by regular expressions on the is-a side of a statement function more like default values than explicit type constraints. To clarify, without this language rule, if is-a side content matched by a regular expression was considered a subtype, then the Salary of any type extending Developer would need to be exactly 50000, which would be undesirable in most cases.

Structure

Polymorphic Type Resolution

The Truth core only has a very loose notion of encapsulation. When an external Truth file is imported into a document, theoretically all the terms defined in the external file become available to the importing document (subject to the scoping rules). If Truth were to simply copy the standard scoping rules from languages such as JavaScript, this could cause undesirable redeclaration of types, essentially rendering some of the imported types inaccessible. Take for example, the following Truth file that declares information about hotels:

// -- Hotel.truth --

Feature
Tennis Court : Feature
Hot Tub : Feature
Patio : Feature
Pool : Feature

Hotel
	Features : Feature...

If an importing file were to re-declare Pool to have a new meaning, Pool still needs to remain accessible. Otherwise, it wouldn't be possible to add Pool as an item in a Hotel's list of features:

Hotel.truth

Game
Pool : Game
Chess : Game
Darts : Game

Hilton : Hotel
	Features
		// ?
		Pool
		Patio

The rule adopted by most languages is that the declaration of an identifier with the same name as another identifier from a higher-level scope causes that higher-level identifier to be overridden. The higher-level identifier becomes inaccessible to the current scope, and any scopes nested inside of it. Under this rule, Pool in this case would be a Game rather than a Feature. However, in the context of a list of Features, we're clearly referring to the Pool like swimming pool rather than Pool like the game. To address this case, Truth uses a type resolution strategy we call Polymorphic Type Resolution.

Instead of climbing up the scope chain looking for the Pool identifier, finding that the first definition of Pool is of the wrong type (Game, not Feature), and then proceeding to generate an error, Polymorphic Type Resolution considers all matching terms from the entire scope ancestry, all the way back to the root. It then internally organizes all occurrences of the term in a list. It then selects the instances of the term from this list that complies with the type definition. An error is then generated only after this list has been found to not contain a sufficient number of terms to conform to the definition specified at the call site.

Unspecified Types Aren't Recognizable Bases

Truth has the concept of Unspecified Types, which are types that exist within a scope implicitly. For example:

Number

Animal
	Weight : Number

Rabbit : Animal
	// Rabbit actually has a Weight type, 
	// but it's in the "Unspecified" state.

These Unspecified Types don't affect type resolution. For example:

Number
Decimal : Number
Weight : Number

Animal
	// The type of "Weight" is inferred as Number
	Weight : Decimal

Rabbit : Animal

	// There's actually an unspecified "Weight"
	// type here, whose type is "Decimal", which
	// is acquired from "Animal"
	
	Head
		// This Weight type is inferred as
		// Number, not Decimal.
		Weight

Multiple Inheritance Can't Create Naming Conflicts

In most programming languages that support it, multiple inheritance typically has the unfortunate side effect of causing naming conflicts. This occurs when type A inherits from types B and C, but both B and C have members named D. This doesn't occur in Truth, because types that extend from multiple sources are given the union of the statements owned by each source. For example:

A
B

ClassA
	Value : A
	
ClassB
	Value : B

// ClassAB has an implicit statement called Value which is of type A, B
ClassAB : ClassA, ClassB

From the example, if Value defined in both ClassA and ClassB were to have the same type, the result is still a union:

A

ClassA
	Value : A
	
ClassB
	Value : A

// ClassAB has an implicit statement called Value which is of type A
ClassAB : ClassA, ClassB

Statements Can Be Recursive

Although occasionally undesirable, it's possible to create a recursive statement structure in Truth:

Number
	Min : Number
	Max : Number

// Means that this possible

Number
	Max
		Number
			Max
				Number
					Max
						// And so on...

If this is undesirable behavior, an Agent working with an additional definition can be used to impose restrictions on specific types to prevent this:

Number
	Min : Number, Halted
	Max : Number, Halted

// Halt would be recognized by the Agent, 
// which would prevent the recursive nature

External Local Files

Truth files reference other Truth files simply by writing out a URI that points to the other file:

External.truth

Animal
Rabbit : Animal

In this case, the compiler looks for a file in the same folder as the local file, and includes it's statements. These are treated as if they were declared on a higher level scope, so declaration merging doesn't occur. For example:

-- File1.truth --

Animal
Rabbit : Animal

-- File2.truth --

File1.truth

Creature
Rabbit : Creature

Rabbit defined in File2.truth would be declared as a Creature, not a Creature, Animal.

External Web-Based Files

In addition to local paths, truth files can reference other files which may be located via a web path. Web paths in Truth use browser-style protocol-relative URIs. For example:

//www.domain.org/External.truth

Animal
Rabbit : Animal

File Relationships Cannot Be Cyclical

While it is possible to import Truth code contained in other files, doing so cannot create a circular relationship. The two documents described below would result in an error:

// -- File1.truth --

File2.truth
// -- File2.truth --

File1.truth

(Diamond inclusion patterns are still permissible.)

Comments

The comment token in Truth is actually two forward slashes followed by a whitespace character:

// Comment

//Not a comment

Valid Type Names

Truth imposes very few restrictions on type names. The only character that cannot exist within a type name is the comma (,). However, commas can still be matched within a regular expression if the comma is escaped. For example:

/A\,B/ : C

If the type name contains a colon, or starts with a forward slash, it must be escaped. Take for example the following code, which is entirely valid, due to the escaped colon:

UriScheme
http\: : UriScheme

Request
	UriScheme

MyRequest : Request
	UriScheme : http: