Skip to content

Flxble.Templating: The language

cannorin edited this page Jan 16, 2019 · 6 revisions

The flxble's templating language is a dynamically-typed ML-like (F#-ish) language and consists of:

  • Values .. The normal forms.
  • Expressions .. Things which are not values but evaluate to values.
  • Statements .. Instructions which the engine executes. Statements are not expressions nor values.

Values

There are 10 kinds of values.

  • Boolean values
  • Integer values
  • Float values
  • String values
  • Null values .. Indicates an error. Can contain an error message.
  • Date values .. Represents a date time.
  • TimeSpan values .. The difference between two date values.
  • Array values
  • Record values .. A hashtable.
  • Function values

Expressions

An expression evaluates to a value. If there is an error, it will evaluate to a Null value containing the error message. If there is an error before evaluating the expression (e.g. when the argument of a function call raises an error), it will be passed to outer expressions so you can always see the error message explaining the root cause.

Literal Expressions

A literal expression creates a value.

  • Boolean literals ... true, false.
  • Integer literals ... 0, -1, 42, etc.
  • Float literals ... 4.2, 1.2e+3, inf, nan, etc.
  • String literals ... "Lorem ipsum", etc.
  • Null literals ... null, ()

Arrays, Records, and Functions have their own expressions to create their values and will be introduced later.

Dates and TimeSpans do not have literal expressions nor their own expressions. Instead, you can use the following functions:

date.new : Record -> Date

Creates a new Date value.

def d = date.new {
  year = 2019,
  month = 1,
  day = 1,
  hour = 12,
  minute = 34,
  second = 56,
  millisecond = 789
}

The fields year, month, and day are required. The other fields are optional and will be 0 if omitted.

timespan.new : Record -> TimeSpan

def t = timespan.new {
  day = 1,
  hour = 23,
  minute = 45,
  second = 6,
  millisecond = 789
}

All the fields are optional and will be 0 if omitted.

Variable expressions

A valid variable has a form of [a-zA-z_]+[a-zA-z0-9_]*. e.g. foo1, _foo_bar.

Variables can be defined by Let-expressions and Define-statements.

A variable evaluates to the corresponding value. If not defined, it evaluates to Null value with an error message (the variable '<name>' does not exist).

Let expression

A let expression creates a local variable to be used inside a scope.

let <variable_name> = <expression> in <expression>

e.g. let x = 41 in let y = 1 in x + y evaluates to 42.

Lambda abstraction expression

A lambda abstraction creates a function value.

fun <arg_name>+ -> <expression> 

e.g. fun x y -> x + y evaluates to a function that computes the sum of the two input values.

Function definition by let expression

let <variable_name> <arg_name>+ = <expression>

is a syntax sugar of

let <variable_name> = fun <arg_name>+ -> expression

Application expression

An application expression applies arguments to a function.

<expression> <expression>+

e.g. (fun x y -> x + y) 21 21 and let f x y = x + y in f 21 21 both evaluate to 42.

Application expression supports partial application; for example, (fun x y -> x + y) 21 evaluates to a new function that adds 21 to the input value:

let f x y = x + y in
let g y = f 21 in
g 21

Binary operator expression

A binary operator expression is just like the application expression but in an infix form.

e.g. 1 + 2 evaluates to 3 by the definition of +.

Treating operator as an ordinary function

You can use an operator as a function by enclosing it by `.

e.g. `+` 1 2 evaluates to 3.

Array expression

An array expression creates an array value.

[ <expression>, <expression>, ... , <expression> ]

e.g. [1+2, 3+4, 5+6] evaluates to [3,7,11].

Record expression

A record expression creates a record value.

{ <field_name> = <expression>, ..., <field_name> = <expression> }

e.g. { a = 1 + 2, b = true && false } evaluates to { a = 3, b = false }.

Record extension expressions

A record expression creates a new record value from an existing record value.

The original record value will not be changed.

{ <expression> with <field_name> = <expression>, ..., <field_name> = <expression> }

e.g. if we have def x = { a = 3, b = false }, then { x with c = 42.0 } evaluates to { a = 3, b = false, c = 42.0 }.

Indexer access expression

An indexer access expression returns the value associated with the specified key.

<expression>.[<expression>]

e.g. [1,2,3].[0] evaluates to 1, and { a = 1, b = "foo" }.["b"] evaluates to "foo".

Indexer access to Dates and TimeSpans

A Date value accepts the following keys: year, month, day, hour, minute, second, millisecond.

Similarly, a TimeSpan value accepts the following keys: day, hour, minute, second, millisecond.

They all return an integer value.

e.g. You can get the current year in an integer value with date.now.year.

Member access expressions

A member access expression is just a syntax sugar of an indexer access expression with a string key.

<expression>.<field_name>

e.g. { a = 1, b = "foo" }.b evaluates to "foo".

If expression

An if expression first evaluates the condition, then returns one of the two expressions.

if <expression> then <expression> else <expression>

e.g. if the_ultimate_anwser = 42 then "foo" else "bar" evaluates to "foo".

Statements

Print-expression statement

Open statement

Define statement

Partial statement

Block statement

When statement

For statement

Raw text statement