Why Download Tutorial Reference

1Introduction

1.1Setup

1.1.1Windows

Unzip the Minimacy distribution to get the minimacy directory. On Windows, you put it where you want.

From now we note [minimacy] this minimacy directory.

Locate the file “[minimacy]/topLevel.mcy” which is at the root of this minimacy directory. Double-click the file, Windows should ask you “How do you want to open this file?” Then select [minimacy]/bin/minimacy.exe

You may as well start Minimacy from the command line:

Now you can run:

[minimacy]/bin/minimacy.exe -h 

Then the terminal displays informations about Minimacy, including the version number.

If you want to recompile the virtual machine:

1.1.2Linux

If you need a graphic UI, you’ll have to install X11 and OpenGL.

Unzip the Minimacy distribution to get the minimacy directory. It is recommended to put it in your home directory ~/minimacy

It is also recommended to make it easy to start minimacy from anywhere in the Terminal. For example you can type this command first in the Terminal:

export PATH=$PATH:$HOME/minimacy/bin 

Now you can run:

minimacy -h 

Then the terminal displays informations about Minimacy, including the version number.

This version is command line only. If you need the UI with openGL, start the following:

minimacyX11GL -h 

If you want to recompile the virtual machine:

cd [minimacy]/unix 
make all 

The resulting files are in [minimacy]/bin/

1.1.3MacOS

On MacOS, there are two versions of the Minimacy machine:

1.1.3.1Command line

Unzip the Minimacy distribution to get the minimacy directory. It is recommended to put it in your home directory /Users/[login]/minimacy

It is also recommended to make it easy to start minimacy from anywhere in the Terminal. For example you can type this command first in the Terminal:

export PATH=$PATH:$HOME/minimacy/bin 

Now you can run:

minimacyMac -h 

Then the terminal displays informations about Minimacy, including the version number.

This version is command line only. If you need the UI with X11 and openGL, you’ll have first to install XQuartz. Then start the following:

minimacyMacX11GL -h 

If you want to recompile the virtual machine:

cd [minimacy]/macos/cmdline 
make all 

The resulting files are in [minimacy]/bin/

1.1.3.2MacOS App

More to come:

1.2Tools

If you are using Visual Code Studio or Notepad++, it is recommended to install the corresponding minimacy extension to highlight the minimacy source code with nice colors.

1.3Hello world

Let’s start with the "hello world" program.

Create a new file "hello.mcy", and enter the following line:

fun run = echoLn "Hello world!";; 

What we can see there:

funwe are declaring a function (because functions are fun;-)
runthe name of the function
=the result of the function follows the sign “equals”
echoLnthis system function takes one argument, displays it with a newline
"Hello world!"the argument for the function echoLn
;;this marks the end of the function declaration

Now you can start your program:

Minimacy - Sylvain Huet - 2020 - 0.0.1/windows 
---- 
> system: ~/minimacy/ 
> compiling 'hello' 
>>>>>>>>> package: hello 
> fun run : fun -> Str 
> compiled in 0 ms 
 
Hello world! 

> system: ~/minimacyThe system directory. It is the directory containing the hello.mcy file
> compiling 'hello'start the compilation of hello.mcy
> fun run : fun -> Strthe inferred type of the function “run”
> compiled in 0 msalways fast! but this one was simple
Hello world!the result of the evaluation of the function “run”. This is the side-effect of the function “echoLn”

Not much to learn from this example, except that:

We wrote the function “run” in a single line but this was not mandatory. Minimacy does not take care of the spaces, tabulation or newlines. We could as well write it like this:

fun run = 
echoLn "Hello world!" 
;; 

2The basics

2.1Functions

Minimacy loves functions. They are the most important declaration. A function takes any number of arguments and returns exactly one value.

Function names must start with a lowercase character.

The syntax is designed to keep it simple, with the minimum of symbols, even less than the mathematical usual syntax. Consider the simple function “f(x) = x+1”. In Minimacy you simply write:

fun f x = x+1 ;; 

Let’s call this function from the function “run”:

fun run = dump f 2;; 

We see there the composition of functions: the machine will first call (f 2) and then call the function “dump” with the result of (f 2). The function “dump” displays the value of its argument in a more technical way than echoLn. For an integer it displays the decimal and the hexadecimal values:

-> Int: 3 (0x3) 

Consider now the function: g(x,y)=x+y. Create a file "sample.mcy" with:

fun g x y = x+y ;; 
fun run = dump g 2 3;; 

The function “g” needs 2 arguments x and y and returns their sum: g 2 3 will return 2+3=5.

A major difference with other programing languages is the use of brackets.

If you want to increase readability, you can use the following:

dump (g 2 3) 
dump g (2) (3) 

In Minimacy brackets surround a single value, therefore you cannot write g(2 3), as it would mean the function “g” gets only one value. There is no comma between the arguments: you can also forget this notation: g(2,3)

It is important to understand how the compiler works when brackets are missing:

f g x y 
f (g x y) 
f (g x) y 
>Compiler: too many argument(s) for function 'g' (should be 1) 

As the compiler knows the number of arguments of ‘g’, it could compile “f g x y” correctly. However it would be very hard for another human reader to follow this code, as he/she should first know all the functions and their number of arguments.

Remember that the aim of Minimacy is to achieve full transparency. This is how you may easily know how the compiler works.

fun f x = x+1 ;; 
fun run = dump #f;; 

Adding a hash sign before a function name gets a kind of pointer to this function. The result of this program is:

-> fun f 
   bytecode: 
| args=1 locals=1 
|    0   rloc.b    0 
|    2   int.b     1 
|    4   add 
|    5   ret 

We’ll see later how to read the bytecode. We can already say that it is based on a stack. This bytecode pushes the argument, then pushes the integer value 1, then pulls two values, computes the sum and pushes the result, then returns.

2.2Local variables

When your calculation gets more complex or when there is redundancy, you may define variables to store intermediate results. Let’s consider the following example:

fun squareSum x y = 
let x*x -> square_x in 
let y*y -> square_y in 
square_x + square_y;; 

The line “let x*x -> square_x in ...” computes x*x and stores the result in a fresh variable square_x, that you can use only in the expression after “in”

You may be surprised by the order of the elements in the “let” declaration, as many languages would say “let square_x=x*x”. The rationale behind this is that it sticks better with the way the computer works:

Another reason is to better distinguish the “let” and the “set”. We’ll see later that it is possible to change the value of the variable, and “let x=x+1” would be syntactically too close to “set x=x+1” but with a very different meaning.

2.3Native types

Minimacy defines a few types natively, and we’ll see later how the user can defines its own types.

TypeMinimacy syntaxDescription
Booltrue, falseThe boolean type has two values: true and false
Int1234, 0xffc000062 bits signed integer
Str"hello"The string is a immutable array of bytes
Float1.23462 bits floating number
BigNum-A big signed integer (up to 16384 bits)
Bytes-A mutable array of bytes. Only the content is mutable, not the size
Buffer-A buffer of bytes, with mutable content and mutable size

You may wonder why integers and floats are only 62 bits, as your computer is 64 bits. It is because the VM uses 2 bits for memory management and debugging.

2.4Usual operators

OperatorTypeDescription
+ - * / ** %fun Int Int -> Intarithmetic on integers (**: power)
& | ^ << >>fun Int Int -> Intoperations on bits
+. -. *. /. **.fun Float Float -> Floatoperations on floats (**.: power)
&& ||fun Bool Bool -> Booloperations on booleans
!fun Bool -> Booloperation on booleans
< > <= >=fun Int Int -> Boolcomparison on integers
<. >. <=. >=.fun Float Float -> Boolcomparison on floats
== != <>fun a1 a1 -> Boolcomparison on any type (<> and != are the same)

2.5Lists and recursivity

Lists are a very powerful structure, they are an immutable ordered set of values.

Let be myList a list of integers. You can:

WhatNotation
Define an empty listnil
Create a new list from an existing one by adding an element at the beginning of the existing one1:myList
Get the first value of a list (aka the “head” or “hd”)hd myList
Get the list without the first element (aka the “tail” or “tl”)tl myList

Note that there is no way to change a list.

Consider the following example:

fun myListLength inputList = 
    if inputList ==nil then 0 
    else 1+myListLength  tl inputList ;; 
fun run = echoLn myListLength 3:2:nil;; 

Let’s start with the function “run”: it first creates a list of integers: 3:2:nil

In order to create this list, it adds 3 in front of the list 2:nil

In order to create the list 2:nil, it adds 2 in front of the empty list nil

Now the function “myListLength”. Note that this function calls itself. This is what we name recursivity. The rationale behind it is:

To implement it we use the “if ... then ... else ...” construction. Comparing to most other languages, this construction in Minimacy is also a function with 3 arguments: the condition, the returned value when the condition is true, the returned value when the condition is false.

The operator “==” is a function with two arguments and returns a boolean.

Now we can understand the function “myListLength”:

fun myListLength inputList =the function has one argument inputList
if inputList ==nil then 0when inputList is empty, returns 0
else 1+myListLength tl inputList ;;else returns 1+ the length of the tail of inputList

The evaluation will end because each recursion calls the function with a smaller list, and that the empty list ends the recursion by returning zero.

Run the program, and get:

>>>>>>>>> package: sample 
> fun myListLength : fun list a1 -> Int 
> fun run          : fun -> Int 
> compiled in 0 ms 
 
3 

The inferred type of myListLength is interesting:

fun list a1 -> Int 

“a1” stands for “any, 1st”. This means that this function is applicable to a list of any type of value. And this is exactly what we do in this function: we don’t perform any computation on the values of the list, we only consider their number.

This is what we call “polymorphism” in the functional paradigm.

A different function would be the sum of the elements of a list of integers:

fun myListSum inputList = 
    if inputList ==nil then 0 
    else (hd inputList)+myListSum tl inputList;; 

The inferred type of myListSum is:

> fun myListSum    : fun list Int -> Int 

Because we perform a sum (+) on the first element of the list (hd inputList) the compiler understands that this function applies only on lists of integers.

Consider another classical example:

fun myConc p q = 
    if p ==nil then q 
    else (hd p): myConc (tl p) q ;; 
fun run = echoLn myConc 1:2:3:nil 4:5:nil;; 

The function “myConc” returns the concatenation of the elements from two lists p and q, in this order. In our example it returns the list 1:2:3:4:5:nil.

The rationale behind it is:

The evaluation will end because each recursion calls the function with a smaller list p, and that when p is the empty list it ends the recursion by returning q.

The compilation returns the following type for myConc:

> fun myConc       : fun list a1 list a1 -> list a1 

This is a function which takes two lists of any type and returns a list of the same type. For a given evaluation the three lists contain any type of values, but the same for all of them! A function with a type “fun list a1 list a2 -> list a3” would be able to work on two lists of different types of values.

Recursion is not only for lists. Consider as an exercise this elegant function “gcd” which returns the greater common divisor of two integers:

fun gcd a b= 
if b>a then gcd b a 
else if b<0 then gcd a (-b) 
else if b>0 then gcd a-b b 
else a;; 

Recursion is the natural way to implement loops in a ML-style programming language.

2.6Static type checking

Observe that we don’t have to specify the type of the arguments or the type of the returned values. Minimacy includes a strong static type checking with type inference. It means that Minimacy infers all the types as it compiles the program, and doesn’t allow any mismatch.

2.6.1Type “any”

The notation is a1, a2, a3, ... “a” stands for “any”.

We already saw polymorphism in the section “Lists and recursivity”:

> fun myListLength : fun list a1 -> Int 

This is also the case when your function does not use an argument:

fun f a= true;; 
 
> fun f            : fun a1 -> Bool 

We already saw other examples: the functions echoLn and dump. Both return their argument. You can insert them anywhere, it won’t have any effect on your computation, it will only print stuff in your terminal.

> fun dump         : fun a1 -> a1 
> fun echoLn       : fun a1 -> a1 

“a1” is the type of both argument and result. The function applies to an argument of any type and returns a value with the same type.

2.6.2nil

We already saw “nil” as the empty list. In Minimacy, nil has a wider meaning, as nil can be a value of any type, even integers and strings.

When omitting the “else” statement in a “if then else” construction, the compiler reacts as if there was an “else nil” at the end.

2.6.3Weak types

Weak types are noted w1, w2, w3, ... “w” stands for “weak”. They indicates that the declaration has not enough information to infer the type. However they are not “any” type, but their type is unknown at that time.

In Minimacy we can define a global variable this way:

var Foo=123;; 
> var Foo          : Int 

Note that global variables must start with an uppercase character. It is the same for constants.

If we don’t specify the value, the compiler initializes the global variable with “nil”. We get a weak type, and the compiler shows a warning.

var Foo;; 
> var Foo          : w1 
> compiled in 0 ms 
 
!! weak type sample.Foo: w1 

Now if another part of the code gives the missing information, the weak type is replaced by the actual type.

var Foo;; 
var Bar=Foo+1;; 
fun f x= set Foo=x+1;; 
> var Foo          : Int 
> var Bar          : Int 
> fun f            : fun Int -> Int 

The declaration of the variable Bar lets the compiler infer that Foo is an integer. The same for the function “f”.

2.6.4Type errors

When the type inference fails, the compiler displays a message.

Consider:

var Foo=123 ;; 
var Bar= Foo+"hello" ;; 

Launch this program, and get the following error message:

>>>>>>>>> package: sample 
> var Foo       : Int 
> proto var Bar : w1 
 
Compiler: 'Str' does not match with 'Int' 
Compiler: error compiling var 'Bar' 
 
>pkg sample : 
>line 2 : 
>var Bar= Foo+"hello" ;; 
>                     ^ 

How to read this?

> var Foo       : Int 
> proto var Bar : w1 

What it means:

2.7Lambda functions

In Minimacy you can manipulate functions like any other data.

Lambda functions are functions with no name. Consider this example:

fun mkAddN n= (lambda x = x+n);; 
 
fun run= 
let mkAddN 10 -> add10 in 
let call add10 5 -> result in 
dump result;; 

Run this program:

>>>>>>>>> package: sample 
> fun mkAddN : fun Int -> fun Int -> Int 
> fun run    : fun -> Int 
> compiled in 0 ms 

-> Int: 15 (0xF) 

The function “mkAddN” takes an argument “n” and returns a function that takes an argument x and return x+n.

Then the function “run” calls mkAddN 10 and therefore gets a function which adds 10 to is argument.

This function is stored as a local variable “add10”.

Then this function is called (see the command “call”) with 5 as an argument, and the result is stored as a local variable “result”.

Eventually, the programs displays its value.

Pay attention to the type of the function “mkAddN”: fun Int -> fun Int -> Int

Let’s add brackets to make it more readable: fun Int -> (fun Int -> Int)

Now we understand that this function takes an integer as an argument and returns function which takes an integer and returns an integer.

It is also possible to get a lambda function from a declared function. Just add a hash sign in front of the function name:

let #dump -> myDump in 
call myDump 123 

3Side effects

What is a side effect? It is when a function does more than just compute and return a value.

We already saw it in the beginning of this document:

Another side effect is changing the value of a variable. In Minimacy you can use a “set” like this:

set x = y+2 

“set” is also a function of type : fun a1 a1 -> a1

In our example, x and y+2 must have the same type, and the value y+2 is returned by “set”.

What interest on a computation point of view? None, the returned value y+2 was already computed. But on the side, it has been stored in x. This is a side effect.

When using side effects, we may need to chain several expressions without using their results. In Minimacy, we can achieve this with the “;”

fun run= 
echo "Hello"; 
echoLn " World";; 

In this example the single “;” means that we forget the result of “echo "Hello"” and proceed with the evaluation of the next expression.

Side effects are not bad by themselves, they are even essential, but it is important to know when we are using them.

Why are side effects dangerous?

Why are side effects useful?

In Minimacy it is easy to detect side-effects:

4User defined types

4.1Tuples

Tuples are a set of values of mixed types, you don’t need to declare it. Just use squared brackets.

let [1 2 "foobar"] -> myTuple in 
let myTuple -> [ a b c ] in ... 

The “let” allows you to extract the inner values of the tuple.

Tuples are not mutable: you can’t change their inner values.

4.2Structures

Structures are like tuples but their values are named (we call them “fields”) and it is possible to change the values of the fields.

struct Label=[ xL yL nameL ];; 
 
const Origin= [xL=0 yL=0 nameL="origin"];; 
 
fun vector p q = 
let q.xL - p.xL -> dx in 
let q.yL - p.yL -> dy in 
[dx dy];; 

We define a structure with the list of its fields. These fields are considered as functions and therefore must have distinct names. So in a single mcy file, you cannot define two structures with the same field name ‘x’.

We recommend to use the initial of the structure name as a final capital letter to the field:

We instantiate a structure like this:

[xL=1 ] 

It is not necessary to give a value to all fields, the missing fields will be “nil”.

You may also instantiate a structure without any value:

[Label] 

The function “vector” shows how to access the fields.

4.3Sums

In some cases, it can be useful to use a variable with several different types: in variable x, you sometimes want an integer, other times a character string, and other times a tuple. Typing forbids this type of operation. To make it possible, you must use the equivalent of the union in C: these are sums.

In the following example we implement an arithmetic tree. We define a sum Node, with 4 constructors intN, addN, mulN and zeroN :

Constructor names must start with a lowercase character.

sum Node = intN _, addN _ _, mulN _ _, zeroN;; 
 
fun nodeEval node= 
match node with 
intN x -> x, 
addN x y -> (nodeEval x)+(nodeEval y), 
mulN x y -> (nodeEval x)*(nodeEval y), 
zeroN -> 0, 
_ -> 0;; 
 
fun run = 
dump nodeEval intN 123; 
dump nodeEval mulN (intN 3) (addN (intN 1) (intN 2));; 

The function “nodeEval” illustrates how to deal with a sum. The construction “match ... with ...” makes it possible to extract the constructors and the associated values. The last line handles the default case with “_ -> ...”. Minimacy uses a comma to separate the different cases. The values associated to each case must have all the same type, because they are the result of the function “nodeEval”, and a function returns only one type.

Then, in the function “run” we see how to use the constructors to create a value of type Node. We use the constructors as regular functions:

Note that there are extra brackets on the last line for readability.

Let’s run this:

>>>>>>>>> package: struct 
> sum Node     : zeroN, mulN, addN, intN 
> constr intN  : fun Int -> Node 
> constr addN  : fun Node Node -> Node 
> constr mulN  : fun Node Node -> Node 
> constr zeroN : Node 
> fun nodeEval : fun Node -> Int 
> fun run      : fun -> Int 
> compiled in 0 ms 
 
-> Int: 123 (0x7B) 
-> Int: 6 (0x6) 

Note that the types of the constructors are functions, except for zeroN which is a constant value.

4.4Enum

Enum is not exactly a type, it is a way to enumerate integer constants:

enum A B C;; 

Then A=0, B=1, C=2

5Other types

5.1Arrays

Arrays are a set of elements of the same type which can be accessible with their index. It is not possible to change the size of an array after its creation, but it is possible to set its values.

An array can be defined statically or dynamically:

var MyStaticArray={ 1 2 3 };; 
var MyDynamicArray= 
let arrayCreate 3 nil -> a in 
( 
set a.0=1; 
set a.1=2; 
set a.2=3; 
a 
);; 

Both MyStaticArray and MyDynamicArray have the same content.

Remember that the “let” defines local variables (here “a”) which you can use only in the expression which follows the “in”. That’s why we add brackets to chain three “set” functions. The value of this bracketed expression is the value of its final expression, after the last “set” (here “a”).

Most of the time, using arrays comes with side-effects. That’s why most ML-style programming languages prefer lists: lists have no side-effects.

5.2Hashmaps

Hashmaps are used to associate one element to another. For example you can associate a integer value to a string key. This would have the type “hashmap Str -> Int

The internal implementation of an hashmap can be seen as an array of lists.

From the key the VM computes an index and the couple (key,value) is added to the list which is at the position “index” in the array.

To make it simple, the length of this internal array is always a power of 2. In order to create a hashmap you need to give this length as the value of the exponent:

Therefore the average length of the internal lists will be 1/256 of the number of values, and then the search for the value associated to a key will be around 256 times faster.

The 3 basic functions for hashmap are:

let hashmapCreate 4 -> h in (hashmapSet h true 123; hashmapSet h false 456; hashmapGet h 123)

> let hashmapCreate 4 -> h in (hashmapSet h true 123; hashmapSet h false 456; hashmapGet h true) 
-> Int: 123 (0x7B) 

The result of this whole expression is the result of the last part “hashmapGet h true”. It is 123 because 123 is associated to true.

5.3Fifo

Fifo means “first in first out”. It is like a pipe: you push elements from one end and you pull them from the other end.

All these elements must have the same type. A fifo of integers will have the type “fifo Int”.

The 3 basic functions for fifos are:

> let fifoCreate -> fifo in (fifoIn fifo 1; fifoIn fifo 2; fifoOut fifo) 
-> Int: 1 (0x1) 

The result of this whole expression is the result of the last part “fifoOut fifo”. It is 1 because 1 was the first element to be put in.

6Execution controls

6.1Match

We already saw how the “match” is used to extract the constructors from a sum:

fun nodeEval node= 
match node with 
intN x -> x, 
addN x y -> (nodeEval x)+(nodeEval y), 
mulN x y -> (nodeEval x)+(nodeEval y), 
zeroN -> 0, 
_ -> 0;; 

It is possible to use “match” for any kind of data.

For example with our previous enum:

enum A B C;; 
 
fun echoMyEnum val= 
echoLn match val with 
A -> "A", 
B -> "B", 
C -> "C", 
_ -> "?";; 

We’ll see later another use of “match” with the derived structures.

6.2For

“for” is a way to loop in the evaluation of an expression as long as a condition is fulfilled.

There are different kinds of “for” depending of the kind of iterator.

The value returned by “for” is the value of the expression in the last loop, nil if no loop occurred.

Most of the time we don’t care about this returned value: “for” is usually a construction with side-effect.

6.2.1integer iterator

for i=0;i<10 do echoLn i 

A local variable “i” is created with the value 0.

Then as long as i<10 the expression is evaluated and then the iterator is incremented.

It is possible to define another increment:

for i=0;i<10;i+2 do echoLn i 

At the end of each loop we will replace the value of “i” by “i+2”.

6.2.2list iterator

for val in 3:2:1:nil do echoLn i 

The local variable val will be 3 in the first loop, 2 in the second, 1 in the last.

6.2.3array iterator

for val of {3 2 1} do echoLn i 

The local variable val will be 3 in the first loop, 2 in the second, 1 in the last.

6.2.4break

It is possible to leave the loop with the “break”. It must be followed by the value that will be returned by the “for”.

fun contains myList searchedValue = 
for val in myList do 
if val == searchedValue then break true 
else false 
;; 

This function iterates through the list and breaks the loop with value true.

When the iteration is complete, it returns false.

6.3While

“while” is a simple loop construction: an expression is evaluated as long as a condition is fulfilled.

let 10 -> val in while val>0 do set val=val-1 

The value returned by “while” is the value of the expression in the last loop, nil if no loop occurred.

Most of the time we don’t care about this returned value: “while” is usually a construction with side-effect.

6.4Exceptions

Exceptions are a way to abort the normal processing of your functions. You may throw an exception, and this will resume the execution from a previous “try ... catch”. When there is no “try ... catch” it will terminate the thread.

Minimacy defines the type “Exception”. It is a sum.

The bios defines the constructor “msgException” with a string argument. You may define your own exception constructors by extending the type Exception. For example:

extend Exception with 
myFirstException _, 
myConstantException;; 

Then your program may throw exceptions, for example:

throw msgException "there is an error" 

Your “throw” command should be called inside the computation of a try ... catch.

fun myDiv x y= 
try 
if y==0 then throw msgException "division by zero" 
else x/y 
catch 
msgException msg -> ( echoLn msg; nil);; 
 
fun run= 
dump myDiv 123 5; 
dump myDiv 123 0;; 

Let’s run this program:

>>>>>>>>> package: sample 
> fun myDiv          : fun Int Int -> Int 
> fun run            : fun -> Int 
> compiled in 0 ms 
 
-> Int: 24 (0x18) 
division by zero 
-> nil 
 

It is not necessary to have the throw inside the “try catch” in the same function. It may be in a function called from inside the “try catch”, like this:

fun _myDiv x y= 
if y==0 then throw msgException "division by zero" 
else x/y;; 
 
fun myDiv x y= 
try 
_myDiv x y 
catch 
msgException msg -> ( echoLn msg; nil);; 

If you need to handle different types of exceptions, you may use the same construction as for the “match”, with the same default case:

catch 
msgException msg -> ( echoLn msg; nil), 
myConstantException -> ( echoLn "constantException"; nil), 
_ -> ( echoLn "default"; nil),;; 

When an exception is not catch, the VM goes up the callstack until a try ... catch is found.

6.5Return

It is possible to leave a function with the “return”. It must be followed by the value that will be returned by the function.

fun contains myList searchedValue = 
for val in myList do if val == searchedValue then return true; 
false 
;; 

This function iterates through the list and stops the loop and returns true when it find an element with the searched value.

Else, after the iteration is complete, it returns false.

7Packages and visibility

A Minimacy file is considered as a “package”, and its name is the name of the file without the “.mcy” extension.

When your program needs the functions from another package, you can import it. For example we need the package “core.util.base64”. We add this in our program:

import core.util.base64;; 

You can import only packages whose name doesn’t contain “._”

If you import two packages “usr.a” and “usr.b” with a common function (or declaration) “foobar” you need a way to say which function you want to call. Then you declare an alias:

import usr.a as A;; 
import usr.b as B;; 
 
fun run= 
A.foobar 123;// call foobar from usr.a 
B.foobar "hello";;// call foobar from usr.b 

Not all the declarations can be imported. Instead of using “public”, “private” or “export”, Minimacy uses a simple rule. When a declaration name starts with an underscore it is private, and therefore not accessible from other packages.

Consider this:

fun foo x = x+1;; 
fun __bar x = x+2;; 

The compiling gives this:

> fun foo            : fun Int -> Int 
> fun __bar           * fun Int -> Int 

The star ‘*’ in the second line is a reminder for the private nature of the function “_bar”.

When you feel your package has too many lines, you may split it into smaller files and use “include”.

Open “bios/bios.mcy” in the ROM. Its content is mostly:

include bios._head;; 
include bios._util;; 
include bios._lock;; 
include bios._worker;; 
include bios._keyboard;; 
include bios._timer;; 
include bios._net;; 
include bios._file;; 
include bios._scheduler;; 
include bios._thread;; 
include bios._date;; 
include bios._pkg;; 
include bios._admin;; 
include bios._ui;; 

You can only include derived file names: the name of the package followed by “._” and what you need after. For example the package “bios” may only include “bios._*.mcy” files.

It might sound strange, but it is necessary to distinct packages to import and packages to include.

“include” should not be used as “import”, but only when you need to split a single package into small files, for readability purpose.

8UI

When your Minimacy VM is running on a computer with a display, you can use the UI functions to create interactive applications.

UI is usually full of side-effects, so don’t be surprise to see many “;” in the examples.

8.12d UI

The UI is a window with a bitmap buffer. The usual way:

fun run= 
uiStart 100 10 600 400 UI_NORMAL "simple UI"; 
bitmapErase uiBuffer 0x400000; 
uiUpdate;; 

8.2Interactivity

Add interactivity: change the color on a click

fun run= 
uiStart 100 10 600 400 UI_NORMAL "simple UI"; 
bitmapErase uiBuffer 0x400000; 
uiUpdate; 
uiOnClick (lambda x y buttons= 
bitmapErase uiBuffer 0x008000; 
uiUpdate 
);; 

See the documentation about other interactivity capabilities (key press, resize, ...)

8.3openGL UI

Minimacy VM supports OpenGL, reduced to OpenGL ES 2.0 features in order to be compatible with mobile phones.

It is quite difficult to master because you need to program small routines in pseudo-C language, named “shaders”.

The following example demonstrates an example without shader, which simply varies the background color:

fun renderLoop val= 
glClearColor (sqr cos val) 0.7 0. 1.; 
glClear GL_COLOR_BUFFER_BIT; 
glSwapBuffers; 
onTimeout 100 (lambda= 
renderLoop val+. 0.1 
);; 
 
fun run= 
uiStart 100 10 600 400 UI_GL "gl UI"; 
renderLoop 0.;; 

9The ROM and the System

9.1Volumes

The Minimacy VM runs as it uses two Volumes: the ROM and the System.

When the VM needs a file, it first looks for it in the System, then in the ROM.

The ROM is in [minimacy]/rom/, where [minimacy] is the minimacy directory.

The ROM contains the BIOS and the official Minimacy set of libraries:

At boot time, the Minimacy VM knows only the ROM. First it compiles and runs [ROM]/bios/bios.mcy

If you provide a *.mcy file to the VM (in the command line or by double-clicking a *.mcy file), this file will be considered as the boot file.

If no *.mcy is provided, the boot file is “[minimacy]/system/boot.mcy”

From the boot file path, it determines the system directory (usually the directory which contains this boot file). From now on, it has its two volumes ROM and System.

Then it compiles and runs the boot file.

You should not change the ROM, but the System is yours. Remember that the System is in “[minimacy]/system” only when you start the VM without mcy file in the command line.

Suppose you create a file “/any_path/tool.mcy” and double-click on it, then the System directory will be “/any_path/”. This is almost correct ;-) When the VM is looking for a file “a.b.c.mcy”, it looks for it in several places:

If the file is in /any_path/a/b/a.b.c.mcy, the System directory will be “/any_path/” instead of “/any_path/a/b/”.

9.2Bios

The Bios is a package which contains a mix of native functions and Minimacy functions. The Minimacy functions have access to reserved native functions: those starting with an underscore as they both belong to the same package “bios”!

The Bios takes care of:

9.3Core library

The core library is 100% Minimacy source code.

core.2d.bmpbmp file format
core.2d.gif gif loader
core.2d.gl basic opengl functions for 2d display
core.2d.polygon drawing polygons
core.2d.png png file format
core.2d.qrcode generate qr-codes
core.3d.gl basic opengl functions for 3d display
core.crypto.aes aes cryptography
core.crypto.asn1 asn1 functions
core.crypto.block cipher blocks
core.crypto.cer cer file format
core.crypto.csr csr file format
core.crypto.curve25519 elliptic curve 25519 functions
core.crypto.des des cryptography
core.crypto.ec elliptic curves functions
core.crypto.ed25519 edCsa cryptography
core.crypto.hash hash functions (sha, ...)
core.crypto.key keys from asn1, pem, der
core.crypto.oaep oaep cryptography
core.crypto.oid some oids
core.crypto.pem pem file format
core.crypto.pkcs1 pkcs1.5 cryptography
core.crypto.primal primal generation
core.crypto.rsa rsa cryptography
core.crypto.rsapss rsa-pss cryptography
core.crypto.shamir shamir splitting algorithm
core.crypto.sign cryptography signing
core.db.compiler compiler for lambdaDB
core.db.kv simple (key, value) database
core.db.lambda lambdaDB
core.net.dns dns requests
core.net.http.cli http client
core.net.http common http declarations (src+cli)
core.net.http.srv http server
core.net.mime mime types
core.net.mysql mysql connector
core.net.ssh ssh client protocol
core.net.ssh.sftp sftp protocol
core.net.ssh.shell shell over ssh protocol
core.net.tls.tcp tls over tcp
core.net.tls13 tls1.3 implementation
core.net.websocket websockets
core.net.xtcp tcp abstraction (with/without tls)
core.util.base64 base64 encoding/decoding
core.util.gzip gzip encoding/decoding
core.util.hashset hashset implementation over hashmap
core.util.insertsort insert sort algorithm
core.util.json json encoding/decoding
core.util.quicksort quicksort algorithm
core.util.regexp regexp
core.util.table displaying tables content in terminal
core.util.tar tar encoding/decoding
core.util.token simple tokenizer
core.util.tree1D binary tree
core.util.tree23 123 trees
core.util.xml xml encoding/decoding
core.util.zipzip encoding/decoding

10Other libraries

10.1Networking

The Bios gives access to standard networking:

10.2Files

The Bios contains file management functions. These rely on the host file system, and may work differently from one host to another.

Basic functions are “load” and “save”, they work on the entire file. You mays also user fileOpen, fileRead, ... to work more precisely inside the file.

11Advanced

11.1Parametric types

Parametric types allow polymorphic user types.

Consider the following example:

struct Ref=[valRef];; 
fun refCreate x=[valRef=x];; 
fun refGet ref = ref.valRef;; 
fun refSet ref val= set ref.valRef=val;; 
 
fun run= refCreate 123;; 

We define a structure “Ref” with only one field “valRef”. It works like a memory cell: we create it with an initial value (refCreate), then we can access the value (refGet) or set the value (refSet). This is definitely a structure which could be used with any type of value.

However, the last line (refCreate 123) will inform the compiler that valRef is an integer, and we won’t be able to use it with strings or any other type.

What we need is a parametric type. We just change the first line:

struct Ref{A}=[valRef:A];; 

Now the compiler knows that Ref has a variable type. We can replace A with any type:

This is the only time when you really need to write a type.

You may have multiple parameter:

struct Multi{A B}=[aMulti:A bMulti:B];; 

Parametric types are also available for sums. For example, a binary tree of any type of value could be defined by:

sum Tree{A}= nodeT Tree{A} A Tree{A}, emptyT;; 

11.2Derived structures

Sometimes we’d like to create a new type from an existing structure by adding a few fields.

Consider the following example:

struct Point=[xP yP];; 
struct NamedPoint= Point+[nameP];; 
fun vectorFromTwoPoints p q = [ xP=(q.xP-p.xP) yP=(q.yP-p.yP) ];; 

We first define a structure Point with 2 coordinates xP and yP.

Then we define a structure NamedPoint by adding a field nameP to the structure Point.

We define the function “vectorFromTwoPoints” which uses the fields xP and yP, and it is pretty obvious that we should be able to call it with values of type Point or NamedPoint.

The compiling gives :

> struct Point            : [ yP xP ] 
> field xP                : a1{Point} -> Int 
> field yP                : a1{Point} -> Int 
> struct NamedPoint       : Point + [ nameP ] 
> field nameP             : NamedPoint -> w1 
> fun vectorFromTwoPoints : fun a1{Point} a2{Point} -> Point 

We discover a new type notation “a1{Point}”. Its meaning is “any derived type of the type Point, including the type Point itself”.

The type of vectorFromTwoPoints is also interesting as both arguments are derived from the type Point, but independently: see a1{...} and a2{...}. We may call this function with a value of type Point and a value of type NamedPoint.

Let’s take the opportunity to raise the weak type of field nameP (w1). At that point in the source code, nothing indicates that it will probably be a string.

It is possible to cast derived types. Our example with Point and NamedPoint leads to this:

fun castFromNamedPointToPoint p = Point< p;; 
fun castFromPointToNamedPoint p = NamedPoint<Point p;; 

Casting from the derived type to the parent type is always possible, and the syntax is simply “ParenType < value”

Casting from the parent type to the derived type will work only if the value to cast has this derived type. If the value has another derived type from the same parent, it will compile but return “nil”. The syntax is “DerivedType < ParentType value”.

The “match” construction can be used to extract a derived type:

fun getName p = match p with 
NamedPoint q -> q.nameP, 
Point q -> "no name";; 

A structure may be derived from an already derived structure!

struct ColoredNamedPoint= NamedPoint + [colorP] ;; 

We’ll also encounter weak types in derived types.

var P;; 
fun vectorFromP q= vectorFromTwoPoints P q;; 

P is a global variable, and we have a function “vectorFromP” which computes a vector from P to an argument q. The resulting types are:

> var P                         : w1{Point} 
> fun vectorFromP               : fun a1{Point} -> Point 

At that time the compiler knows that P has a type which is derived from Point, but it does not know yet if it is Point or NamedPoint or ColoredNamePoint. It can be only one of them, and you cannot use “set P=...” one time with a value of type Point and another time with a value of type NamedPoint. Therefore it is a weak type: w1{Point}.

11.3Threads, Apps and memory

11.3.1The basics

The Bios implements a preemptive multi-threading behaviour, based on three concepts:

A memory manager is defined by a name, a maximum memory size and a set of currently allocated memory blocks. Its task is to provide memory in respect of this maximum size. It may have a parent memory manager. Memory allocated by a child memory manager is taken into account for both child and parent Memory managers.

The memory manager is in charge to stop a thread when the memory limit is reached.

An App has a name and a memory manager.

A thread is a processing unit with a name and a dedicated stack. It also has a dedicated memory manager whose parent is an App’s memory manager.

This is the typical usage of these concepts:

Now we have a mono-thread App. This is how to go multi-thread:

Now we have an App with two threads with their own Memory Manager and a common Memory Manager parent. This guarantees that the memory limit assigned to the app will be respected.

11.3.2Sharing the memory manager

Sometimes we need more control on memory. Assume that we have an in-memory database App. Other client Apps interact with this database and store some data into the database. This data has probably been created by the client App and is attached to the client App’s Memory Manager. It makes sense that when this data is stored into the database it is attached to the database App’s Memory Manager. There is the function ‘memoryTake’ for that.

Another situation is when a lambda function should be run under a different Memory Manager: the lambda function is evaluated by Thread A but its memory allocations are attached Thread B’s Memory Manager. This can be achieved with function ‘memoryUse’.

To make it possible there is a simple security mecanism: the function ‘memoryAuthorize’ returns an authorization to use the Memory Manager of the current thread. There is no other way to get this authorization, and this authorization is required to run memoryTake and memoryUse.

11.3.3Messaging and Thread Life Cycle

A thread has a fifo of incoming messages. A message is a lambda function without parameter. Processing the message means evaluating the lambda function.

As soon as the lambda function is evaluated, the thread evaluates the next message if any, else it goes to idle mode.

It is possible to post a message to a thread with the function threadPost.

When a thread is slow in the evaluation of a message, it prevents it from evaluating its next pending messages. However, and because Minimacy has preemptive multi-threading, the other threads will run: the scheduler suspends the slow thread and run other active threads, before allocating another slice of CPU time for the slow thread until its evaluation is over.

The fifo of incoming messages has a limited size. When it reachs this limit, a thread trying to post another message with threadPost will be suspended until the fifo is not full anymore.

11.3.4Synchronization

Preemptive multithreading requires synchronization capabilities between threads.

Minimacy provides Lock and Join.

A Lock secures the evaluation of a lambda function.

A Join is like a pipe:

This systems allows an elegant “await” mecanism:

fun await asyncFun= 
let joinCreate -> join in 
( 
threadFork "async" (lambda = call asyncFun join); 
joinWait join 
);; 

The function await has the following polymorphic type:

fun fun Join(a1) -> a2 -> a1 

The lambda function asyncFun is called in a new thread with a new Join, and whatever it does, synchronous or asynchronous, at the end of the day it must call joinSend to provide its result. We don’t care about the returned value of asyncFun.

The lambda function asyncFun has the following polymorphic type:

fun Join(a1) -> a2 

11.4Top-Level

Locate the file “topLevel.mcy” in the Minimacy directory, and launch it.

You get a Top-level interface with a prompt:

Minimacy Top-level - Sylvain Huet - 2020 
Ready 
>  

It looks like a shell, but it is more than that. A dedicated empty package has been created for you and you can type either declarations as in a *.mcy file, and expressions.

The Top-level automatically adds “;;” at the end of each line.

> 123+456 
-> Int: 579 (0x243) 

12Samples

12.1Http client

Minimacy makes it easy to send http requests.

You can use the simple function “httpGet” if all you need is a GET request without special header and without any interest for the returned headers. The function performs a HTTP/1.0 request, so the connection is closed after the request.

import core.net.http;; 
import core.net.http.cli;; 
 
fun run= 
echoLn httpGet "https://www.minimacy.net";; 

If you are interested in a better control, consider the following example:

let httpCreate "POST" "https://www.minimacy.net" HTTP_1_0 -> h in 
( 
httpAddHeader h "Cookie" "myCookie=xxxxxxxxxxxxxx"; 
httpSetPostData h "foo=123&bar=456"; 
httpSend h (lambda status headers data= 
dump status;// tuple like [ "HTTP/1.1" 200 "OK" 
dump headers;// hashmap key->val 
echoLn data;// response content 
) 
);; 

The major improvement of HTTP/1.1 is that you can reuse a connection for multiple requests. Consider this last example:

// first request 
let httpCreate "GET" "https://www.minimacy.net" HTTP_1_1 -> h in 
httpSend h (lambda status headers data= 
echoLn data; 
// second request, same connection 
httpReuse h "GET" "https://www.minimacy.net" HTTP_1_1; 
httpSend h (lambda status headers data= echoLn data) 
);; 

The function “httpReuse” clears any request header from the previous call.

12.2Http server

Minimacy makes it easy to start a http server.

import core.net.http;; 
import core.net.http.srv;; 
 
fun run= 
srvHttpCreate nil nil 8080 
nil// lambda function for initial acceptance 
(lambda h uri= 
dump srvHttpGetRequestHeaders h; 
dump srvHttpParseArgs h; 
strFormat "<h1>Hello</h1><br>Time: *<br>Request: *" 
(fullDate time) uri 
) 
nil// lambda function for websocket incoming message 
;; 

Run the program and open the following url in your browser: http://127.0.0.1:8080/foobar

Each time a request is received, the http server creates a new thread to handle the connection. In the previous example, we didn’t fill two lambda functions (set at nil).

The first one is evaluated when the request header is received. If it doesn’t return “true”, the connection is closed immediately.

The last one is evaluated when a websocket message has arrived.

In the following example, we reject the connection when it is received on an even timestamp. Le last lambda function displays the content of a websocket message and send it back.

srvHttpCreate nil nil 8080 
(lambda h= bitTest time 1) 
(lambda h uri= echoLn uri) 
(lambda h opcode msg= wsSend h opcode echoLn msg) 
;; 

The two first arguments of srvHttpCreate are nil in the example. The first one may be a SSL certificate for https. The second one is to bind the server with a specific IP address.

Testing the websocket server requires to have a websocket client. It is a bit harder than just typing a url in your browser. You may create a html page with the following javascript content:

<script> 
const socket = new WebSocket('ws://my_server/uri'); 
socket.onopen    = (event) =>socket.send('Hello'); 
socket.onclose   = (event) =>alert('Websocket terminated'); 
socket.onmessage = (event) =>{console.log('receive',event.data);}; 
</script>" 

13Appendix

13.1Grammar

13.1.1Types

‘*’ means any number, including 0.

Red characters are part of the Minimacy syntax.

Type=Native
|Type*
| Type* Type
| Type
| Type
| Type Type
| Type
|Sum
|Struct
|Any
|Weak
|Recursive
Sum=Label
|LabelParam*
Struct=Label
|LabelParam*
|AnyLabel
|WeakLabel
|AnyLabel{}}
|WeakLabelParam*
Native=Native type: , , , ...
Label=User type name
Param=Parameter name
Any=Type “any”: , , , ...
Weak=Weak type: , , , ...
Recursive=Recursive type: , , , ... (the number is the level of the recursion)

Recursive types are quite unusual but you may encounter them:

13.1.2Minimacy

Minimacy=Declaration*
Declaration= Function LocalExt* Program
| Global
| Global Program
| Global
| Global Program
| TypeDef Fields
| TypeDef TypeDef Fields
| TypeDef Constructs
| TypeDef Constructs
| Package
| Package
| Label*
|
Program=Expr
|Expr Program
Expr=Arithm
|Arithm Expr
|SpecialProgram
Arithm=A1
|A1 Arithm
|A1 Arithm
A1=A2
|A1
A2=A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
|A3 A3
A3=A4
|A4 A3
|A4 A3
|A4 A3
|A4 A3
A4=A5
|A5 A4
|A5 A4
|A5 A4
|A5 A4
|A5 A4
|A5 A4
|A5 A4
A5=A6
|A6 A5
|A6 A5
|A6 A5
|A6 A5
|A6 A5
A6=Term
| A6
| A6
| A6
| Integer
| Float
|Float
Term= Program
| Program
|Integer
|Char
|
|String
| TypeName
| (NameOfField Expr)*
| Expr*
| Expr*
|Ref
| Ref Expr
|TypeName TypeName Expr
|Function Expr*
| Function
| LocalExt* Program
| Expr LocalExt Expr
| Expr Expr Expr
| Expr Expr
| Expr Expr
| LocalExt Expr Expr Expr Expr
| Local Expr Expr Expr
| LocalExt Expr Expr
| LocalExt Expr Expr
| Expr Expr*
|ConsName Expr*
| Expr Case
| String Expr*
| String Expr*
| Expr
| Expr
| Expr
| Expr Case
LocalExt=Local
|
|LocalExt LocalExt
| LocalExt*
|LocalExt
Fields=Field
|Field Fields
Field= FieldName
|FieldNameType
Constructs=Construct
|Construct Constructs
Construct=ConsName ConsArg*
ConsArg=Type
|
Case=Case'
|Case' Case
| Expr
Case'=Expr Expr
| ConsName LocalExt* Expr
|TypeName Local Expr
Ref=Label
|Ref FieldName
|Ref Expr
Label=Global
|Local
Function=name of a function (lowercase first letter)
Local=name of a local variable
Global=name of a global variable or constant (uppercase first letter)
TypeDef=TypeName
|TypeName Labels*
Type=type (see Appendix “Type grammar”)
Special=special mode: , , , , ,
TypeName=type name (uppercase first letter)
FieldName=field name
ConsName=constructor name (lowercase first letter)
Package=string
|PackageName
PackageName=Name
|Name PackageName
Integer=integer: decimal, hexadecimal
Char=character
String=binary string inside double-quotes
Float=floating number
Comments=lines starting with
| ...