Genlang is a compact experimental programming language implemented as a small Python library. It is designed for teaching language implementation techniques, rapid prototyping of domain specific languages, and for fun. The implementation provides a lexer, parser, AST interpreter and a tiny runtime with dynamic variable dependencies.
This README documents language syntax, examples, and the Python API you can import into other projects.
- Simple, concise syntax designed to be easy to parse and reason about.
- Declarations and assignments use a
$-based variable syntax. - Dynamic expressions, declared once, automatically update when their dependencies change.
- Lightweight function definitions and calls, with expression-based return values.
- Minimal, explicit control flow: conditionals, loops, and input/print primitives.
Genlang is delivered as a single Python module. To experiment with it install via pip:
pip install genlang
Then run:
genlang run <file.gen>
That's it!
No external dependencies are required beyond the Python standard library. Notice that Genlang files has a file format of <.GEN>.
In addition to CLI, it's possible to save your Genlang source into a string and run it from Python:
from genlang import run_genlang
source = """
:$a = 10;
:$b = $.a + 5;
! $.b;
$a = 20;
! $.b;
"""
interp = run_genlang(source)
# `run_genlang` returns the interpreter instance. You can inspect `interp.vars` after execution.
print(interp.vars)This prints the value of b twice, demonstrating dynamic declarations. The declaration :$b = $.a + 5; makes b depend on the dynamic reference $.a so when $a changes, $b is updated automatically.
- Numbers:
123or12.34 - Strings: single or double quoted with backslash escapes
- Identifiers: e.g.
foo,bar_2
- Declare a variable, without initializing:
:$name; - Declare and assign:
:$name = <expr>; - Assign to an already-declared variable:
$name = <expr>; - Evaluate an expression for side effects:
<expr>; - Print:
! <expr>;(prints to stdout) - Read input into a variable:
?$<name>;(reads from provided input orinput()when real_input=True)
Notes:
- Every statement must end with a semicolon
;. - Variable names used in assignment are always prefixed with
$when referenced on the left side. Plain identifiers (without$) are used for local function parameter names and ordinary identifiers in expressions.
- Use
$.nameto mark a dynamic dependency to variablenameinside a declaration expression. If a declaration expression contains any dynamic references, the declared variable becomes "dynamic", and will be kept up to date whenever its dependencies change.
Example:
:$x = 2;
:$y = $.x * 3; // y is dynamic, depends on x
! $.y; // prints 6
$x = 5;
! $.y; // now prints 15, because y was dynamic
- If with optional else block uses
??followed by(condition)and blocks in braces{}. An optional else block follows a comma and another brace enclosed block.
?? ($a > 10) { ! 'big'; } , { ! 'small'; };
- Loop uses
*followed by(condition)and a{}-block body. The loop repeats while the condition is true.
* ($n > 0) { ! $n; $n = $n - 1; };
- Declared with
@name(params) { ... };. Parameters can be either plain identifiers or$-prefixed names when you want to accept variables by name. - A function body may contain declarations and statements. A function returns the last expression statement evaluated in its body (if any).
Example:
@add($a, $b) {
:$s = $a + $b;
s; // expression evaluates to the value returned by the function
};
! @add(3, 4);
- Arithmetic:
+ - * / % - Comparison:
== != < > <= >= - Logical:
&(and),|(or),~(not) - Unary minus supported
- String concatenation supported via
+when either operand is a string
Precedence follows a standard ordering from logical OR down to multiplication and unary operators.
! 'Hello, world!';
:$x = 10;
:$y = $.x * 2;
! $.y;
$x = 7;
! $.y;
?$name;
?? ($name == 'Alice') { ! 'Hi Alice'; } , { ! 'Who are you?'; };
:$n = 5;
* ($n > 0) { ! $n; $n = $n - 1; };
@sum($a, $b) {
:$r = $a + $b;
r;
};
! @sum(10, 20);
You can import the module into Python and run Genlang programs with a few helper functions and classes.
Tokenizes source into Token objects. Useful for debugging or tooling.
Parses tokens into an AST you can inspect for tooling or transformations. Raises ParseError for syntax problems.
Creates an interpreter instance. Important fields and behavior:
interp.varsstores runtime variables by nameinterp.dynamic_exprsstores any declared dynamic expressionsinterp.funcsstores declared functions- Call
interp.eval_program(ast)to run a parsed AST from the parser
Runtime exceptions raised are RuntimeErrorWithPos which include a line and column when available.
Convenience runner that lexes, parses, and executes source. Returns the interpreter instance after execution. Example:
from genlang import run_genlang
source = """
?$name;
! 'You entered: ' + $name;
"""
interp = run_genlang(source, input_values=['42'])- Syntax errors during parsing raise
ParseErrorwith a descriptive message and a line/column position. - Runtime problems raise
RuntimeErrorWithPos, which attempts to include a line/column for easier debugging, e.g. division by zero, undeclared variable, or cycle in dynamic dependencies.
- Dynamic declarations: when a declaration expression contains
$.namereferences, the declared variable is stored as a dynamic expression. When a dependency variable changes, dependent dynamic values are recomputed and changes propagate. A cycle in dependencies will raise a runtime error. - Functions are simple. When a function executes, a snapshot of the caller environment is saved and restored after the call. Functions return the value of the last expression statement if present.
- The interpreter performs minimal type coercion. Numeric operations require numeric operands, except
+supports string concatenation when either operand is a string.
Contributions are welcome. A few ideas:
- Add more builtin functions and types
- Extend the standard library with collections or I/O primitives
- Add static analysis tooling or an LSP server for editor support
- Implement a bytecode compiler or REPL
If you make a change, please add tests and keep the module self-contained.
This project is released under the MIT license. You may copy and adapt the code freely.