About mn
mn is a concatenative, fully-homoiconic, functional, interpreted programming language.
This basically means that:
- It is based on a somewhat obscure and slightly unintuitive programming paradigm, think of Forth, Factor and Joy but with parentheses for an extra Lispy flavor.
- Programs written in mn are actually written using quotations, i.e. lists.
- It comes with map, filter, find, and loads of other functional goodies.
- It is probably slower than the average production-ready programming language.
Why?
mn is min’s little brother. When I started implementing min, I wanted to create a small but practical programming language you could use for shell scripting and perform common tasks. As more feature requests piled in, I noticed it slowly became more and more comprehensive and batteries-included: I slowly swapped small, less-unknown and somewhat quirky libraries used for regular expressions, compression etc. with more complete and well-known ones, added HTTPS support (and OpenSSL), improved runtime checks when creating symbols, enhanced the type system, and so on. While min can now be used on its own to create quite complex programs, it became less minimal than originally intended.
I tried to add compilation variants to reduce the modules to include but that made it more difficult to maintain and still included complex constructs like dictionaries and the full type system, so one day I decided to… fork it! And that’s how mn was born.
Is mn the successor of min? No! As I said, it is min’s little brother, and it has its own (somewhat more minimalist) life. If you want to create a quick script to glue some shell commands together, then mn is definitely the fastest way to do so. If you want to use the concatenative paradigm to create more complex applications, then min comes with a much bigger toolbox.
How?
mn is developed entirely in Nim and started off as a fork of the min programming language. I took the v0.35.0 codebase and started removing stuff, including the only vowel used in the language name. What else was removed you ask? Let’s see… compared to min, mn:
- does not have dictionaries
- does not have modules
- does not have require, include, etc.
- does not support compilation via Nim
- does not have sigils
- does not have an operator symbol, only lambda
- does not have any dependency from third-party code
- does not have type classes or type expressions, except for unions of basic types
- does not have JSON interoperability
- does not have error handling, i.e. a try/catch mechanism
- does not have any built-in support for networking, cryptography, etc.
- does not have a fancy REPL with autocompletion
What does it have then? Well, mn provides:
- exactly 72 symbols, nearly all of which are borrowed from min
- file reading/writing (via the read and write symbols)
- stdin reading (gets) and writing (puts)
- external command execution via run and automatic command expansion for all strings wrapped in square brackets
- string evaluation via eval
- string interpolation via interpolate
- a basic REPL
Who?
mn was created and implemented by Fabio Cevasco.
When?
mn source code repository was created on March 23rd 2021.
Get Started
You can download one of the following pre-built mn binaries:
Building from source
Alternatively, you can build mn from source as follows:
- Download and install Nim.
- Clone the mn repository.
- Navigate to the mn repository local folder.
- Run ./build.sh.
Running the mn REPL
To start the mn REPL, run mn with no arguments. You will be presented with a prompt displaying the path to the current directory:
mn v0.4.0 ::
You can type mn code and press ENTER to evaluate it immediately:
:: 2 2 + 4 ::
The result of each operation will be placed on top of the stack, and it will be available to subsequent operation
:: dup * 16 ::
To exit mn shell, press CTRL+C or type 0 exit and press ENTER.
Executing an mn Program
To execute a mn script, you can:
- Run
mn -e:"... program ..."
to execute a program inline. - Run
mn myfile.mn
to execute a program contained in a file.
mn also supports running programs from standard input, so the following command can also be used (on Unix-like system) to run a program saved in myfile.mn:
$ cat myfile.mn | mn
Learning the mn Language
mn is a stack-based, concatenative programming language that uses postfix notation. If you already know Forth, Factor or Joy, or if you ever used an RPN calculator, then mn will look somewhat familiar to you.
If not, well, here’s how a short mn program looks like:
; This is a comment
(1 2 3 4 5) (dup *) map
#| This is a...
...multiline comment |#
This program returns a list containing the square values of the first five integer numbers:
(1 4 9 16 25)
Let’s see how it works:
- First, a list containing the first five integers is pushed on the stack.
- Then, another list containing two symbols (
dup
and*
) is pushed on the stack. This constitutes a quoted program which, when executed duplicates the first element on the stack — this is done bydup
— and then multiplies — with*
— the two elements together. - Finally, the symbol
map
is pushed on the stack. Map takes a list of elements and a quoted program and applies the program to each element.
Note that:
- There are no variable assignments.
- elements are pushed on the stack one by one.
- Parentheses are used to group one or more elements together so that they are treated as a single element and they are not evaluated immediately.
- Symbols (typically single words, or several words joined by dashes) are used to execute code that performs operations on the whole stack.
Unlike more traditional programming languages, in a concatenative programming language, there is no inherent need for variables or named parameters, as symbols act as stack symbols that consume elements that are placed in order on top of a stack.
Data Types
The following data types are availanle in mn (with the corresponding shorthand symbols used in symbol signatures in brackets):
- null (null)
- null value.
- boolean (bool)
- true or false.
- integer (int)
- A 64-bit integer number like 1, 27, or -15.
- float (flt)
- A 64-bit floating-point number like 3.14 or -56.9876.
- string (str)
- A series of characters wrapped in double quotes: “Hello, World!”.
- quotation (quot)
- A list of elements, which may also contain symbols. Quotations can be used to create heterogenous lists of elements of any data type, and also to create a block of code that will be evaluated later on (quoted program). Example:
(1 2 3 + \*)
- command (cmd)
- A command string wrapped in square brackets that will be immediately executed on the current shell and converted into the command standard output. Example:
[ls -a]
Operators
Every mn program needs operators to:
- Manipulate elements on the stack
- Perform operations on data
- Provide side effects (read/print to standard input/output/files, etc.)
An mn symbol is a single word that is either provided by mn like dup
or defined by the user. User-defined symbols must:
- Start with a letter
- Contain zero or more letters, numbers and/or underscores.
To define a new operator symbol, you can use the lambda symbol. For example, the following symbol defines a quotation that can be used to calculate the square value of a number.
(dup *) (square) lambda
Note that this feels like using let, but the main difference between lambda and let is that lambda
only works on quotations and it doesn’t auto-quote them, so that they are immediately evaluated when the corresponding symbol is pushed on the stack.
Tip
You can use lambda-bind to re-set a previously set lambda.
Definitions
Being a concatenative language, mn does not really need named parameters or variables: symbols just pop elements off the main stack in order, and that’s normally enough. There is however one small problem with the traditional concatenative paradigm; consider the following program for example:
dup
() cons "Compiling in $# mode..." swap interpolate puts pop
() cons "nim -d:$# c test.nim" swap interpolate run
This program takes an string containing either “release” or “development” and attempts to build the file test.nim for it. Sure, it is remarkable that no variables are needed for such a program, but it is not very readable: because no variables are used, it is often necessary to make copies of elements and push them to the end of the stack – that’s what the dup and swap are used for.
The good news is that you can use the let symbol to define new symbols, and symbols can also be set to literals of course.
Consider the following program:
(mode) let
"Compiling in $# mode..." (mode) interpolate puts pop
"nim -d:$# c test.nim" (mode) interpolate run
In this case, the first element on the stack is saved to a symbol called mode, which is then used whenever needed in the rest of the program.
Lexical scoping and binding
mn, like many other programming languages, uses lexical scoping to resolve symbols.
Consider the following program:
4 (a) let
(
a 3 + (a) let
(
a 1 + (a) let
(a dup * (a) let) dequote
) dequote
) dequote
…What is the value of the symbol a
after executing it?
Simple: 4
. Every quotation defines its own scope, and in each scope, a new variable called a
is defined. In the innermost scope containing the quotation (a dup * (a) let)
the value of a
is set to 64
, but this value is not propagated to the outer scopes. Note also that the value of a
in the innermost scope is first retrieved from the outer scope (8).
If we want to change the value of the original a
symbol defined in the outermost scope, we have to use the bind, so that the program becomes the following:
4 (a) let ;First definition of the symbol a
(
a 3 + (a) bind ;The value of a is updated to 7.
(
a 1 + (a) bind ;The value of a is updated to 8
(a dup * (a) bind) dequote ;The value of a is now 64
) dequote
) dequote
Control Flow
mn provides some symbols that can be used for the most common control flow statements. Unlike most programming languages, mn does not differentiate between functions and statements – control flow statements are just ordinary symbols that manipulate the main stack.
Conditionals
The when symbol can be used to implement conditional statements.
For example, consider the following program:
(
"Unknown" (system) let
[uname] (uname) let
(uname "MINGW" indexof -1 !=)
("Windows" (system) bind)
when
(uname "Linux" indexof -1 !=)
("Linux" (system) bind)
when
(uname "Darwin" indexof -1 !=)
("macOS" (system) bind)
when
"The current OS is $#" (system) interpolate puts
) (display-os) lambda
This program defines a symbol display-os
that execute the uname system command to discover the operating system and outputs a message.
Loops
The following symbols provide ways to implement common loops:
For example, consider the following program:
(
(n) let
1 (i) let
1 (f) let
(i n <=)
(
f i * (f) bind
i 1 + (i) bind
) while
f
) (factorial) lambda
This program defines a symbol factorial
that calculates the factorial of an integer iteratively using the symbol while.
Extending mn
mn provides a fairly very basic standard library. If you want to extend it, you basically have the following options:
- Implementing new mn symbols using mn itself
- Embedding mn in your Nim program
Implementing new mn symbols using mn itself
When you just want to create more high-level mn symbol using functionalities that are already available in mn, the easiest way is to create your own reusable mn symbols in separate files.
(dup *) (pow2) lambda
(dup dup * *) (pow3) lambda
(dup dup dup * * *) (pow4) lambda
Save your code to a file (e.g. quickpows.mn) and you can use it in other nim files using the read symbol to read it and then the eval to evaluate the program in the current scope:
"quickpows.mn" read eval
2 pow3 pow2 puts ;prints 64
Embedding mn in your Nim program
If you’d like to use mn as a scripting language within your own program, and maybe extend it by implementing additional symbols, you can use mn as a Nim library.
To do so:
- Download and install Nim
- Import it in your Nim file.
- Implement a new
proc
to define the module.
The following code is adapted from HastySite (which internally uses min) and shows how to define a new hastysite
module containing some symbols (preprocess
, postprocess
, process-rules
, …):
import mn
proc hastysite_module*(i: In, hs1: HastySite) =
var hs = hs1
let def = i.define()
def.symbol("preprocess") do (i: In):
hs.preprocess()
def.symbol("postprocess") do (i: In):
hs.postprocess()
def.symbol("process-rules") do (i: In):
hs.interpret(hs.files.rules)
# ...
def.finalize("hastysite")
Then you need to:
- Instantiate a new mn interpreter using the
newMnInterpreter
proc. - Run the
proc
used to define the module. - Call the
interpret
method to interpret a mn file or string:
proc interpret(hs: HastySite, file: string) =
var i = newMnInterpreter(file, file.parentDir)
i.hastysite_module(hs)
i.interpret(newFileStream(file, fmRead))
Tip
For more information on how to create new symbols with Nim, have a look in the lang.nim file in the mn repository, which contains all the symbols included in mn.
Reference
Notation
The following notation is used in the signature of all mn symbols:
Types and Values
- ∅
- No value.
- null
- null value
- a
- A value of any type.
- bool
- A boolean value
- int
- An integer value.
- flt
- A float value.
- num
- A numeric (integer or float) value.
- str
- A string value.
- 'sym
- A string-like value (string or quoted symbol).
- quot
- A quotation (also expressed as parenthesis enclosing other values).
Suffixes
The following suffixes can be placed at the end of a value or type to indicate ordering or quantities.
- 1
- The first value of the specified type.
- 2
- The second value of the specified type.
- 3
- The third value of the specified type.
- 4
- The fourth value of the specified type.
- ?
- Zero or one.
- *
- Zero or more.
- +
- One or more
Symbols
a1 a2 ⇒ bool
Returns true if a1 is greater than a2, false otherwise.
Note
Only comparisons among two numbers or two strings are supported.
a1 a2 ⇒ bool
Returns true if a1 is greater than or equal to a2, false otherwise.
Note
Only comparisons among two numbers or two strings are supported.
a1 a2 ⇒ bool
Returns true if a1 is smaller than a2, false otherwise.
Note
Only comparisons among two numbers or two strings are supported.
a1 a2 ⇒ bool
Returns true if a1 is smaller than or equal to a2, false otherwise.
Note
Only comparisons among two numbers or two strings are supported.
a1 a2 ⇒ bool
Returns true if a1 is equal to a2, false otherwise.
a1 a2 ⇒ bool
Returns true if a1 is not equal to a2, false otherwise.
quot ⇒ bool
Assuming that quot is a quotation of quotations each evaluating to a boolean value, it pushes true on the stack if they all evaluate to true, false otherwise.
quot ⇒ bool
Assuming that quot is a quotation of quotations each evaluating to a boolean value, it pushes true on the stack if any evaluates to true, false otherwise.
bool1 ⇒ bool2
Negates bool1.
num1 num2 ⇒ num3
Sums num1 and num2.
num1 num2 ⇒ num3
Subtracts num2 from num1.
∅ ⇒ num
Returns negative infinity.
num1 num2 ⇒ num3
Multiplies num1 by num2.
num1 num2 ⇒ num3
Divides num1 by num2.
∅ ⇒ num
Returns infinity.
∅ ⇒ nan
Returns NaN (not a number).
str1 str2 ⇒ ∅
Appends str1 to the end of file str2.
quot ⇒ (a*)
Returns a new quotation obtained by evaluating each element of quot in a separate stack.
∅ ⇒ quot
Returns a list of all arguments passed to the current program.
a 'sym ⇒ ∅
Binds the specified value (auto-quoted) to an existing symbol 'sym.
quot1 quot2 ⇒ quot3
Concatenates quot1 with quot2.
a1 (a*) ⇒ (a1 a*)
Prepends a1 to the quotation on top of the stack.
∅ ⇒ str
Returns the host CPU. It can be one of the following strings i386, alpha, powerpc, powerpc64, powerpc64el, sparc, amd64, mips, mipsel, arm, arm64.
a1 (a2) ⇒ a* a1
Removes the first and second element from the stack, dequotes the first element, and restores the second element.
a1 ⇒ a1 a1
Duplicates the first element on the stack.
quot ⇒ a*
Pushes the contents of quotation quot on the stack.
Each element is pushed on the stack one by one. If any error occurs, quot is restored on the stack.
str ⇒ a*
Parses and interprets str.
int ⇒ ∅
Exits the program or shell with int as return code.
quot1 ⇒ quot2
Validates the first n elements of the stack against the type descriptions specified in quot1 (n is quot1’s length) and if all the elements are valid returns them wrapped in quot2 (in reverse order).
Tip
You can specify two or more matching types by separating combined together in a type union, e.g.: string|quot
quot1 quot2 ⇒ quot3
Returns a new quotation quot3 containing all elements of quot1 that satisfy predicate quot2.
quot1 quot2 ⇒ a*
Applies the quotation quot2 to each element of quot1.
quot int ⇒ a
Returns the nth element of quot (zero-based).
∅ ⇒ str
Reads a line from STDIN and places it on top of the stack as a string.
∅ ⇒ (a*)
Puts a quotation containing the contents of the stack on the stack.
str1 str2 ⇒ int
If str2 is contained in str1, returns the index of the first match or -1 if no match is found.
str quot ⇒ str
Substitutes the placeholders included in str with the values in quot.
Notes
- If quot contains symbols or quotations, they are interpreted.
- You can use the
$#
placeholder to indicate the next placeholder that has not been already referenced in the string. - You can use named placeholders like
$pwd
, but in this case quot must contain a quotation containing both the placeholder names (odd items) and the values (even items).
quot 'sym ⇒ str
Joins the elements of quot using separator 'sym, producing str.
quot 'sym ⇒ ∅
Defines a new symbol 'sym, containing the specified quotation quot. Unlike with let
, in this case quot will not be quoted, so its values will be pushed on the stack when the symbol 'sym is pushed on the stack.
Essentially, this symbol allows you to define an symbol without any validation of constraints and bind it to a symbol.
quot 'sym ⇒ ∅
Binds the specified quotation (unquoted) to an existing symbol 'sym.
'sym ⇒ int
Returns the length of 'sym.
a 'sym ⇒ ∅
Defines a new symbol 'sym, containing the specified value.
quot1 quot2 ⇒ quot3
Returns a new quotation quot3 obtained by applying quot2 to each element of quot1.
∅ ⇒ str
Returns the host operating system. It can be one of the following strings: windows, macosx, linux, netbsd, freebsd, openbsd, solaris, aix, standalone.
a ⇒ ∅
Removes the first element from the stack.
a ⇒ a
Prints a to STDOUT.
a ⇒ a
Prints a and a new line to STDOUT.
a ⇒ (a)
Wraps a in a quotation.
str ⇒ (sym)
Creates a symbol with the value of str and wraps it in a quotation.
str ⇒ (sym)
Creates a command with the value of str and wraps it in a quotation.
str ⇒ str
Reads the file str and puts its contents on the top of the stack as a string.
str1 str2 str3 ⇒ str4
Returns a copy of str1 containing all occurrences of str2 replaced by str3
'sym ⇒ int
Executes the external command 'sym in the current directory and pushes its return code on the stack.
quot ⇒ a*
Substitute the existing stack with the contents of quot.
a1 a2 ⇒ a2 a1
Swaps the first two elements on the stack.
quot ⇒ int
Returns the length of quot.
quot1 int1 int2 ⇒ quot2
Creates a new quotation quot2 obtaining by selecting all elements of quot1 between indexes int1 and int2.
'sym1 'sym2 ⇒ quot
Splits 'sym1 using separator 'sym2 and returns the resulting strings within the quotation quot.
'sym ⇒ str
Returns str, which is set to 'sym with leading and trailing spaces removed.
str1 int1 int2 ⇒ str2
Returns a substring str2 obtained by retriving int2 characters starting from index int1 within str1.
∅ ⇒ (str*)
Returns a list of all symbols defined in the global scope.
∅ ⇒ int
Returns the current time as Unix timestamp.
a ⇒ str
Puts the data type of a on the stack.
quot1 quot2 ⇒ a*
If quot1 evaluates to true then evaluates quot2.
'sym ⇒ str
Returns the full path to the directory containing executable 'sym, or an empty string if the executable is not found in $PATH.
quot1 quot2 ⇒ a*
Executes quot2 while quot1 evaluates to true.
str1 str2 ⇒ ∅
Writes str1 to the file str2, erasing all its contents first.