Educational v1 reference
Educational v1 reference
Mini is a small, dynamically typed, expression-based interpreter for learning language implementation. This page describes the handwritten interpreter, which implements a subset of the intended language design.
Use Python 3.10 or later. No third-party Python packages are required.
git clone https://github.com/mini-lang/mini-lang
cd mini-lang
python mini.py examples/hello_world.m
python mini.py repl
python test.py
The CLI loads src/parser.py and src/lexer.py, which re-export src/manual/.
src/evaluator.py executes the resulting AST. src/grammar/mini.lark is an
experimental grammar draft, not the active parser. In particular, its colon
bodies, newline rules and pattern syntax do not describe v1.
See the capability matrix
and implementation tutorial. Every mini code fence below runs in
a fresh environment in the interpreter’s documentation tests; // => gives the
exact printed output.
Values and expressions
Values include numbers, booleans, strings, lists, tuples, maps and unit ().
Operators include + - * / % ^, comparisons, and short-circuit and / or
(also && / ||). Negation is not or !; single & and | are unsupported.
(x) groups an expression; (x, y) is a tuple. A one-element tuple literal is
not supported. Unicode letters are allowed in identifiers; emoji belong in strings.
Whitespace separates expressions. Newlines are not statement terminators;
semicolons are unsupported. Keep calls and indexes adjacent to their receiver:
f(x), items[0]. Comments use // or /* ... */.
π = 3.14
print(π * 2, not false, "hello 🌍")
print(false and missing)
// => 6.28 true hello 🌍
// => false
Variables, blocks and functions
= creates a local name if no enclosing binding exists; otherwise it updates
the nearest existing binding. Identifier += is supported for numbers and
strings. Other compound assignments are deferred. Underscore-prefixed names
are ordinary variables, not discards.
Blocks return their last expression, or () if empty. New block-local names
do not leak. Functions capture their defining environment, and parameters always
bind locally, shadowing outer names. Functions may use an expression or block body.
x = 10
{ x += 2 local = 99 }
add(x, y) = x + y
square = (x) => x * x
print(x, add(2, 3), square(4))
// => 12 5 16
Collections and built-ins
Lists concatenate with +; tuples add element-wise; maps merge with +.
Indexing supports lists, tuples and maps, including negative list/tuple indexes.
Lists and maps support assignment through computed indexes. Map identifiers are
literal string keys. Integer and boolean literal keys use their string forms.
Slices require explicit start and end; the end is excluded and the step cannot be zero.
Use free functions such as list_append, list_size, list_map, list_filter,
list_reduce, map_size and map_contains. Collection methods such as
items.append(value) are not implemented. print, input, typeof and
assert(condition, message) are also available.
items = [1, 2, 3]
for i in 0..3 { items[i] = items[i] * 2 }
list_append(items, 8)
print(items, items[1:3])
m = #{name: "Mini"}
key = "name"
m[key] = "Ada"
print(m.name, map_size(m))
// => [2, 4, 6, 8] [4, 6]
// => Ada 1
The conversion functions are str, number, int and bool, not string.
number and int return () when string conversion fails. The existing bool
conversion recognizes the text "true" case-insensitively; it is not general
truthiness. Conditions themselves must be booleans.
print(str(42), number("1.5"), int("12"), bool("true"), bool(1))
print(number("invalid"))
// => 42 1.5 12 true false
// => ()
The larger host-oriented standard library is experimental and outside the v1 example guarantee.
Control flow structures
if condition { ... } else if condition { ... } else { ... } returns the chosen
body’s value. Without a matching branch or else, it returns ().
Use braces for clear body boundaries; v1 also retains single-expression bodies
without colons.
while condition { ... } checks a boolean before every iteration.
for name in values { ... } evaluates the iterable once and visits list/tuple
elements, string characters or map keys in insertion order. Unit () iterates
as an empty tuple. Each iteration has a fresh scope; the for name shadows an
outer name and does not leak. Loops return ().
n = 0
while n < 4 {
n += 1
if n == 2 { continue }
print(n)
}
for key in #{a: 1, b: 2} { print(key) }
// => 1
// => 3
// => 4
// => a
// => b
break exits the nearest loop; continue skips to its next iteration.
Neither may cross a function boundary. return expression exits the current
function through nested blocks and loops. Use return () for unit. Return from
a top-level block, and loop control outside a loop body, are errors even when
that code would never run.
Ranges
start..end produces an eager, ascending, end-exclusive integer list.
Both bounds are required. This is not the older proposed inclusive lazy range.
print(1..4)
print(4..1)
// => [1, 2, 3]
// => []
Errors and deferred features
Lexical, syntax and runtime errors include the filename and one-based line and
column. File execution writes errors to stderr and exits nonzero. The REPL
reports an error and accepts the next input; EOF exits it. --debug includes
Python tracebacks for interpreter development.
Classes, enums, pattern matching (match, is, destructuring), sets, modules,
membership in outside a for header, open-ended ranges and omitted slice bounds
are deferred. The grammar draft’s none is unsupported; use ().
Consult the TODO list
before extending the language.