~maelkum/viuact-self-host

Viuact language compiler
Update lexer code
Better highlighting for strings

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~maelkum/viuact-self-host
read/write
git@git.sr.ht:~maelkum/viuact-self-host

You can also use your local clone with git send-email.

Viuact language

Viuact is a general purpose programming language with execution model following the Actor model, and with emphasis on fault-tolerant and parallel programming.

(let greeter () (print "Hello, World!"))

(let main () {
    (actor greeter)
    0
})

The above program spawns an actor (a function running in an isolated process) to print the Hello, World! message -- which, according to tradition, is the canonical first program you should write in a new language.


Short lanuguage description

Viuact is an expression-based language (most language features are expressions and yield a value). It follows strong-dynamic typing discipline and sports a Lisp-like syntax. Error conditions and failures are signalled using exceptions.

The basic code organisation primitive in Viuact is a function. Functions can be grouped in modules. Both functions and modules can be nested to arbitrary levels.

Functions are defined using let-bindings (and can be thought of as parametrised values). A function's body is a single expression. Functions take parameters - these formal parameters can be positional (required in invocation), labelled (required in invocation), and defaulted (not required in invocation; missing parameters are automatically supplied by the compiler).

Variables are immutable and defined using let-bindings. A variable always evaluates to the same value after being defined. Variables can be re-bound to a different value.

There are no loops - iteration must be implemented in terms of recursion. This is helped by the presence of tail calls.

Resource management is semi-automatic. Calls to finalisers for values that need them may be scheduled using deferred calls.

Canonical extension for files containing Viuact source code is either .viuact or .vt.


Installation

Currently only Linux is supported. Execute the following commands to get Viuact installed on your system:

$ git clone https://git.sr.ht/~maelkum/viuavm core/viuavm
$ cd core/viuavm
$ make -j
$ make PREFIX=~/.local install
$ cd ../..
$ git clone https://git.sr.ht/~maelkum/viuact-self-host
$ cd viuact-self-host
$ make
$ make PREFIX=~/.local install

Language overview

This section provides a brief overview of language features of Viuact. Most of the language constructs (i.e. all expressions) can be mixed in arbitrary ways without limits.

All executable modules must have a main function named main. The main function may have one of the three following signatures:

; No formal parameters.
(let main () 0)

; All command line arguments in a single vector.
(let main (args) ...)

; Executable name as the first formal parameters; all other command line
; arguments in the vector `args`.
(let main (exec args) ...)

Other modules do not have to define a main function.

Style

Module names must be Uppercase. In case a module name is more than one word it should be Upper_snake_case unless there are good reasons to break the rule.

Enum names must be Uppercase. Enum field names must be Uppercase. In case they are more than one word they should be Upper_snake_case.

Function names, variables, and other identifiers should be lower_snake_case.


How to define a module?

An inline module is defined inside another module directly. An inline module is considered to be nested in the module it is defined in.

(module An_inline_module (
    ; Module's contents here.
))

An out-of-line module that should be considered to be nested in another module must be declared inside that module. A module declaration states just the module's name, without specifying its contents:

(module A_nested_module)

A module contents are put inside a file. The file's name is the same as the name of the module and must follow rules for module names.

$ cat src/A_module.vt
(let foo () "Hello, World!")

How to define a variable?

A variable must be defined inside a function.

A variable is defined using let-binding. It has a name, and its value is a single expression (which may be a compound expression):

(let answer 42)
(let hello "Hello, World!")
(let x {
    (let a 4)
    (let b 2)
    (+ a b)
})

The value of a compound expression (e.g. one used above to define z) is the value of the last expression inside it.


How to define a function?

A function may be defined in a module, or nested in another function.

A function is defined using let-binding. It has a name, a list of formal parameters, and its body is a single expression. The body expression may use the names of the formal parameters as if the were normal variables defined with let-bindings.

; Take no formal parameters and always return the same value.
(let what_is_the_answer () 42)

; Just return the formal parameter given.
(let id (x) x)

; Perform a simple operation before returning.
(let max (a b) (if (> a b) a b))

; Perform a more complex operation before returning.
(let max_and_log (a b) {
    (let bigger_value (if (> a b) a b))
    (let s "The bigger value was: ")
    (let s (Std.String.concat s (Std.String.to_string bigger_value)))
    (print s)
    bigger_value
})

How to call a function?

Viuact uses a uniform prefix notation for all functions and operators. A function is called by wrapping its name and any arguments (a.k.a. actual parameters) in parenthesis:

; A simple operator call, e.g. addition.
(+ a b)

; A call to a function which takes two parameters.
(some_user_defined_fn "Hello, World!" 42)

; A call to function that takes no parameters.
(fn_with_no_parameters)

; A call to a function defined inside another module.
(Some.Module.fn 42)

Values are bound to actual parameters by copy. To avoid making a copy a pointer must be used instead.


How to handle an exception?

Exceptions are handled using try-catch expressions. A guarded expression is followed by one or more catch expressions; a catch expression is evaluated if an expression matching its tag is thrown during evaluation of the guarded expression.

The exception value can be bound to a name to be used inside a catch expression. If that is not needed the _ character should be used to inform the compiler that the exception value may be safely dropped.

(try (potentially_throwing_function a b c) (
    (catch Oh_noes x {
        (print (Std.String.concat "Failed: (Std.String.to_string x)))
        0
    })
    (catch We_are_doomed _ {
        (print "We are DOOMED!")
        0
    })))

Both guarded expression and catch expressions may be arbitrarily complex.


How to create an enum?

An enum must be defined inside a module.

An enum has a name and at least one field. There are two kinds of enums:

  • simple enums; whose fields a just symbolic names
  • tag enums; enums with at least one field that carries a value

Both enums are defined the same way. The only difference is that some of the fields of a tag enum specify a placeholder for a value to hold.

(enum A_simple_enum (
    Foo
    Bar
    Baz
))

(enum Maybe (
    (Some _)
    None
))

Values of simple enums are simple to define:

(let x A_simple_enum.Foo)

Values of tag enums must be constructed, i.e. the function call syntax must be used to create them:

(let x (Maybe.Some 42))
(let y (Maybe.None))

How to match on an enum?

Viuact has match expressions for this. For value-carrying fields of tag enums the value can be bound to a name to be used within with expression of that field.

Use the _ character to create a catch-call with expression.

(match x (
    (with A_simple_enum.Foo "Foo")
    (with A_simple_enum.Bar "Bar")
    (with _ "Bar")))

(match y (
    (with Maybe.Some y' y')
    (with Maybe.None "default value")
))

How to comment some code?

Viuact supports two styles of comments. A single-line comment:

; It begins with the ';' character and ends at the newline.

and a block comment:

(*
 * Begins with '(*' and ends with '*)'. May be nested.
 *)

How to deal with indirect access (e.g. pointer, reference) to values?

To create a pointer use the Str.Pointer.take function:

(let x 42)
(let p (Std.Pointer.take x))

To dereference a pointer and get a value behind it use the ^ operator:

(let x' (^ p))

Pointers are not automatically dereferenced.


License

Copyright (c) 2020 Marek Marecki

Viuact language compiler and its supporting software and libraries are published under the GNU GPL v3 license.