aboutsummaryrefslogtreecommitdiff

A tour of Micro

Micro is a small statically typed toy programming language.

Syntax

Micro statements are:

  • Variable declarations and expressions, delimited by semicolons.
  • Function and structure definitions.

Comments

Only line comments, starting with // and ending at the end of the line.

// This is a comment

Reserved words

These are the reserved words in micro:

var const def func bool true false return if else for in break continue

See also the built-in functions.

Identifiers

Identifiers can be any string starting with a letter (upper or lower case) or underscore (_), followed by the same type of character or a numeric value.

An identifier can't be a reserved word.

var example number;
var _example number;
var example2 number;

Blocks and scope

Micro has series of statements in blocks delimited with { and }, and a block defines a lexical scope.

A variable declaration can shadow another from a parent scope.

var a number = 1;
println(a);

{
    // block, new scope
    var a string = "one";
    println(a);
}

println(a);

// output:
// 1
// one
// 1

Operators

Micro uses the same operators and almost the same operator precedence as C, from more to less priority:

? ! - ~
/ * %
&
- +
<< >> | ^
> >= < <=
|| &&
!= ==
=

Arithmetic operators only apply to integers.

Comparison operators only apply to integers, with the exception of == and != that also apply to other types (if both operands are of the same type).

Logic operators apply to booleans and to the following:

  • An empty string is false, true otherwise.
  • A numeric value of 0 is false, true otherwise.
  • A function reference is always true.

The result of these operators is a boolean and they are lazy operators (e.g. in a && b, b won't be evaluated if a is false).

Values

Type Keyword Sample
Boolean bool true; false;
Number number (int64) 123;
String string "example";

There is also a "none" type referred when a function doesn't return a value. It is not a available to use but it will appear in error reporting.

For example, the interpreter running this code:

def fn() { }

var a number = fn();
// error: type mismatch expected number found none

See functions for more information about the "func" type.

Numbers support the following formats:

  • 0 decimal
  • 0x00 hexadecimal
  • 0b00 binary
  • 'x' for "ASCII character"

Variables

Variables are declared with var.

If they are not initialized, a default value is assigned; with the exception of functions, that can't be used until they have a value (or there will be a runtime error).

var a number = 123;
var b number; // initialises default to 0
var c string; // empty string
var d bool; // false

// e was not declared: error
e = 123;

var fn func ();
fn();
// error: value is not callable

Constants

Constants are immutable values.

Constants can be literals or expressions that get evaluated at parsing time.

const K number = 10;

var a number = K;

K = 0;
// error: value cannot be assigned to constant K

Conditionals and flow control

If-else

var a number;

if a > 0 {
    println("it is true");
} else {
    println("it is false");
}

// output:
// it is false

For and for-in

The block will loop while the expression evaluates to true.

var i number = 4;

for i > 0 {
    println(i);
    i = i - 1;
}

// output:
// 4
// 3
// 2
// 1

Loop flow can be altered with:

  • continue to jump to next iteration.
  • break to exit the loop.

The expression is optional (infinite loop).

var i number = 4;

for {
    println(i);
    i = i - 1;
    if i == 0 {
        break;
    }
}

for ... in can be used to Iterate over arrays.

var arr [3]string = ["one", "two", "three"];
for i in arr {
    // i is of type string
    println(i);
}
// output:
// one
// two
// three

Functions

Functions can be defined using def and they are assigned a "func" type describing parameters and return type.

Recursion is supported and recursive tail calls will be optimized.

// recursive factorial
// (the tail call will be optimized)
def fact(n number, acc number) number {
  if n == 1 {
      return acc;
  } else {
      return fact(n - 1, acc * n);
  }
}
// type of fact is: func (number, number) number

println(fact(20, 1));
// output:
// 2432902008176640000

def fn(a number, b number) {
    // implicit return with no value
    // it is an error if a value is expected
}

Functions are higher-order.

// b is a function that takes one number and returns a number
def fn(a number, b func (number) number) number {
    return b(a);
}

Closures are supported.

def makeCounter() func (number) number {
    // defaults to 0
    var c number;
    def count() number {
        c = c + 1;
        return c;
    }
    return count;
}

var c func = makeCounter();

println(c()); // 1
println(c()); // 2
println(c()); // 3

Arrays and structures are always passed as reference.

var arr [5]number;

def fn(a [5]number) {
    a[0] = 10;
}
fn(arr);

arr[0]; // 10

Local arrays and "struct" can't be returned from a function.

def fn() [5]number {
    var local [5]number;
    return local;
}
// error: returning a local value of type array [5]number

But references to variables in scope are allowed.

var g [5]number;
def fn() [5]number {
    // local is a reference to a non-local variable
    var local [5]number = g;
    return local;
}

Returning error

For cases when the return value can't be used to inform of an error, a function can tag a return value as "error" using !?. That tag can be tested with the ? unary operator.

Even if a return value is tagged, it must be a valid return value for the function definition.

def safeDiv(a number, b number) number {
    // a / 0 would result on a runtime error!
    if b == 0 {
        return !? 0;
    }
    return a / b;
}

var result number;

// checks if the error tag is set while keeping the return value
if ? (result = safeDiv(1, 0)) {
    println("safeDiv returned error");
}

// it can also be used with functions returning no values
def fn() {
    return !?;
}

if ? fn() {
    println("fn always fails!");
}

? (test error) operator resets the error flag before evaluating the right expression.

// our previous "always fails" function
fn();
// error is not set
if ? true {
    println("Not run");
}

Arrays

Arrays are zero based and are supported for all types.

Array size is a numeric literal or a constant expression (must be known at compilation type).

Arrays can be initialised to literals with:

  • [ and ] providing a list of values.
  • A literal string using double quotes for arrays of unit8; the array size must be at least the length of the string plus 1 (a zero terminating the string).
// array of 5 numbers; the size is only needed
// when no initializing values are provided
var arr [5]number;

// initialized to specific values
var arr2 [5]bool = [true, false, true, false, true];

arr[0]; // 0
arr2[0]; // true

// iterate over members
for i in arr {
    println(i);
}

len(arr); // 5

var i number;
// arrays indexes are zero based
for i < len(arr) {
    println(arr[i]);
    i = i + 1;
}

Arrays can be constants.

const arr [10]number;

arr[0] = 10;
// error: can't modify a constant variable

// but can change references
const ref [10]number = arr;
const arr2 [10]number;

ref = arr2;

Arrays are passed by reference to functions.

Structures

Structures allow grouping data and functions.

In a structure definition only variable declarations, and definitions of functions and other structures are allowed.

// declare struct A
def A {
    var n number;

    def fn() number {
        // n is in scope
        n = n + 1;
        return n;
    }
}

// memory is allocated for obj
var obj A;

// obj2 is a reference to obj
var obj2 A = obj;

prinln(obj2.n);
// output: 0

println(obj.fn());
// output: 1

prinln(obj2.n);
// output: 1

def B { var n bool; }

var b B = obj;
// error: type mismatch: expected struct B found struct A

// recursive structures are not supported (micro doesn't have pointers)
def B {
    var next B;
    // error: recursive definition of struct B
}

def C {
    println("error");
    // error: expected definition in a struct block
}

Interpreter built-in functions

These are available on the interpreter.

Function Description Example
len returns the size in elements of a variable of type array len(arr);
panic ends execution with a message and a runtime error panic("message");
println writes to standard output a variable list of expressions, followed by an end of line; returns number of bytes written println("value: ", a);

To do

See TODO for features that are planned but not yet implemented.