MyLittleLang is a small typed language that compiles to standard Brainfuck with wrapping 8-bit cells. Its compiler performs lexical analysis, parsing, semantic analysis, feature analysis, tape layout, Brainfuck generation, and transitive runtime linking.
The canonical grammar is
The BNF for the language. This document describes
the language semantics and the supported compiler interfaces.
MyLittleLang source
→ lexer
→ parser and AST
→ semantic and type analysis
→ feature analysis
→ tape layout and Brainfuck generation
→ minimal runtime dependency closure
→ Brainfuck
Declarations are collected globally before code generation, including
declarations written inside structured blocks. Duplicate declarations,
unknown variables, invalid indexes, incompatible types, unknown labels, and
invalid break statements are compile-time errors.
The generated program expects a Brainfuck implementation with wrapping 8-bit cells, byte-oriented input and output, and sufficient tape space. Turing completeness additionally assumes the conventional theoretically unbounded tape model.
_ and may subsequently contain letters,
digits, or _.;.;; begins a line comment. # remains accepted for compatibility.;;; is one statement delimiter followed by a line comment.| Type | Runtime representation | Semantics |
|---|---|---|
number |
one cell | unsigned 8-bit value, 0 through 255 |
integer |
eight cells | signed 64-bit two's complement |
long |
eight cells | distinct signed 64-bit compatibility type |
float |
four cells | IEEE-754 binary32 bit pattern |
double |
eight cells | IEEE-754 binary64 bit pattern |
string |
64 cells by default | NUL-terminated byte string |
Integer and number arithmetic wraps to the destination width. Integer division
truncates toward zero. Division by zero currently produces zero for
number, integer, and long.
Mixed arithmetic follows this promotion order:
integer → number → float → double
long is deliberately distinct and is not implicitly mixed with this chain.
Narrowing conversions are rejected.
Scalar declarations use dcl, followed by a type and identifier:
dcl number counter;
dcl integer total;
dcl long distance;
dcl float ratio;
dcl double precise;
dcl string name;
Assignments require a compatible expression:
counter = 10;
total = -125;
name = "Ada";
string uses a 64-byte buffer by default. The legacy declaration
dcl string[20] text; declares a scalar string with a 20-byte capacity,
including its terminating NUL.
Arrays are structural types. Empty brackets belong to the type, while fixed positive dimensions follow the identifier:
dcl string[] names[3];
dcl integer[] values[10];
dcl float[] samples[4];
dcl double[][] matrix[2][5];
Arrays use row-major inline layout. Every reference must currently contain one compile-time integer index for each dimension:
values[0] = 42;
matrix[1][3] = 0x1.8p+2;
Dynamic index expressions, partial array references, whole-array assignment,
and dynamically resized typed arrays are not implemented. The legacy numeric
form dcl integer[4] values; remains accepted as a one-dimensional array.
Operators are evaluated in this precedence order, from highest to lowest:
^, right-associative* and division /+ and subtraction -Comparisons are ==, !=, <, <=, >, and >=. They produce a
number containing 0 or 1. Zero is false and every nonzero numeric value
is true.
Power is right-associative:
a ^ b ^ c means a ^ (b ^ c)
The five arithmetic operators are available for number, integer, and
long. Complete floating constant expressions are evaluated by the compiler's
IEEE reference model.
float is IEEE-754 binary32 and double is binary64. Supported source forms
include decimal fractions, decimal e/E exponents, and hexadecimal
significands with a mandatory binary p/P exponent:
dcl float small;
dcl double exact;
small = 1.25f;
exact = 0x1.921fb54442d18p+1;
The suffix f or F selects binary32. No suffix selects binary64. A suffix
l or L is recognized but rejected because long-double representation is
platform-dependent.
Literal conversion uses an exact mathematical value followed by direct
rounding to binary32 or binary64 with round-to-nearest, ties-to-even. The
reference model covers signed zero, normal and subnormal values, infinity,
NaN, overflow, underflow, comparisons, and + - * / ^.
Variable-based Float32/Float64 arithmetic, comparisons, input/output
conversion, and their Brainfuck software-FPU emitters are not yet complete.
Such uses produce an explicit compiler error rather than approximate code.
General noninteger reference pow currently uses the host libm; correct
rounding is therefore not guaranteed for every possible halfway case.
Strings are NUL-terminated byte sequences. Source characters are encoded as UTF-8. Operations stop at the first NUL byte.
.concat constructs a checked string expression:
dcl string first;
dcl string result;
first = "Ada";
result = first.concat(" Lovelace");
The compiler rejects a concatenation when the destination capacity cannot hold the proven maximum result plus its terminating NUL.
.toNumeric parses a decimal string in the destination's integer type:
dcl integer value;
value = "-9987".toNumeric;
Invalid text or overflow produces zero.
in currently accepts a string destination. It reads bytes through LF,
removes the LF, appends NUL, and drains excess input when the buffer is full:
dcl string line;
in line;
out accepts an expression. Strings are written through their first NUL;
integer types are formatted as human-readable decimal text:
out line;
out 40 + 2;
Floating-point input and formatted output remain part of the pending Brainfuck software-FPU work.
while condition do;
statements
end;
The condition is evaluated before the first iteration and again after every completed iteration.
if condition then;
statements
else;
statements
end;
The else branch is optional.
break; exits the innermost lexically enclosing while or if block. A
break in an if nested inside a loop therefore exits the if, not the
surrounding loop. Using break outside both constructs is an error.
Top-level labels and jumps use these forms:
start:
label alternative:
goto start;
Labels and goto are currently restricted to the top level, and a goto
must terminate its basic block. When a program contains goto, the compiler
emits a program-counter dispatcher. Programs without goto receive no
dispatcher runtime.
The statement below embeds Brainfuck commands directly:
brainfuck ">,[>,]<[.<]";
Only >, <, +, -, ., ,, [, ], and whitespace are accepted.
Whitespace is discarded and brackets are validated.
When normal compiled statements follow a direct block, every loop in that block must restore its starting pointer position, allowing the compiler to calculate a safe net displacement. A pointer-dynamic block must be the final top-level statement and cannot be combined with the goto dispatcher.
For every Brainfuck program P, this terminal MyLittleLang program:
brainfuck "P";
compiles byte-for-byte to P. Brainfuck with an unbounded tape is Turing
complete; consequently the full MyLittleLang language is Turing complete under
the same model. The typed subset without direct tape access remains
finite-state.
The compiler analyzes the typed AST before generation and records only the features actually used. Runtime components declare their dependencies, and the linker computes a deduplicated transitive closure.
Examples of features include number.div, integer.pow, long.mul,
float32.add, array.address, io.string.input, goto.dispatch, and
tape.direct. Unused Float, string, array, output, or goto components are not
linked automatically.
The Python API compile_detailed() exposes the selected feature set and linked
component names for verification.
Compile one file and write Brainfuck to standard output:
python3 src/python/mini_compiler.py program.mll
Write to a selected file. .bf is added when the path has no extension:
python3 src/python/mini_compiler.py program.mll -o build/program
Compile every *.mll file below a directory while preserving its relative
subdirectories:
python3 src/python/mini_compiler.py -d examples -o build/examples
Use -v for compiler phases and -h for help:
python3 src/python/mini_compiler.py -v program.mll -o program.bf
python3 src/python/mini_compiler.py -h
mkdir -p build/java
javac -d build/java \
src/main/java/io/github/hglabplh_tech/mini_bf_comp/MiniCompiler.java
java -cp build/java \
io.github.hglabplh_tech.mini_bf_comp.MiniCompiler \
program.mll -o program.bf
Compile the Java core first, then:
mkdir -p build/kotlin
kotlinc \
src/main/kotlin/io/github/hglabplh_tech/mini_bf_comp/MiniCompilerKotlin.kt \
-classpath build/java -d build/kotlin
kotlin -classpath build/java:build/kotlin \
io.github.hglabplh_tech.mini_bf_comp.MiniCompilerKotlin \
program.mll -o program.bf
Compile the Java core first, then:
scala run --server=false --classpath build/java \
--main-class io.github.hglabplh_tech.mini_bf_comp.MiniCompilerScala \
src/main/scala/io/github/hglabplh_tech/mini_bf_comp/MiniCompilerScala.scala \
-- program.mll -o program.bf
Compile the Java core first, then:
clojure -Sdeps '{:paths ["src/main/clojure" "build/java"]}' \
-M -m io.github.hglabplh-tech.mini-bf-comp.mini-compiler-clojure \
program.mll -o program.bf
All five command-line entry points use the canonical Python compiler and share the same options and generated output.
When src/python is on PYTHONPATH, source text can be compiled directly:
from mini_compiler import compile_source, compile_detailed
brainfuck = compile_source(source)
details = compile_detailed(source)
print(details.features)
print(details.runtime_components)
The reference IEEE functions are in src/python/ieee754.py. They are intended
for compile-time constants and tests, not as a replacement for generated
Brainfuck runtime behavior.
The CLI returns zero after successful compilation and nonzero after an error.
Diagnostics are written to standard error. With -v, phase messages also go
to standard error, so standard output remains valid Brainfuck.
Common errors include: