
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:
Then the terminal displays informations about Minimacy, including the version number.
If you want to recompile the virtual machine:
- first install Visual Studio 2019 or later
- open [minimacy]/windows/minimacy/minimacy.sln
- let Visual Studio compile it (Ctr-B)
- the resulting file minimacy.exe is found at [minimacy]/bin/minimacy.exe
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:
Now you can run:
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:
If you want to recompile the virtual machine:
The resulting files are in [minimacy]/bin/
1.1.3MacOS
On MacOS, there are two versions of the Minimacy machine:
- a command line tool
- a macOS App
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:
Now you can run:
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:
If you want to recompile the virtual machine:
The resulting files are in [minimacy]/bin/
1.1.3.2MacOS App
More to come:
- COCOA Xcode project
- IOS Xcode project
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:
What we can see there:
fun | we are declaring a function (because functions are fun;-) |
run | the name of the function |
= | the result of the function follows the sign “equals” |
echoLn | this 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:
- on windows: double-click on hello.mcy
- on unix: [minimacy]/bin/minimacy hello.mcy
- on macos: [minimacy]/bin/minimacyMac hello.mcy
> system: ~/minimacy | The system directory. It is the directory containing the hello.mcy file |
> compiling 'hello' | start the compilation of hello.mcy |
> fun run : fun -> Str | the inferred type of the function “run” |
> compiled in 0 ms | always 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:
- Minimacy is a virtual machine which runs the Minimacy language
- Minimacy programs are source files with ".mcy" extension
- When Minimacy starts, it compiles the source file and calls the function “run”
- The function “run” has no argument
- It stops automatically when there is nothing more to compute and nothing to wait for
- the machine did not create any intermediary file, such as a binary or bytecode file
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:
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:
Let’s call this function from the function “run”:
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:
Consider now the function: g(x,y)=x+y. Create a file "sample.mcy" with:
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:
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:
- the compiler knows when a label is a local variable, a global or constant variable, a function, a type, a field, a constructor:
- a local variable is defined in the function
- global variables and constants start with an uppercase character
- function names start with a lowercase character
- type names start with an uppercase character
- constructor names start with a lowercase character
- fields name are used after a dot (read) or before an = (write)
- let’s assume that f and g are functions and x and y are local variables:
- the compiler interprets it this way:
- now, if f has 2 arguments and g has 1 argument, the correct syntax should have been:
- instead the simple notation “f g x y” will raise a compilation error because the compiler believes that ‘y’ is the second argument of g :
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.
Adding a hash sign before a function name gets a kind of pointer to this function. The result of this program is:
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:
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:
- first we compute a value: let x*x
- then we decide to assign a name to this value: -> square_x
- then comes the computation in which we will use this name: in ... square_x...
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.
Type | Minimacy syntax | Description |
Bool | true, false | The boolean type has two values: true and false |
Int | 1234, 0xffc0000 | 62 bits signed integer |
Str | "hello" | The string is a immutable array of bytes |
Float | 1.234 | 62 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
Operator | Type | Description |
+ - * / ** % | fun Int Int -> Int | arithmetic on integers (**: power) |
& | ^ << >> | fun Int Int -> Int | operations on bits |
+. -. *. /. **. | fun Float Float -> Float | operations on floats (**.: power) |
&& || | fun Bool Bool -> Bool | operations on booleans |
! | fun Bool -> Bool | operation on booleans |
< > <= >= | fun Int Int -> Bool | comparison on integers |
<. >. <=. >=. | fun Float Float -> Bool | comparison on floats |
== != <> | fun a1 a1 -> Bool | comparison 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:
What | Notation |
Define an empty list | nil |
Create a new list from an existing one by adding an element at the beginning of the existing one | 1: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:
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:
- the length of the empty list is zero
- the length of a non empty list is one plus the length of the list without its first element
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 0 | when 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:
The inferred type of myListLength is interesting:
“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:
The inferred type of myListSum is:
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:
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:
- if p is the empty list, then the concatenation is the list q itself
- else the concatenation is the first element of p (hd p) followed by the concatenation of the tail of p and q
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:
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:
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”:
This is also the case when your function does not use an argument:
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.
“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:
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.
Now if another part of the code gives the missing information, the weak type is replaced by the actual type.
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:
Launch this program, and get the following error message:
How to read this?
What it means:
- Foo compilation is complete: it is an integer
- Bar compilation is not complete (keyword “proto”) and its type is still unknown when the compiler stops
- “Compiler: 'Str' does not match with 'Int'”: the compiler expects an integer and is given a string.
- In fact the “+” operator has type “fun Int Int -> Int”, but we give a string as second argument
- The last two lines show where the compiler stops. The compiler always stops at the first error.
2.7Lambda functions
In Minimacy you can manipulate functions like any other data.
Lambda functions are functions with no name. Consider this example:
Run this program:
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:
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:
- functions “echoLn” and “dump” return their argument. On a computation point of view, they do nothing. But on the side, they send data to the output terminal. This is a side effect
- we don’t care about the result of the function “run”. If we don’t care, why evaluate it? because it performs at least a side effect: we had calls to dump or echoLn
Another side effect is changing the value of a variable. In Minimacy you can use a “set” like this:
“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 “;”
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?
- when we ignore the result of the function, the compiler cannot check the type of this result
- changing the value of a variable makes is more complex to test a program, because there are hidden states: the same computation will give different results depending on the values of some variables.
Why are side effects useful?
- a program without side-effect can only takes arguments and return values, as in a spreadsheet, in a determinist way. Same arguments mean same results.
- interactivity requires side-effect
- i/o (files, networking, display) requires side-effect
In Minimacy it is easy to detect side-effects:
- if you are using the single “;”, then there is a side-effect in the previous expression because you ignore its value
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.
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.
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:
- Label => xL
We instantiate a structure like this:
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:
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 :
- zeroN is a constant constructor
- intN contains one value (one underscore)
- addN and mulN contain two values (two underscores)
Constructor names must start with a lowercase character.
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:
- intN 123 : a node with an integer value 123
- mulN (intN 3) (addN (intN 1) (intN 2)) : an arithmetic tree for the expression: 3 * (1 + 2)
Note that there are extra brackets on the last line for readability.
Let’s run this:
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:
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:
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:
- hashmapCreate 8 : this will create a hashmap with an internal array of size 2^8=256
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:
- hashmapCreate n : create an hashmap
- hashmapSet hashmap key value : set the value associated to a key
- hashmapGet hashmap key : get the value associated to a key. nil if the key is not found
let hashmapCreate 4 -> h in (hashmapSet h true 123; hashmapSet h false 456; hashmapGet h 123)
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:
- fifoCreate : create a fifo
- fifoIn fifo value : push the value in the fifo
- fifoOut fifo : pull the next value from the fifo
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:
It is possible to use “match” for any kind of data.
For example with our previous enum:
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
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:
At the end of each loop we will replace the value of “i” by “i+2”.
6.2.2list iterator
The local variable val will be 3 in the first loop, 2 in the second, 1 in the last.
6.2.3array iterator
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”.
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.
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:
Then your program may throw exceptions, for example:
Your “throw” command should be called inside the computation of a try ... catch.
Let’s run this program:
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:
If you need to handle different types of exceptions, you may use the same construction as for the “match”, with the same default case:
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.
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:
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:
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:
The compiling gives this:
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:
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:
- uiStart: create a window, with geometry and style and title
- uiBuffer: returns the bitmap buffer, then you draw what you want into this buffer
- uiUpdate: call this function to draw the bitmap buffer into the screen
8.2Interactivity
Add interactivity: change the color on a click
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:
- uiStart is called with UI_GL type
- the renderLoop calls standard openGL functions : glClearColor, glClear and glSwapBuffers
- the function “onTimeout” evaluates a lambda function after a delay expressed in milliseconds
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:
- /bios/* : bios
- /core/* : official 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:
- [system]/a.b.c.mcy
- [system]/a/a.b.c.mcy
- [system]/a/b/a.b.c.mcy
- [system]/a/b/c/a.b.c.mcy
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:
- the i/o: sockets, timers, keyboard
- the Minimacy threads and their scheduler
- the workers (native processing in a native thread)
- the messaging between threads
9.3Core library
The core library is 100% Minimacy source code.
core.2d.bmp | bmp 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.zip | zip encoding/decoding |
10Other libraries
10.1Networking
The Bios gives access to standard networking:
- sockets TCP, UDP, client and server
- DNS resolution: name to ip, ip to name
- http client and server, including websocket,TLS1.3
- ssh client, including sftp
- mysql client
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:
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:
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:
Parametric types are also available for sums. For example, a binary tree of any type of value could be defined by:
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:
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 :
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:
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:
A structure may be derived from an already derived structure!
We’ll also encounter weak types in derived types.
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:
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:
- the Memory Manager
- the App
- the Thread
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:
- an App is created with a name, a memory limit and a lambda function
- the system creates an App’s Memory Manager with this name and this memory limit
- the system creates a Thread with this name and a new Memory Manager whose parent is the App’s Memory Manager.
- the system post the lambda function to this new thread
Now we have a mono-thread App. This is how to go multi-thread:
- the thread calls the function threadFork to start another thread in the same App
- this function is called with a name and a lambda function
- the system creates a Thread with this name and a new Memory Manager with the same parent as the calling thread’s.
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.
- You create a Lock with lockCreate. Only one thread at a time can use the lock.
- Thread A and Thread B call lockSync with this lock and a lambda function
- The system guarantees that one of the two threads will have to wait for the other to complete evaluation of the lambda function
A Join is like a pipe:
- thread A creates a join with joinCreate
- then it may start a thread B, and call joinWait
- the call to joinWait suspends thread A until thread B calls joinSend
This systems allows an elegant “await” mecanism:
The function await has the following polymorphic type:
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:
11.4Top-Level
Locate the file “topLevel.mcy” in the Minimacy directory, and launch it.
You get a Top-level interface with a prompt:
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.
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.
If you are interested in a better control, consider the following example:
The major improvement of HTTP/1.1 is that you can reuse a connection for multiple requests. Consider this last example:
The function “httpReuse” clears any request header from the previous call.
12.2Http server
Minimacy makes it easy to start a http server.
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.
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:
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 |
| | ... |