Skip to content
Jan Špaček edited this page Apr 12, 2016 · 1 revision

The syntax of Spiral is heavily influenced by the syntax of Scheme. The programs are composed from fully parenthesized s-expressions that are simple for the parser and pleasing to the eye.

Programs and modules

<top-level>   = <program> | <module>
<program>     = (program <stmt>...)
<module>      = (module <ident> <decl>...)
<decl>        = (export <ident>...) | <stmt>

A program consists of a sequence of statements that are executed in textual order. A module can also contain exports, denoting the names that the module provides to other modules and programs.

Statements

Statements can define imported names, named functions or plain variables.

<stmt>        = (import <import-def>...)
              | (fun <ident> (<ident>...) <stmt>...)
              | (var <ident> <expr>)
              | <expr>
<import-def>  = <ident>
              | (only <import-def> <ident>...)
              | (except <import-def> <ident>...)
              | (prefix <import-def> <ident>)
  • (import <import-def>...) The import statement loads a module and exposes the imported variables in the active context. Specified list of names can be imported using (only ...), some names can be excluded using (except ...) or the imported names can be prefixed with an identifier using (prefix ...).

    Examples of imports:

    • (import std std.math) imports all names from modules std and std.math.
    • (import (only std + - *)) imports from std just the names +, - and *.
    • (import (except std.math sin cos)) imports from~std.math all names except sin and cos.
    • (import (prefix (only std.math div mod) m.)) imports from std.math names div and mod prefixed m. (so we will refer to them as m.div and m.mod).
  • (fun <fun-name> (<arg>...) <body-stmt>...) defines a named function. Multiple named functions defined next to each other can be mutually recursive, because a function defined earlier can refer to the functions defined later, including itself.

  • (var <var-name> <value>) defines a variable. Variables cannot refer to themselves, because their values must be used after the definitions.

  • <expr> -- an expression found where a statement is expected is evaluated. If it ends a sequence of statements, its value is used as the value of the whole sequence, otherwise it is ignored.

Expressions

All remaining constructs of the language are expressions returning a value. If there is no reasonable value for an expression (e.g. an empty expression (begin)), it evaluates to false.

<expr>        = (if <expr> <expr> <expr>)
              | (cond (<expr> <stmt>...)...)
              | (when <expr> <stmt>...)
              | (unless <expr> <stmt>...)
              | (do ((<ident> <expr> <expr>)...) (<expr> <stmt>...) <stmt>...)
              | (and <expr>...)
              | (or <expr>...)
              | (begin <stmt>...)
              | (let ((<ident> <expr>)...) <stmt>...)
              | (lambda (<ident>...) <stmt>...)
              | (<expr> <expr>...)
              | (extern <ident> <expr>...)
              | <variable>
              | <integer-literal>
              | <float-literal>
              | <string-literal>
              | <character-literal>
  • <variable> evaluates to the value currently bound to a variable.

  • <integer-literal>, <float-literal> are numeric constants that evaluate to the number they denote. Digits can be separated by underscores and float literals can have an exponent.

  • <character-literal> is a character literal in single quotes that evaluates to the integer value of the character.

  • <string-literal> is a string literal in double quotes. Escape sequences from C are supported (for example \n is a newline, \" is a quote).

  • (if <condition> <then> <else>) is a conditional expression. The condition is evaluated and if it is true, the first branch is evaluated, otherwise the second branch is evaluated. All values except false are considered true.

  • (when <condition> <body-stmt>...) evaluates the statements in the body only if the condition evaluates to true.

  • (unless <condition> <body-stmt>...) is a counterpart of when that evaluates the body if the condition is false.

  • (cond (<condition> <stmt>...)...) evaluates the statements next to the first condition that evaluates to true.

  • (and <expr>...) evaluates expressions from left to right and returns the value of the first expression that evaluates to false, or true if all were true.

  • (or <expr>...) evaluates expression from left to right and returns the value of the first that evaluated to true.

  • (let ((<var> <expr>)...) <body-stmt>...) evaluates the expressions, binds the values to the variables and then evaluates the statements in the body.

  • (lambda (<arg>...) <body-stmt>...) creates an anonymous function with the given arguments. The statements in the body can access variables defined outside the function.

  • (<fun> <arg>...) denotes a function call. All arguments are evaluated from left to right, then the function is evaluated and called. It is an error if the first expression does not evaluate to a function.

  • (extern <fun-name> <arg>...) is an extern function call. Extern functions are defined in C and are mostly used in the standard library. The arguments and the existence of the function cannot be checked, so the language does not guarantee that the call is safe (in the ,,will not segfault'' sense).

  • (begin <body-stmt>...) evaluates all statements and returns the value of the last.

  • (do ((<var> <init> <next>)...) (<exit-condition> <exit-stmt>...) <body-stmt>...) is a loop expression. At the beginning, initial values of the variables are evaluated and bound. Then, if the condition (<exit-condition>) evaluates to true, the loop ends with evaluating exit statements (<exit-stmt>...). Otherwise, the body statements (<body-stmt>...) are evaluated, then the variables are bound to the values of <next> and the condition is checked again.

    For example, the following program computes and prints the first hundred Fibonacci numbers and then finishes with done:

    (program
      (import std)
      (do ((f1 0 f1)
          (f2 1 (+ f1 f2))
          (i  1 (+ i 1)))
        ((> i 100)
          (println "done"))
        (println f1)))