PumaLang is a small interpreted programming language implemented in Python. It is expression-oriented, dynamically typed, and designed for learning language implementation fundamentals.
Based on: py-myopl-code
- Simple syntax with familiar operators
- Variables, strings, lists, dictionaries, and user-defined functions
- Control flow:
if,for,while,return,break,continue - Built-in I/O and list manipulation functions
- Modular interpreter package under
pumalang/ - CLI shell with basic / debug modes
- Legacy single-file implementation under
onefile/
- Python 3.10+
Start the interactive shell from the project root:
python -m pumalang.shellRun a .pumalang script:
python -m pumalang.shell examples/hello_world.pumalangRun with lexer/parser diagnostics:
python -m pumalang.shell -d examples/test_arithmetic.pumalangAlternative entry point:
python pumalang/shell.py examples/hello_world.pumalang| Command | Description |
|---|---|
python -m pumalang.shell |
Open REPL (basic mode) |
python -m pumalang.shell <file> |
Execute a .pumalang file |
python -m pumalang.shell -d <file> |
Execute with debug diagnostics |
python -m pumalang.shell -v |
Print version and exit |
| Mode | Flag | Output |
|---|---|---|
| Basic (default) | — | Program print output and errors only |
| Debug | -d / --debug |
Also shows lexer/parser diagnostics |
The shell does not echo interpreter return values. Use print() for visible output.
python onefile/shell.py
python onefile/shell.py examples/hello_world.pumalangThe onefile/ interpreter always prints lexical/syntax debug messages.
Modular package (recommended):
from pumalang import run
value, error = run("<stdin>", 'print("Hello World")')
if error:
print(error.as_string())With debug diagnostics:
from pumalang import run
value, error = run("<stdin>", "1 + 2", debug=True)Legacy single-file import:
import sys
from pathlib import Path
sys.path.insert(0, str(Path("onefile").resolve()))
import pumalang
value, error = pumalang.run("<stdin>", 'print("Hello World")')PumaLang source files use the .pumalang extension. Programs are sequences of statements separated by ; or newlines.
var x = 1
var y = 2
print(x + y)
Comments start with #.
var and or not if then elif else for to step while func end return continue break global
| Type | Notes |
|---|---|
| Number | Integer or float |
| String | Double-quoted literals |
| List | [1, 2, 3] |
| Dict | {"key": value} — keys must be strings or numbers |
| Function | User-defined or built-in |
| NULL | NULL |
| Boolean | TRUE, FALSE, True, False |
Arithmetic: + - * / ^
Comparison: == != < > <= >=
Logical: and or not
Expression form:
if x > 0 then 1 elif x == 0 then 0 else -1
Block form:
if x > 0 then
print("positive")
else
print("not positive")
end
for i = 0 to 5 then print(i)
for i = 10 to 0 step -2 then print(i)
var i = 0
while i < 3 then var i = i + 1
Arrow (single-expression) form:
func add(a, b) -> a + b
print(add(2, 3))
Block form with return:
func sum_to(n)
var total = 0
for i = 1 to n + 1 then var total = total + i
return total
end
print(sum_to(5))
By default, var inside a function creates a local binding. Use global to read and write a name in the program scope instead:
var counter = 0
func bump()
global counter
var counter = counter + 1
end
bump()
print(counter)
Multiple names: global x, y, z
var l = [3, 1, 2]
append(l, 5)
print(l)
print(pop(l, 1))
sort(l, 0)
print(l)
print(len(l))
Index with / or []:
var seq = [10, 20, 30]
print(seq / 1)
print(seq[1])
var d = {"a": 1, "b": 2}
print(d["a"])
print(d["b"])
Empty dict: var empty = {}
| Category | Functions |
|---|---|
| I/O | print, print_ret, input, input_int, clear / cls |
| Type checks | is_number, is_string, is_list, is_function |
| Lists | append, pop, extend, len, sort |
| Scripts | run(filename) |
Aliases: is_sum, is_str, is_fun, exetend.
From the REPL:
run("examples/test_fibonacci.pumalang")
Or use a launcher script:
python -m pumalang.shell examples/run_fibonacci.pumalang| File | Topic |
|---|---|
hello_world.pumalang |
Hello World |
test_arithmetic.pumalang |
Arithmetic |
test_if.pumalang |
Conditionals |
test_loops.pumalang |
Loops |
test_function.pumalang |
Functions |
test_global.pumalang |
Global variables |
test_lists.pumalang |
Lists |
test_dict.pumalang |
Dictionaries |
test_builtins.pumalang |
Built-ins |
test_comments_newlines.pumalang |
Comments & newlines |
test_fibonacci.pumalang |
Recursive Fibonacci |
test_fibonacci_iterative.pumalang |
Iterative Fibonacci |
run_fibonacci.pumalang |
Script launcher |
PumaLang reports lexical, syntax, and runtime errors with source locations and caret arrows pointing at the offending code. Runtime errors include a traceback through function call contexts.
PumaLang/
├── pumalang/ # Modular interpreter (recommended)
│ ├── __init__.py # Public package API
│ ├── shell.py # CLI / REPL entry point
│ ├── run.py # Compile-and-run driver
│ ├── lexer.py # Tokenizer
│ ├── parser.py # Recursive descent parser
│ ├── interpreter.py # AST visitor / evaluator
│ ├── nodes.py # AST node definitions
│ ├── tokens.py # Tokens and Position
│ ├── values.py # Runtime value types
│ ├── builtins.py # Built-in functions
│ ├── globals.py # Global symbol table
│ ├── errors.py # Error classes
│ ├── strings_with_arrows.py # Error pointer rendering
│ └── rtresult.py # Control-flow result wrapper
├── onefile/ # Legacy single-file interpreter
│ ├── pumalang.py
│ ├── strings_with_arrows.py
│ └── shell.py
├── examples/ # Sample .pumalang programs
├── grammar.txt
├── LICENSE.txt
└── readme.md
- Source files use the
.pumalangextension - Modular shell defaults to basic mode; pass
-dfor debug output - Return values are not printed automatically — use
print() - Each script run uses a fresh program scope layered on the global builtin table
- REPL sessions keep one program scope for the whole session
- Function bodies use local scopes; locals do not overwrite builtins in the global table
- Implementation prioritizes educational clarity over performance