About mn

mn is a concatenative, fully-homoiconic, functional, interpreted programming language.

This basically means that:

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:

What does it have then? Well, mn provides:

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:

  1. Download and install Nim.
  2. Clone the mn repository.
  3. Navigate to the mn repository local folder.
  4. 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:

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:

  1. First, a list containing the first five integers is pushed on the stack.
  2. 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 by dup— and then multiplies — with *— the two elements together.
  3. 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:

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:

An mn symbol is a single word that is either provided by mn like dup or defined by the user. User-defined symbols must:

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

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:

  1. Download and install Nim
  2. Import it in your Nim file.
  3. 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:

  1. Instantiate a new mn interpreter using the newMnInterpreter proc.
  2. Run the proc used to define the module.
  3. 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.

-inf

num

Returns negative infinity.

*

num1 num2 num3

Multiplies num1 by num2.

/

num1 num2 num3

Divides num1 by num2.

+inf

num

Returns infinity.

nan

nan

Returns NaN (not a number).

append

str1 str2

Appends str1 to the end of file str2.

apply

quot (a*)

Returns a new quotation obtained by evaluating each element of quot in a separate stack.

args

quot

Returns a list of all arguments passed to the current program.

bind

a 'sym

Binds the specified value (auto-quoted) to an existing symbol 'sym.

concat

quot1 quot2 quot3

Concatenates quot1 with quot2.

cons

a1 (a*) (a1 a*)

Prepends a1 to the quotation on top of the stack.

cpu

str

Returns the host CPU. It can be one of the following strings i386, alpha, powerpc, powerpc64, powerpc64el, sparc, amd64, mips, mipsel, arm, arm64.

dip

a1 (a2) a* a1

Removes the first and second element from the stack, dequotes the first element, and restores the second element.

dup

a1 a1 a1

Duplicates the first element on the stack.

dequote

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.

eval

str a*

Parses and interprets str.

exit

int

Exits the program or shell with int as return code.

expect

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

filter

quot1 quot2 quot3

Returns a new quotation quot3 containing all elements of quot1 that satisfy predicate quot2.

foreach

quot1 quot2 a*

Applies the quotation quot2 to each element of quot1.

get

quot int a

Returns the nth element of quot (zero-based).

gets

str

Reads a line from STDIN and places it on top of the stack as a string.

getstack

(a*)

Puts a quotation containing the contents of the stack on the stack.

indexof

str1 str2 int

If str2 is contained in str1, returns the index of the first match or -1 if no match is found.

interpolate

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).

join

quot 'sym str

Joins the elements of quot using separator 'sym, producing str.

lambda

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.

lambdabind

quot 'sym

Binds the specified quotation (unquoted) to an existing symbol 'sym.

length

'sym int

Returns the length of 'sym.

let

a 'sym

Defines a new symbol 'sym, containing the specified value.

map

quot1 quot2 quot3

Returns a new quotation quot3 obtained by applying quot2 to each element of quot1.

os

str

Returns the host operating system. It can be one of the following strings: windows, macosx, linux, netbsd, freebsd, openbsd, solaris, aix, standalone.

pop

a

Removes the first element from the stack.

print

a a

Prints a to STDOUT.

puts

a a

Prints a and a new line to STDOUT.

quote

a (a)

Wraps a in a quotation.

quotesym

str (sym)

Creates a symbol with the value of str and wraps it in a quotation.

quotecmd

str (sym)

Creates a command with the value of str and wraps it in a quotation.

read

str str

Reads the file str and puts its contents on the top of the stack as a string.

replace

str1 str2 str3 str4

Returns a copy of str1 containing all occurrences of str2 replaced by str3

run

'sym int

Executes the external command 'sym in the current directory and pushes its return code on the stack.

setstack

quot a*

Substitute the existing stack with the contents of quot.

swap

a1 a2 a2 a1

Swaps the first two elements on the stack.

size

quot int

Returns the length of quot.

slice

quot1 int1 int2 quot2

Creates a new quotation quot2 obtaining by selecting all elements of quot1 between indexes int1 and int2.

split

'sym1 'sym2 quot

Splits 'sym1 using separator 'sym2 and returns the resulting strings within the quotation quot.

strip

'sym str

Returns str, which is set to 'sym with leading and trailing spaces removed.

substr

str1 int1 int2 str2

Returns a substring str2 obtained by retriving int2 characters starting from index int1 within str1.

symbols

(str*)

Returns a list of all symbols defined in the global scope.

timestamp

int

Returns the current time as Unix timestamp.

type

a str

Puts the data type of a on the stack.

when

quot1 quot2 a*

If quot1 evaluates to true then evaluates quot2.

which

'sym str

Returns the full path to the directory containing executable 'sym, or an empty string if the executable is not found in $PATH.

while

quot1 quot2 a*

Executes quot2 while quot1 evaluates to true.

write

str1 str2

Writes str1 to the file str2, erasing all its contents first.