Documentation
Documentation for the mini
language
Welcome to the documentation for the mini
language! This is the place to go if you want to learn more about the language and how to use it. The documentation mainly focus on the language specification.
mini
is a minimal programming language that is built for the purpose of teaching programming language design.
The language is dynamically typed and expression-based. It is designed to be easy to learn and use. There are support for both functional and object oriented programming paradigms.
Table of contents
Table of contents generated with markdown-toc
Features
The language has a rich set of features, including:
- Basic arithmetic, logic, and comparison operators
- Primitive data types such as
number
,boolean
, andstring
- Collection data structures like
list
,tuple
,map
, andset
- Lambda expressions and functions
- Classes and inheritance
- Enum types
- Variables
- Code blocks
- C-stlye Comments
- A set of built-in functions
- Basic control flow structures
- Other keywords and operators
Specification
The syntax of mini
is similar to the ECMAScript specification, but not identical.
Primitive data types
There are three primitive data types in mini
:
number
- Represents both floating point and integer numbers- Arithmetic operators:
+
,-
,*
,/
,%
(modulo) - Comparison operators:
==
,!=
,<
,<=
,>
,>=
- Examples:
21
,4.2
,-3
,-3.5
- Arithmetic operators:
boolean
- Represents logicaltrue
orfalse
- Logical operators:
&
(and),|
(or),!
(not) - Comparison operators:
==
- Examples:
true
,false
- Logical operators:
string
- Represents a sequence of characters, or a single character- Arithmetic operators:
+
(concatenation) - Comparison operators:
==
,!=
- Examples:
"hello"
,'world'
,"a"
,'b'
- Arithmetic operators:
Variable declaration
A variable declaration is the same as an assignment, but if the variable is not already declared, it is automatically declared.
x = 10
Multiple variables can be declared with the same value in a single statement.
x = y = z = 10
Or multiple variables can be declared with different values on a single line, must be separated by whitespace.
x = 10 y = 20 z = 30
All variable identifiers starting with an underscore _
are treated as โdonโt careโ variables. Meaning its value is โthrown awayโ when bound in a scope.
_ignored = 10
print(_ignored) // Error, _ignored is not defined
This is useful for catching all values in a pattern match.
match x
| 10 -> "ten"
| _ -> "other" // Catch all values not already matched
Variables can also be set to the return value of a code block.
x = {
y = 10
y * 2
}
x // 20
Comments
// Single-line comment
/* Multi-line
comment */
There are also unofficial block comment syntax for documentation. This is not currently supported in this language, but mainly processed by documentation tools to generate application programming interface (API) documentation.
/**
* Documentation comment
*
* @param {number} x
* @param {string} y
* @returns {boolean}
**/
Lambda expressions
Lambdas are declared with the =>
operator.
The left hand side of the =>
is the parameter-list tuple, and the right hand side is the function body (A single expression or block).
(x, y) => x + y
Empty parameter-list lambdas can be declared with an empty tuple. Used for generator and supplier functions.
() => 42
Functions
A function is simply a lambda expression bound to a variable. Functions can be declared by assigning a lambda expression to a variable as seen below.
add = (x, y) => x + y
add(1, 2)
Alternative syntax sugar for functions is to directly specify the parameter-list and function body separated by an assignment operator.
add(x, y) = x + y
These two forms are equivalent as the latter is a shorthand for the former.
Functions can also have a body block.
add(x, y) = {
z = y * 2
x + z // Return value
}
mini
has therefore higher-order functions.
As functions and lambdas are the same thing, they can be used interchangeably. The value of a function is the value of the lambda expression.
add = (x, y) => x + y // Lambda expression style
add10(y) = add(10, y) // Function style
[1, 2, 3].map(add10) // Function passed as parameter
// [11, 12, 13]
Collection data structures
Primitive data types are useful, but limiting on their own. mini
has a number of data structures that can be used to store and manipulate collections of data.
list
- An ordered collection of values, where each value can be accessed by index. Elements are stored in the order they are declared.- Examples:
[1, 2, 3]
- A list of three integers[true, false, 'hello', 'world']
- Mixed types[]
- An empty listlist[0]
- Access the first elementlist[-1]
- Access the last elementlist[5] = 10
- Set the sixth element to 10
- Operators:
list1 + list2
- Concatenate two listslist[index]
- Access an element by index (zero-based)
- Methods:
list.length
- The number of values in the listlist.append(value)
- Adds a value to the end of the listlist.prepend(value)
- Adds a value to the beginning of the listlist.pop()
- Removes and returns the last value in the listlist.shift()
- Removes and returns the first value in the listlist.map(fn)
- Returns a new list with the values transformed by the given functionlist.filter(fn)
- Returns a new list with the values filtered by the given functionlist.reduce(fn, initialValue)
- Returns a single value by combining the values in the listlist.indexOf(value)
- Returns the index of the given value, or-1
if the value is not in the listlist.lastIndexOf(value)
- Returns the index of the last given value, or-1
if the value is not in the listlist.join(separator)
- Returns a string with the values in the list separated by the given separator
- Examples:
tuple
- An ordered collection of values, where each value can be accessed by index.- Examples:
(1, 2, 3)
- A tuple of three integers('hello', 'world')
- A tuple of two strings()
- An empty tuple, orunit
. This should be treated the same asnull
in other languages.
- Operators:
tuple1 + tuple2
- Add the values of two tuples together (element-wise).tuple[index]
- The value at the given index
- Methods:
tuple.length
- The number of values in the tuple
- Examples:
map
- A disorderly collection of hashed key-value pairs, where each key can be accessed by index.- Examples:
#{a: 1, b: 2, c: 3}
- A map of three key-value pairs#{0: 1, 1: 2, 2: 3}
- A map with numeric keys#{'hello world': 'hello', 'goodbye world': 'goodbye'}
- A map with string keys#{}
- An empty mapmap['test'] = 123
- Assigns a value to the given key
- Operators:
map1 + map2
- Concatenate two maps together (merge the values of the two maps).map[key]
- The value at the given key
- Methods:
map.<key>
- The value associated with the given key, ie.map.abc
is equivalent tomap['abc']
map.has(key)
- Returnstrue
if the map contains the given keymap.delete(key)
- Removes the value associated with the given keymap.clear()
- Removes all key-value pairs from the mapmap.keys()
- Returns a new list with the keys in the mapmap.values()
- Returns a new list with the values in the mapmap.entries()
- Returns a new list with the key-value pairs in the map
- Examples:
More on collection data structures
There are many tips and tricks for working with collections. For example, it is possible to use a list as a stack.
stack = [1, 2, 3]
stack.append(4)
print(stack) // [1, 2, 3, 4]
stack.pop()
print(stack.pop()) // 3
It is also possible to use a list as a queue.
queue = [1, 2, 3]
queue.append(4)
print(queue) // [1, 2, 3, 4]
queue.shift()
print(queue.shift()) // 2
Or even a reversed queue.
queue = [3, 2, 1]
queue.prepend(4)
print(queue) // [4, 3, 2, 1]
queue.pop()
print(queue.pop()) // 2
Lists have support for partial pattern matching, utilizing the |
operator to separate elements from the rest of the list in a pattern match.
You can extract values from different collections using pattern matching as shown below.
match [1, 2, 3]
| [f, s | t] => print(f, s, t) // 1 2 [3]
| [h | t] => print(h) // 1
| [] => print('empty list') // empty list
| _ => print('other') // other type
list = [1, 2, 3]
if list is [head | tail] {
print(head) // 1
print(tail) // [2, 3]
}
Maps have also support for partial pattern matching.
match #{a: 1, b: 2, c: 3}
| #{a: f, b: s} => print(f, s) // 1 2
| #{a: f} => print(f) // 1
| #{} => print('empty map') // empty map
| _ => print('other') // other type
A Set is a collection of unique values.
mini
provides aset
data structure in its standard library for storing and manipulating unique values in an unordered collection.
Examples:
set(1, 2, 3)
- Initialize a set with three unique values.set()
- The empty set.
Methods:
set.length
- The number of values in the setset.has(value)
- Returnstrue
if the set contains the given valueset.add(value)
- Adds the given value to the setset.delete(value)
- Removes the given value from the setset.clear()
- Removes all values from the setset.values()
- Returns a new list with the values in the set
Enums
mini
supports Rust-like enums.
enum Letters = A | B | C
All identifiers (e.g. A
, B
and C
) in an enum must be unique.
This means that the names may not occur more than once in the enum declaration.
Example: Invalid enum declaration โ
enum Optional = A | B | A
The example above would throw an error, since
A
occurs twice in the enum declaration.
The enum variant arguments/values are assigned in the order they are declared and are dynamically typed. This means that the enum variant arguments/values can be of any type.
enum Optional = Some(value) | None
x = Some(42)
y = None
z = Some([1, 2, 3])
Classes
Animal = class {
Animal(name) = #{
name: name
}
speak() {
return "{this.name} makes a noise."
}
}
In the example above, Animal
is a class with a constructor and a speak
method.
The method is defined as a function expression, which has an implicit this
parameter.
mini
also supports basic inheritance.
Dog = class : Animal {
Dog(name) {
// Code block with logic other than just a map
return #{
name: name,
dogSpecificSound: "Woof"
}
}
speak() {
return "{this.speak()} {this.dogSpecificSound}!"
}
}
Running this with the code below will produce the following output:
dog = Dog("Fido")
dog.speak() // "Fido makes a noise. Woof!"
Classes are instantiated simliar to function calls, other languages like Python have a similar syntax.
animal = Animal("Pig")
animal.speak() // "Pig makes a noise."
animal.name // Ok, returns "Pig"
animal.dogSpecificSound // Throws an error, since dogSpecificSound is not defined on Animal
Because classes also are expressions, they can be assigned to variables. This is useful for creating objects that are used in multiple places, or for creating objects that are passed to functions. This is also how static methods can be defined on a class.
MyClass = class {
hello() {
return "Hello world!"
}
}
MyClass.hello() // Throw an error, since hello is only defined on MyClass instances
MyClass.bye() // Throws an error, since bye is not defined on MyClass
myInstance = MyClass()
myInstance.hello() // "Hello world!"
myInstance.bye() // Throws an error, since bye is not defined on MyClass instances
// Define a static method on MyClass
MyClass.bye = () => {
return "Bye world!"
}
MyClass.bye() // "Bye world!"
myInstance.bye() // Throws an error, since bye is not defined on MyClass instances byt on MyClass itself
Note:
mini
does not support static methods inside of class expressions.
Instead, static methods can be defined on the class value itself as shown above forMyClass.bye
.
Built-in functions
There are a number of built-in functions that can be used in mini
:
print
- Prints a string to the console- Examples:
print('hello')
print("hello", "world")
print(1, 2, 3)
- Examples:
input
- Gets a string from the console- Examples:
input()
input("Enter your name: ")
- Examples:
There are also a number of helper functions to convert values between different types.
string
- String representation of a given value- Examples:
string([1, 2, 3])
string(1.23)
string(true)
string(#{'a': 1, 'b': 2})
string("hello")
- Examples:
number
- Integer representation of a given value- Examples:
number('1')
number(1.23)
number(true)
- Returns1
number(#{'a': 1, 'b': 2})
- Fails with an errornumber("hello")
- Fails with an error
- Examples:
boolean
- Boolean representation of a given value- Examples:
boolean('1')
- Returnstrue
boolean(1.23)
- Returnstrue
boolean(0)
- Returnsfalse
boolean(true)
- Returnstrue
boolean("")
- Returnsfalse
boolean(#{'a': 1, 'b': 2})
- Returnstrue
boolean("hello")
- Returnstrue
- Examples:
Control flow structures
There are three control flow structures in mini
:
if
- If-then-else statementmatch
- Pattern matching statementfor
- For-loopwhile
- While-loopbreak
- Break out of a loopcontinue
- Continue to the next iteration of a loopreturn
- Return from a function, last expression in a block is implicitly returned
Below are examples of each control flow structure.
if
statementThe
if
statement is used to execute and return the value of a block of code or a single expression if a condition guard is true.
The guard is an expression directly following the if
keyword, surrounding parentheses are optional. After that, a block of code or a single expression is specified.
if x == 1 "one"
The example above returns the string "one"
if the condition x == 1
is true.
if x % 2 == 0 {
x *= 2
return x + 1
}
The example above returns the value of x * 2 + 1
if x
is even.
If statements also has the power to pattern match on values using the is
operator.
if-else
statementThe
if-else
statement is used to execute and return the value of different blocks of code or single expressions depending on the value of a condition guard.
The code below is a short implementation of the absolute value function |x|
.
if x > 0 x
else -x
else if
statementThe
else if
statements can be used to add additional conditions to anif-else
statement.
if x >= 10 "ten or more"
else if x >= 5 "five or more"
else if x >= 1 "one or more"
else "none"
match
statementThe
match
statement matches a value against a set of patterns, and returns the value of the first pattern that matches.
First specify the value to match against, then specify a list of patterns separated by |
characters.
The first pattern that matches the value is the one that gets evaluated and returned.
match x
| 1 => "one"
| 2 => "two"
| _ => "many"
One could think of match as many if x is p
statements combined into a single statement.
A match case can also have a guard using the if
keyword, which ensures that the pattern on the left hand side also satesfies a logical condition on the right hand side, before evaluating the matching case expression.
match x
| number(n) if n > 0 => "positive"
| number(n) if n < 0 => "negative"
| number(n) if n == 0 => "zero"
| _ => "not a number"
You can also inline the pattern list, which is useful when the pattern list is short.
x = Optional.Some(1)
match x | Some(n) => "some" | None => "none"
for
statementThe
for
statement is used to iterate over a list of values or an iterator.
The syntax is similar to the for
loop in other languages such as Python and Ruby, using the in
keyword to specify the list or iterable.
for x in [1, 2, 3] print(x)
Using a lazy iterator is possible by using the ..
operator.
for x in 1..10 print(x)
It is also possible to iterate over a map where the keys are used as the iterator. This is because the map data structure implements the Iterator
class interface.
m = #{a: 1, 'b': 2, "c": 3}
for x in m
print(x)
// prints a, b, c
Passing an extra pattern argument, v
for example, to the for
statement is used to specify a variable to be used as the iterator value.
In this case the variable k
is the key and v
is used to store the value of each map entry.
m = #{a: 1, 'b': 2, "c": 3}
for (k, v) in m
print(k, v)
// prints a 1, b 2, c 3
while
statementThe
while
statement is used to execute a block of code while a condition is true.
The guard is an expression directly following the while
keyword, surrounding parentheses are optional. After that, a block of code or a single expression is specified.
while x < 10 x += 1
Alternative styles of writing a while loop are also possible.
while x < 10 {
x += 1
}
while (x < 10) x += 1
Or both.
while (x < 10) {
x += 1
}
break
statementThe
break
statement is used to break out of the nearest enclosing loop.
while x < 10 {
x += 1
if x == 5 break
}
continue
statementThe
continue
statement is used to continue to the next iteration of the nearest enclosing loop.
while x < 10 {
x += 1
if x % 2 == 0 continue
print(x)
}
The example above prints all odd numbers from 1 to 10. E.g. 1, 3, 5, 7, 9.
return
statementThe
return
statement is used to return a value from a function or code block. The last expression in a block is implicitly returned.
value = {
x = 1
if condition return x // Early return if condition is true
y = 2
x + y // Returned implicitly
}
// value can either be 1 (early) or 3 (implicit)
Other keywords and operators
Other than the operators and keywords listed above, there are a number of other keywords and operators that can be used in mini
.
is
- Used to match values against patterns and bind them to variablesin
- Used to check if a value is in a set of values..
- Used to specify a range of values
is
keywordThe
is
keyword is used to match values against patterns and bind them to variables.
Use is
in an if-else statement to pattern match on values and change the control flow of the program depending on the match result.
if x is number "number"
else if x is string "string"
else if x is boolean "boolean"
else "unknown"
Or more specifically, get the type of the value and bind it to a variable.
if x is number(n) "number: " + n
else if x is string(s) "string: " + s
else if x is boolean(b) "boolean: " + b
else "unknown: " + x
Can be used to handle optional values.
if x is Optional.Some(v) "some: " + v
else "none"
Or match on data types.
if l is [head | tail] "list: " + head + " " + tail
else
in
keywordThe
in
keyword is used to check if a value is in a set of values.
Check if a value is in a list.
if x in [1, 2, 3] "in list"
else "not in list"
Check if a value is in a range. Read more about the ..
operator below.
if x in 1..10 "in range"
else "not in range"
Check if value is a key in a map. If x
would be 2
for example, x
would be in the map and the condition would be true.
if x in #{1: "one", 2: "two", 3: "three"} "in map"
else "not in map"
Check if a value is in a tuple.
if x in (1, 2, 3) "in tuple"
else "not in tuple"
One can also use in
when iterating over a set of values using a for
loop.
for x in [1, 2, 3] print(x)
..
OperatorThe
..
operator is used to specify a range of values including the lower and upper bounds.
for i in 1..10 {
txt = 'i: ' + i
print(txt)
}
The example above prints the numbers 1 to 10.
It is also possible to generate a lazy sequence of values withouth having to store them in memory by excluding the upper bound.
for i in 1.. {
if i > 1000 break
txt = 'i: ' + i
print(txt)
}
Contribute! ๐
Contributions of all kinds are welcome, not only in the form of code but also with regards to the official documentation, debugging help and tickets/issues in the bug tracker, support of other users on the community forum or the official discord and also financially.
If you think something is bad about the mini
language or the article series written about it, please help in any way to make it better instead of complaining about it.
For information about how to go about submitting bug reports or pull requests, please see the projectโs contribution guidelines.
1. Perfecting the language ๐
There is much more to the language than just the syntax, and there are several ways to improve it. Optimizing the interpreter and compiler is one of the most important aspects of the tooling for a language. Do consider contributing to the mini-lang project to make your own improvements.
Do you have your own ideas for improvements? Are they specialized in a certain area or controversial in nature? Please create your own version of the language by forking it on GitHub and sharing your changes!
2. Improve the documentation ๐
The documentation is a great source of information for anyone who wants to learn the language. It is also a great source of information for anyone who wants to contribute to the project.
If you are a regular user of the language, you can help improve the documentation by creating a pull request to the official documentation repository.
3. Expand the ecosystem ๐๏ธ
There are numerous ways of contributing to the mini
language, one of which is to contribute code to the official repository, but there are also many other ways to contribute.
- Consider building projects in
mini
and publish your projects on GitHub. - Expand on the tools and libraries that are already available for
mini
and publish your own, look at the todo list and the third-party tools section.
4. Get involved in the community ๐ซ
The best way to show support and help is to get involved in the community! There are many ways to get involved, start by joining the official discord and ask questions, share your ideas and discuss the language there! It is a great place to learn about the language and to get help with any issues you might have, or to even help out with the development of the language.
5. Improve the article series ๐
If you are a writer and want to contribute to the article series, please see the contributing to the article series section.
Other ways to contribute to the article series include:
- Commenting on the articles
- Writing articles about the series
- Writing articles about the language and the project
Citation
Correctly cite and refer to the
mini
programming language and the article series by using the๐จ๏ธ Cite this repository
button in the right-hand sidebar of this repository.
License
This project is licensed under the MIT license. The source code is available on GitHub. To modify the code, please fork the project and experiment with the language. Expanding on the language is highly encouraged. If you have any suggestions, please open an issue or pull request.