Monkey 🐒 the language interpreter implementation done with C++. https://interpreterbook.com/
- There is no compiler implementation here. Sorry. But I dont have a piece of time for this :(
- Feel free to ask me anything.
- Now I'm a more experienced and would rewrite this code completely :)
- thanks Thorsten Ball :)
- https://monkeylang.org/
- C++ compiler (c++11 or later)
- The book https://interpreterbook.com/. If you have not read it yet.
Mico is a header-only project. Well you need just compile mico.cpp
github $ git clone https://github.com/newenclave/mico.git
github $ cd mico
mico $ git submodule update --init etool
mico $ g++ -std=c++11 mico.cpp -O2 -Iinclude -Ietool/include -Wall -o mico
Or clang
mico $ clang++ -O2 mico.cpp -std=c++11 -Iinclude -Ietool/include -Wall -o mico
Or MS SDK compiler. It should be called from the "Visual Studio Command Prompt" for example
mico> cl /I include /I etool/include mico.cpp
mico> link /OUT:"mico.exe" mico.obj
Or just use Visual Studio (at least version 12)
Thats all.
"Mico" is an implementation but of course it has some difference. For now I'm pretty sure that Mico can run the major part of Monkey's code.
Mico supports: integers, floats, chars, strings, arrays, tables, functions, modules, intervals
let int = 1_000_000 // int is an integer
let f = 0.1e-3 // f is a float `0.0001`
let s = "This is a string" // string
let t = { "x": 0, "y": 0, "z": 0 } // table (or Hash)
let a = [1, 2, 3, 4, 5, 6, [0, 0, 0]] // array (that contains another array)
let fun = fn(a, b){ (a - b) * (a + b) } // function
let mod = module { let var1 = 1; let var2 = 2 } // module
let ival = 1..100 // interval
let c = '🐒' // character
Identifiers can use unicode symbols
let кирилица = "Кирилица"
let españa = "Spain"
let 中國 = 12312
let العربية
= {1: 中國}
io.puts(кирилица)
io.puts(españa)
io.puts(中國)
io.puts(العربية
[1])
// Кирилица
// Spain
// 12312
// 12312
Every token has its position.
let test_val = someUnknownCall()
error: [1:15] Identifier not found 'someUnknownCall'
Numbers can contain a gap symbol _
. The symbol can be included both in integers and in floats.
let int = 1_000_000 // 1000000
let hex = 0xAAA_BBB // 11185083
let bin = 0b1111_0000_1111_0000 // 61680
let ter = 0t22_11_22_11 // 6232
let oct = 0_777_111_222_333 // 68604470491
let flt = 10.1000_0002 // 10.10000002
let s = "string"
io.puts(s[1])
// `t`
let s = "中國"
io.puts(s, " ", len(s), " ", s[0])
// `中國 2 中`
Yes. It supports the unicode. Not completely of course. I've written a small utf8 parser and I think it's enough for this toy language. And for Windows it uses native API for encoding.
Raw strings are just arrays of bytes
let s = r"string"
io.puts(s[1])
// 116
let s = r"中國"
io.puts(s, " ", len(s), " ", s[0])
// 中國 6 228
Unlike strings raw strings know nothing about the unicode. And that is why string
中國
has length 2 and the
lenght of the raw string
is 6.
Slice is a part of an array or a string. Slice holds the object (string or array) and an interval [left..right]
.
The slice can be created with index
operator []
. Interval includes only its left side (i.e. the intervals are left closed, right open
).
Full container interval can be created with the interval cont[0..len(cont)]
or cont[0..-1]
(see "Negative index" above)
let str = "This is a string"
let this = str[0..4]
io.puts(this)
// shows `This`
Arrays slices
let arr = [0,1,2,3,4,5,6,7,8,9]
let s = arr[0..3]
for i in s { io.put( i, " " ) } // shows `0 1 2`
Slice can change its direction. If the left part of the interval is greater then right, interval changes its direction.
let arr = [0,1,2,3,4,5,6,7,8,9]
let s = arr[8..2]
for i in s { io.put( i, " " ) } // shows `7 6 5 4 3 2`
Of cource it's only valid when an element is accessed by index
let str = "Hello, world!"
let s = str[len(str)..0]
for i in s { io.put( i ) } // shows `!dlrow ,olleH`
/// but!
io.puts(s) // Hello, world!
Slices are not copyes!
Slice can be also created from another slice.
let arr = [0,1,2,3,4,5,6,7,8,9]
let s = arr[1..10] // s is a slice[1,2,3,4,5,6,7,8,9]
let t = s[2..5] // t is a slice[3, 4, 5]
for i in t { io.put( i, " " ) } // shows `3 4 5`
Elements of arrays or strings can be obtained by negative index. -1
means the last element of the array/string
let arr = [0,1,2,3,4,5,6,7,8,9]
io.puts(arr[-1]) // 9
io.puts(arr[-2]) // 8
let s = "中國"
io.puts(s[-2]) // 中
Slices also can be created by adding a negative index
let str = "Hello, world!Äáç¶"
let sym = str[-5..-1]
let hello = str[0..-5]
io.puts(sym) // Äáç¶
io.puts(hello) // Hello, world!
By-default all values set by let
statement are inmutable. For making a variable, that can change its value use var
statement.
Every variable is a real variable and can be changed by assignmet operator =
.
var a = [1, 2, 3, 4, 6]
var b = 1
let d = 77.77 // `d` is constant
b = 10 // ok
c = 100 // oops; error: [1:0] Identifier not found: 'c'
d = 88.88 // oops; error: [1:2] Invalid left value for ASSIGN d
let fun = fn( ) {
let a = 100 // here `a` shadows the variable `a` from the global scope
b = 0.0 // here `b` is from the global scope
}
fun( )
All objects are also constats. It can be changed by keyword mut
. It makes sense for arrays and tables.
let a = [1,2,3,4]
a[0] = 1000
// error: [2:5] Invalid left value for ASSIGN a[0]
// as far as the object is const
let a = mut [1,2,3,4]
a[0] = 1000 // ok
io.puts(a[0]) // shows `1000`
The const
keyword can make an object constant.
let a = mut [1,2,3,4]
a[0] = 1000 // ok
let a = const a // set const
a[0] = 1000
// error: [1:5] Invalid left value for ASSIGN a[0]
The keywords mut
and const
always make a copy of the object if the mutablity of the object is different.
let a = mut [1,2,3,4]
let a = mut a // returns `a` without changes
let a = const a // makes a copy of `a`
The operator is a right arm
operator.
var a = [1, 2, 3, 4, 5]
a[0] = a[1] = a[2] = a[3] = a[4] = 0
// a == [0, 0, 0, 0, 0]
The let
statement makes constants objects
let a = [1, 2, 3, 4, 5]
a[0] = a[1] = a[2] = a[3] = a[4] = 0
// error: [2:9] Invalid left value for ASSIGN a[0]
Intervals are pairs of values. Values can be integers, floats, strings or boolean. Operator ..
creates an interval.
For now there are not a lot of operations with intervals.
let float = 1.0..100.0
let int = 1..100
let bool = false..true
let strings = "a".."z"
The operator makes a for-loop. It's an expression and always returns its value
as the result. There are several types of the operator.
Simple counter loop. Accepts an integer or a float as a value
and makes a loop that is repeated value
times
for i in 10 {
io.put(i, " ")
}
io.puts( )
This code shows 0 1 2 3 4 5 6 7 8 9
.
The operator can have a step
value that can change loop's increment and even make it negative
for i in 10, 2 {
io.put(i, " ")
}
io.puts( )
// 0 2 4 6 8
for i in -7, -0.7 {
io.put(i, " ")
}
io.puts( )
// 0 -0.7 -1.4 -2.1 -2.8 -3.5 -4.2 -4.9 -5.6 -6.3
An interval loop acccepts a numeric (float or integer) interval and repats [start..stop]
times. It means that this type of the loop includes right side of the interval.
for i in 1..10 {
io.put(i, " ")
}
io.puts( )
// 1 2 3 4 5 6 7 8 9 10
// the `reverse` variant is also available
for i in 0.15..-12, -1.66 {
io.put(i, " ")
}
io.puts( )
// 0.15 -1.51 -3.17 -4.83 -6.49 -8.15 -9.81 -11.47
for i in -12..0.15, 1.66 {
io.put(i, " ")
}
io.puts( )
// -12 -10.34 -8.68 -7.02 -5.36 -3.7 -2.04 -0.38
As you can see values -13.13
(next to -11.47 for first) and 1.28
(next to -0.38 for second) are not shown because they lie outside the intervals
A container loop accepts containers (i.e. arrays, strings, slices or tables) and iterates those values.
for v in [1, "Hello", 0.19, -100] {
io.put(v, " ")
}
io.puts( )
// 1 Hello 0.19 -100
for v in {1: 1, 2: -0.2, 3: "zero.3"} {
io.put(v, " ")
}
io.puts( )
// zero.3 -0.2 1
// yep. table doesn't have an order
For loop that accepts an array and a string it's also possible to use negative step
.
for v in [1, 2, 3, 4, 5], -1 {
io.put(v, " ")
}
io.puts( )
// 5 4 3 2 1
// And of course it doesn't have to be `1` or `-1`
for v in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], -2 {
io.put(v, " ")
}
io.puts( )
// 10 8 6 4 2
for i in "Кирилица, latinic, 中國 จีน มีตัวละครจ πολλοί عرب ٹیسٹ χαρακτήρες", -1 {
io.put(i)
}
io.puts( )
// ςερήτκαραχ ٹسیٹ برع ίολλοπ จรคะลวัตีม นีจ 國中 ,cinital ,ацилириК
for i in "Кирилица, latinic, 中國 จีน มีตัวละครจ πολλοί عرب ٹیسٹ χαρακτήρες", -3 {
io.put(i)
}
io.puts( )
// ςήαχس عοοจะัมี國,na,ир
// raw string are also itarables
for i in r"Кирилица, latinic, 中國 จีน มีตัวละครจ πολλοί χαρακτήρες", -1 {
io.put(i, " ")
}
io.puts( )
// 130 207 181 206 129 207 174 206 132 207 186 206 177 206 129 207 177 206
// 135 207 32 175 206 191 206 187 206 187 206 191 206 128 207 32 136 184
// 224 163 184 224 132 184 224 176 184 224 165 184 224 167 184 224 177
// 184 224 149 184 224 181 184 224 161 184 224 32 153 184 224 181 184
// 224 136 184 224 32 139 156 229 173 184 228 32 44 99 105 110 105 116
// 97 108 32 44 176 208 134 209 184 208 187 208 184 208 128 209 184 208 154 208
For table
loops step
is not available.
Key-Value syntax.
for k, v in [8,8,8,8,8,8,8,8,8] {
io.put(k, ":'", v, "' ")
}
io.puts( )
/// 0:'8' 1:'8' 2:'8' 3:'8' 4:'8' 5:'8' 6:'8' 7:'8' 8:'8'
// for the tables key is the value of the current table key.
for k, v in { "x": 0, "y": -100, "z": -1, "t": "00:00:00" } {
io.put(k, ":'", v, "' ")
}
io.puts( )
// z:'-1' t:'00:00:00' y:'-100' x:'0'
Containers loops always set next value variable of the loop as a reference if the value
is mutable.
It means that values in the container can be changed in the loop. And as far as loops are expressions we can easily create and change a container in the place
// helper function. Shows a container value
let show = fn( arr ) { for i in arr { io.put( i, " " ) } io.puts( ) }
let r = const for v in mut [1,2,3,4,5] {
v = v + v * v
}
show( r )
// shows 2 6 12 20 30
The pow
array. Let's make an array that contains functions and they make a pow
of those arguments. Just as an example
let make_pows = fn( max ) {
var res = [ ] // result is an array
for i in 0..max { // array starts from 0
res = res + [ fn( c ) { // add an element to the result
if( i == 0 ) {
return 1
}
var res = 1 // internal `res`
for j in 1..i {
res = res * c
}
res
} ]
}
res // return
}
let arr = make_pows( 10 ) // makes an array 0..10.
io.puts( arr[0](1000) ) // `1`
io.puts( arr[1](1000) ) // `1000`
io.puts( arr[2](1000) ) // `1000000`
io.puts( arr[10](2) ) // `1024`
Five little monkeys
let the_monkey_song = fn( monkeys ) {
let sing = " little monkey jumping on the bed\n"
"She fell off and bumped her head\n"
"Momma called the doctor and the doctor said\n"
"\"No more monkeys jumping on the bed!\"\n"
let plur = " little monkeys jumping on the bed\n"
"🐒 fell off and bumped his head\n"
"Momma called the doctor and the doctor said\n"
"\"No more monkeys jumping on the bed!\"\n"
for i in monkeys..1, -1 {
if ( i == 1 ) {
io.puts( "🐒", sing )
} else {
io.puts( "🐒" * i, plur)
}
}
return
}
the_monkey_song( 5 )
//🐒🐒🐒🐒🐒 little monkeys jumping on the bed
//🐒 fell off and bumped his head
//Momma called the doctor and the doctor said
//"No more monkeys jumping on the bed!"
//
// .................................
//
//🐒🐒 little monkeys jumping on the bed
//🐒 fell off and bumped his head
//Momma called the doctor and the doctor said
//"No more monkeys jumping on the bed!"
//
//🐒 little monkey jumping on the bed
//She fell off and bumped her head
//Momma called the doctor and the doctor said
//"No more monkeys jumping on the bed!"
Of course they exist. And they do what they do always.
They interrupt current loop and then continue
makes the loop continue and break
just breaks it (of course!).
for i in [1,2,3,4,5,6,7,0,-1,-2,-3,-4] {
if( i == 0 ) {
break
}
io.put(i)
}
io.puts( )
// 1234567
for i in [1,2,3,4,5,6,7,0,-1,-2,-3,-4] {
if( i == 0 ) {
continue
}
io.put(i)
}
io.puts( )
// 1234567-1-2-3-4
continue
and break
must be in the body of a loop.
for i in 10 {
if( i == 5 ) {
if( true ) {
if( true ) {
if( true ) { /// just an example
continue
}
}
}
}
io.put(i)
}
// 012346789
let r = fn( ) {
break
}
// 2:8 Unexpected 'break'
for i in 10 {
let r = fn( ) {
continue
}
r( )
}
// 3:12 Unexpected 'continue'
let x = 1000;
let res = if( x < 0 ) {
"too small!"
} elif(x == 0) {
"zero"
} elif( x < 10 ) {
"less than 10"
} elif( x < 100 ) {
"less than 100"
} else {
"too big!"
}
Also it is possible to use the infix form of the operator if
let r = io.gets("input key: ")
io.puts("ok" if len(r) >= 8 else "too short")
It looks like expression if condition [else alternative_expression]
io.puts("It's true!") if x // shows the string if condition in `x` is true
The unless
operator is a negative sibling of the operator if
. It just reverts its condition by not
logical operator.
let x = 10
let r = unless x < 0 {
"greater or equal"
} else {
"less"
}
// r == "greater or equal"
let r = io.gets("input key: ")
io.puts("fail") unless r == "valid_key" // shows "fail" if `r` is not equal to "valid_key"
Mico has: +, -, *, /, %, ==, !=, <, >, <=, >=, |, &, ^, &&, ||, >>, <<, ~, in
let bits = fn( value ) {
let impl = fn( val, acc ) {
if( val > 0 ) {
impl( val >> 1, acc + (val & 1) ) // or val % 2
} else {
acc
}
}
impl( value, 0 )
}
bits(0xFFFFFFFFF) // => 36
bits(0b1111111000000001111111) // => 14
Logical operators &&
and ||
are lazy. &&
doesn't eval its right side if its left side is false
.
||
does the same thing. It returns true
if its left side is true
let f = 10 > 100 && "100" < 100 // ok. `f` is `false`
let t = 10 > 100 || "100" < 100 // failed
// error: [1:26] Infix operation '<' is not defined for string and integer
Checks if a value exists in a container or in an interval
io.puts( 1 in { 1: "one", 2: "two", 3: "three" } ) // `true`
io.puts( 4 in { 1: "one", 2: "two", 3: "three" } ) // `false`
io.puts( 0 in 0..100 ) // `true`
io.puts( 7 in 0..6 ) // `false`
io.puts( 7 in [1,2,3,4,5,6] ) // `false`
io.puts( 5 in [1,2,3,4,5,6] ) // `true`
Complexity
i in a..b // O(1)
i in {...} // O(1) aprox. see `hash tables`
i in [...] // O(n)
All function in Monkey (i.e. in Mico also) are first-class citizens
let sum = fn(a, b) {
a + b
}
let apply = fn( call, a, b ) {
call(a, b)
}
let bind = fn( call, a ) { // returns a function that accepts 1 parameter
fn( b ) {
apply( call, a, b )
}
}
let inc = bind(sum, 1)
let res = inc(10) // res == 11
let sum = fn(a, b, c) { a + b + c}
let sum2 = sum(0)
let res = sum2(1, 2) // res == 3
Here sum2
is a function that accepts 2 parameters (a, b
for example) and returns 0 + a + b
.
A function can be "restored" from its partial from. The prefix operator *
does it.
let sum = fn(a, b, c) { a + b + c}
let sum2 = sum(0)
let res0 = sum2(2, 3) // res0 = 5
let res1 = (*sum2)(1, 2, 3) // res1 = 6
Functions in Mico can accept a variable number of arguments.
let param_count = fn( ...parms ) { // prefix operator elipsis must be the last one
return len(parms)
}
let seven = param_count( 1, 2, [], {}, fn(){ }, 6, 7 )
let zero = param_count( )
io.puts("seven is ", seven, " and zero is ", zero)
This code shows seven is 7 and zero is 0
.
Here parms
is the last parameter of the function and it's an array that contains all function's parameters.
The array can be empty.
The elipsis doesn't have a role in partial application. It is "invisible" for the partial application system
let reduce = fn( call, arr ) {
let impl = fn( arr, acc, id ) {
if( id < len(arr) ) {
impl( arr, call(acc, arr[id]), id + 1 )
} else {
acc
}
}
impl( arr, 0, 0 )
}
let sum = fn( a, b, ...args ) {
let arr_sum = reduce( fn(a, b){ a + b } ) // partial
a + b + arr_sum(args)
}
let sum_0 = sum(1, 2) // 3
let sum_1 = sum(1, 2, 3) // 6
let sum_2 = sum(1, 2, 3, 4, 5) // 15
let sum_part = sum(0.001) // `sum_part` is a function with 1 parameter + `...`
let sum_3 = sum_part( 1, 2, 3 ) // => sum( 0.001, 1, 2, 3 ) => 6.001
The pipe operator |
is a shortcat for the call
operator ()
.
The left side of the operator is passed to the right side.
In this case the right side must be a function (builtin or not).
let sum = fn( a, b ) { a + b }
let hello = sum("Hello, ") // fn( a ) { "Hello, " + a } // partial
"Hello, world!" |len // => 13
"world!" |hello // sum("Hello, ", "world!") => "Hello, world!"
"world!" |sum("Hello, ") |len // len(sum("Hello, ", "world!")) => 13
/// check this out =)
let x = 10
"world!" |if(x > 0){hello} else {(*hello)("Goodbye, ")} // => Hello, world!
......
let x = 0
"world!" |if(x > 0){hello} else {(*hello)("Goodbye, ")} // => Goodbye, world!
Well. Mico supports it. Tail Call
let spin = fn( count ) {
if( count > 0 ) {
spin(count - 1) // a tail call
} else {
"Ok"
}
}
spin( 0xFFFF_FFFF ) // Ok, Here is a tail call
let spin = fn( count ) {
if( count > 0 ) {
spin(count - 1) // not a tail call, beause of "Ok" expression
}
"Ok"
}
spin( 100_000 )
/// Stack overflow is here!
/// error: [3:12] Stack overflow spin((count-1))
let spin = fn( count ) {
if( count > 0 ) {
return spin(count - 1) // return makes the tail call
}
"Ok"
}
spin( 100_000 ) /// optimized again.
let call1 = fn(c) {
io.puts("1 ", c)
if( c > 0 ) {
call2(c - 1) // indirect call
} else {
"Ok 1"
}
}
let call2 = fn(c) {
io.puts("2 ", c)
if( c > 0 ) {
call1(c - 1) // indirect call
} else {
"Ok 2"
}
}
call1(0xFFFFFF) // Ok
Yes. Finally I've had some free time to add the system; https://interpreterbook.com/lost/ So. First of all quote, unquote, and macro are keywords and I can parse them as I wish. And there are 2 types of quote
/// expression quote = quote + ( + expression + )
let a = quote( 2 + 2 ) /// `a` is an ast node and contains (2 + 2)
unquote(a) // => ( 2 + 2 ) => 4
quote(unquote(quote(unquote(quote(unquote(quote(unquote(2+2)))))))) // => quote(4) =)
/// unquote can be placed wherever you want.
/// function body
let x = 100 /// just a value for unquote the call
let quote_func = quote( fn( a ) { unquote(10 * x) + a } )
// quote_func => quote( fn( a ) { 1000 + a } )
let quote_if = quote( if( unquote(x) > 10 ) { "huge" } else { "small" } )
// quote_if => quote( if( 100 > 10 ) { "huge" } else { "small" } )
let x = "Hello!"
let quote_call = quote( unquote(x) | len )
/// quote_call = quote( "Hello!" | len )
unquote(quote_func)(10) // 1010
unquote(quote_if) // "huge"
unquote(quote_call) // 6
/// statements quote = quote + { + statements + }
let a = quote { let a = 1000; let b = a * 2 } // `a` is an ast node
// and contains 2 `let` statements
unquote(a) // => eval `let a = 1000; let b = a * 2` => null
// now `a` is 1000 and `b` is 2000
The macro
keyword defines a piece of code that is placed in the position where it is called.
And this is all happening in the macro expansion phase before the evaluation.
let sum = macro( a, b ) { unquote(a) + unquote(b) }
sum(10, 200) // here the `ast` is replaced by body of the macro sum so => 210
let set_env = macro( env ) { unquote(env) }
set_env( quote {
let a = 10
let b = 20
} )
/// now a is 10 and b is 20
sum(a, b) // => 30 yeah! inline (a + b) =)
/// macro `test` stores the macro `sum` and it is inlined when `test` is called
let test = macro( a ) { sum(10, unquote(a) ) }
test(10) /// ast is ( 10 + 10 )
let test2 = macro( a, b ) {
let impl = macro( a, b ) { /// yes! macroses have their scopes!
/// impl can be accessed only in `test2`
unquote(a) + unquote(b) /// variable shadowing...yes here is
/// `a` and `b` shadow
/// `a` and `b` from the scope of test2
}
impl(unquote(a), unquote(b))
}
test2(10, 20) // just inlined (10 + 20) => 30 of course
/// and `The Mighty Unless Macro` of course
let unless = macro(condition, consequence, alternative) {
io.puts("Here the macro inlined!")
if(!(unquote(condition))) {
unquote(consequence);
} else {
unquote(alternative);
};
}
unless(10 > 5, io.puts("not greater"), io.puts("greater"))
// Here the macro inlined!
// greater
// =)
/// just for debug purposes
__macro( ) // shows all the macroses stored by REPL
// Macros:
// set_env => macro(env) {
// unquote(env)
// }
// ==========
// sum => macro(a, b) {
// (unquote(a)+unquote(b))
// }
// ==========
// test => macro(a) {
// sum(10, a)
// }
// ==========
// test2 => macro(a, b) {
// let impl = macro(a, b) {
// (unquote(a)+unquote(b))
// };
// impl(unquote(a), unquote(b))
// }
// ==========
// unless => macro(condition, consequence, alternative) {
// io.puts("Here the macro inlined!");
// if ((!unquote(condition))) {
// unquote(consequence)
// } else {
// unquote(alternative)
// }
// }
// ==========
// And one more thing...
let ident = macro( ){ test_sum }
let ident( ) = fn(a,b,c){ a + b + c };
let apply = fn(call, a,b,c) { call(a,b,c) }
apply(ident(), 1, 2, 3 ) /// Yeee-haa! we have made an ident!
test_sum(1,2,3) /// call
__env( ) // shows us the environment
/// Root: [0x560858819c30]
/// __env => fn(0x56085881a030) [0x560858819c30]
/// __macro => fn(0x56085881a160) [0x560858819c30]
/// apply => fn(4) [0x56381c7409a0]
/// copy => fn(0x560858819f00) [0x560858819c30]
/// len => fn(0x560858819ce0) [0x560858819c30]
/// puts => fn(0x560858819df0) [0x560858819c30]
/// test_sum => fn(3) [0x56085881ed10] // here is our function
/// Child: 1 [0x56085881d8a0]
/// Child: 1 [0x56085881ed10]
Yes, it is possible to add them. One test macro __I
I've added to the macro processor.
It just returns an ident that contains a concatination of all of the parameters it has.
let __I(name, 1) = "this is the first one"
io.puts(name1)
io.puts(__I(name, 1)) // also should work
/// this is the firts one
io.puts(__I(name, 2))
/// error: [1:29] Identifier not found 'name2'
let __I(sum, 2) = fn(a,b) { a + b }
__I(sum, 2)(1, 2) /// => sum2(1, 2) => 3
sum2(1, 2) // => 3
Modules are just pieces of the environment.
They are more like namespaces
in c++ but in Mico modules are first-class citizens.
let a = module {
let value = "value a"
let show = fn( ) { io.puts( value ) }
}
a.show( ) // shows `value a`
let b = module {
let a = a // shadowing
let show = fn( ) { io.puts( "module b: " + a.value ) }
}
b.a.show( ) // same as a.show( )
b.show( ) // `module b: value a`
let show = fn( mod ) { // accepts module like a parameter
mod.show( )
}
show( a ) /// `value a`
show( b ) /// `module b: value a`
A module can inherit values from another module. In the case of inheritance all elements of the parent are available in the child. And by the child.
let a = module {
var value = "value a"
let show = fn( ) { io.puts( value ) }
let set = fn( val ) { value = val }
}
let b = module: a { } // Inheritance
b.show( ) // `value a`
b.set( "changed!" )
io.puts(b.value) // `changed!`
a.show( ) // `changed!`
The inheritance list is a list of the expression. Every expression returns a module object. And if doesn't...
let a = module: 1 + 1 { }
// error: [1:18] Bad parent for module OBJ_INTEGER 2
A module can have one or more parents.
let a = module {
let value = "value a"
let showa = fn( ) { io.puts( value ) }
let seta = fn( val ) { value = val }
}
let b = module {
let value = "value b"
let showb = fn( ) { io.puts( value ) }
let setb = fn( val ) { value = val }
}
let c = module: a, b { } // Inheritance
c.showa( ) // `value a`
c.showb( ) // `value b`
And what is here?
io.puts(c.value)
Here is value b
. Because module b
is the last in the inheritance list and the value
from the module b
shadows the a
's value
.
All modules, by-default, are anonymous. But there is a way to add a name for them. Here:
let a = module parent1 {
let value = 100
}
let b = module parent2 {
let value = 200
}
let c = module: a, b { }
Here parent1
and parent2
are names. And why? Well. If we call c.value
(in the example above) we get 200
because of inheritance and the module parent2
is the last in the search list.
But what if we want to obtain value from parent1
? And that is exactly what the name serves for! Check this out.
let a = module parent1 {
let value = 100
}
let b = module parent2 {
let value = 200
}
let c = module child: a, b { }
let d = module: c { }
io.puts( c.value ) // 200
io.puts( c.parent2.value ) // 200
io.puts( c.parent1.value ) // 100
io.puts( d.value ) // 200
io.puts( d.child.parent1.value ) // 100
Now we can obtain anything.
Just an example. How can we construct a module with defined value?:
let m = fn( init ) {
module {
let value = init
}
}
let a = module: m(1000) { }
let b = module: m(0.01) { }
let c = module: m("Hello, world!") { }
io.puts(a.value) // 1000
io.puts(b.value) // 0.01
io.puts(c.value) // Hello, world!