Chapter 1

Introduction

Minilang is a dynamically typed interpreted scripting language, designed to be embedded into larger applications in order to allow application users to run their own code in a safe and controlled manner. This can be to customize or extend the application, or as the main feature of the application (e.g. a serverless development platform). Minilang code can also be run directly by the standalone interpreter, which can used to create scripts or desktop applications.

Here's some sample Minilang code.

  • 2 3.14 "Hello world!"
  • [1, 2, 3] {"a" is 1, "b" is 2, "c" is 3}
  • Values

    Nil

    Minilang has a dedicated value, nil, for representing the absence of any other value. Variables and parameters that are not assigned any other value will have a value of nil. nil is also returned from operations and methods which do not generate a result or generate a "negative" result such as accessing a missing key in a map, indexing outside the range of a list and comparison operations that are not satisfied.

  • nil {"a" is 1, "b" is 2, "c" is 3}["d"] [1, 2, 3][4] 1 > 2
  • Numbers

    Numeric values (integers and real numbers) follow the usual syntax.

  • 2 1000 -50 3.14159 1.23e45
  • Minilang provides the usual infix and prefix arithmetic operators.

    Note

    Infix operations in Minilang do not have any precedence rules, i.e. all infix operations are evaluated from left to right, except that operations in parentheses are evaluated first.

  • 2 + 3 4 * 7
  • 2 + 3 * 4 :> evaluates as (2 + 3) * 4 = 20 2 + (3 * 4) :> evaluates as 2 + (3 * 4) = 14
  • 1 / 2 3 ^ 3
  • Strings

    Strings values are written between double quotes, "...". Non-printable characters can be written using escape sequences, \ followed by one or more characters.

  • "Hello world!" "This is a tab\tand a new line\n" "Unicode characters can be written directly: ☺" "or with escape sequences: \u263A"
  • Minilang provides operators and methods for working with strings.

    Note

    In Minilang, string indexing (as well as indexing lists, arrays, etc) begins at 1. In addition, -1 refers to the last index, with 0 referring to just beyond the last index, useful for slicing strings and lists.

  • "Hello " + "world!" "Hello world!"[1, 6] "Hello world!"[7, -1] "Hello world!"[12, 0]
  • "Hello world!":upper "Hello world!":lower
  • "Hello world!":find("world") "Hello world!":find("Goodbye")
  • Variables

    There are 4 ways to declare variables in Minilang using different keywords, let, var, def and ref. The last 2 of these are not commonly used so will be explained in later chapters. In general, the same identifier can not be used to declare more than one variable within the same block / scope, but can be used in nested blocks / scopes. As a special exception to this rule, interactive environments, including the embedded code blocks in this tutorial, often allow the same identifier to be redeclared, overwriting the previous declaration.

    let declarations

    A variable declared with let Name := <expression> has the value of <expression> for the rest of the block / scope. Such variables can not be reassigned. This is the preferred way to declare variables in Minilang.

    Note

    If the result of <expression> has mutable contents like a list, map, etc, the contents can still be modified even if let is used. Is it only the variable that can not be reassigned.

  • let X := 1 + 2 X X + 3 X := 4
  • Note

    The error message in the last example, TypeError: <integer> is not assignable, is due to the left-hand side of the assignment, X, evaluating directly to 3, which can not be assigned a new value. The full details of how references and assignments work in Minilang will be explained in later chapters.

    var declarations

    If it necessary to modified the value of a variable itself over its lifetime, the variable must be declared using var Name := <expression>. In this case, <expression> can be omitted, in which case the value nil is used instead.

  • var Y := 1 + 2 Y Y + 3 Y := 4 Y + 3
  • var Z Z Z := 10 Z
  • Blocks

    Every construct in Minilang is an expression, that is they evaluate to a value. This includes if-expressions, switch-expressions and even for-expressions and loop-expressions. These will be covered individually later, in general these expressions use blocks of code in their bodies / branches. A block of code in Minilang is simply a number of declarations and expressions. After evaluating each expression in the block, the result of the last expression is returned as the value of the block.

    If required, a block can be introduced whenever a single expression is expected by using do ... end.

  • print(do let X := "Hello" X + " world!" end)
  • Expressions and declarations within a block can also be separated by semi-colons ;. The example above can be rewritten as:

  • print(do let X := "Hello"; X + " world!" end)
  • Error handling

    When an error occurs in Minilang code, an error value is created. Execution then jumps to the nearest surrounding error handler, passing this error value. If there is no error handler within the current function, the error is returned to the calling function which then jumps to the nearest surrounding error handler and so on. When run from the command line, an unhandled error will cause Minilang to terminate, displaying information about the error in the console. Interactive sessions such as the sample code blocks in this tutorial will display the error but still allow more code to be evaluated.

  • 1 + "a"
  • 1 / 0
  • Any block of code can contain an error handler using the syntax on Variable do, where Variable is a variable that will receive the error details. Usually, Error is used as the variable name.

  • for X in [1, 0, 2.5, "a", nil, 5] do print(2 / X, "\n") on Error do print('An error occurred: {Error}\n') end
  • When an error occurs, the value assigned to Error is an error value. Details of the error can be extracted from Error using methods;

    • Error:type - the type of error, usually something like "TypeError", "RangeError", etc.

    • Error:message - a longer, more specific description of the error.

    • Error:trace - a trace of function calls (source names and line numbers) leading to the error

    Finally, a given error value can be re-raised after handling using Error:raise.

    Note

    Since an error can occur in any step of a block, variables declared in a block are not visible to the error handler for that block. If it is needed to access a variable within an error handler, the variable must be declared outside the error handler's block. This often requires introducing a new do-block to contain the error handler.

  • fun test() do let X := 1 - 1 let Y := 2 / X on Error do print('Could not evaluate {2} / {X}\n') end test()
  • fun test() do let X := 1 - 1 do let Y := 2 / X on Error do print('Could not evaluate {2} / {X}\n') end end test()