A simple, educational programming language built from scratch in Python!
TinyScript is a beginner-friendly language designed to teach you how programming languages work. It includes a full compiler pipeline: lexer, parser, optimizer, and interpreter.
- I wanted to help other kids like me, learn how to make their own programming language.
- Anyone can fork or remake it but give credits please!
- I also want you to help me develop it, give ideas to give it more possibilities and maybe turn it into a real programming languague
- Please report bugs from recent updates!
- Variables - Store and use values
- Math Operations -
+,-,*,/ - Comparisons -
==,!=,<,>,<=,>= - Conditionals -
if/elsestatements - Loops -
whileloops - Functions - Define and call your own functions
- Optimization - Automatic code optimization before execution
- Interactive REPL - Test code snippets interactively
- Clearer diagnostics - Syntax errors with line and column where possible; runtime errors name the problem (
undefined variable, division by zero, bad operand types,returnoutside a function) - Script-friendly CLI - Running a file prints errors to stderr, uses exit code
1on failure, and reads sources as UTF-8 - Auto-update system -
tinyscript.pycan check a lightweight update server and pull down the newest release automatically, with SHA-256 verification before anything touches disk
No installation needed! Just Python 3.6+
# Clone or download the files
# Run any TinyScript program:
python tinyscript.py example.tsprint("Hello, World!")
x = 10
y = 20
z = x + y * 2
print(z) # Output: 50
age = 18
if age >= 18 {
print("You are an adult")
} else {
print("You are a minor")
}
counter = 0
while counter < 5 {
print(counter)
counter = counter + 1
}
func add(a, b) {
return a + b
}
result = add(5, 3)
print(result) # Output: 8
func factorial(n) {
if n <= 1 {
return 1
} else {
return n * factorial(n - 1)
}
}
print(factorial(5)) # Output: 120
TinyScript is built with these main components:
Breaks source code into tokens (like words in a sentence). Strips a leading UTF-8 BOM if present, reports unterminated strings and bad escapes clearly, and gives clearer errors for unknown characters.
"x = 10" β [IDENTIFIER(x), ASSIGN(=), NUMBER(10)]
Builds an Abstract Syntax Tree (AST) from tokens. Allows newlines in more places (e.g. after =, inside parentheses, and between operators in expressions), accepts optional semicolons, rejects duplicate function parameter names, and includes column information in many parse errors.
Tokens β AST (tree structure representing your code)
Makes code run faster by:
- Constant folding:
2 + 3becomes5at compile time - Dead code elimination: Removes code that never runs
- Algebraic simplification:
x + 0becomesx
Constant folding uses explicit exception handling (no silent βcatch everythingβ).
Executes the optimized AST. Raises structured TinyScriptRuntimeError (see errors.py) for undefined names, bad types in operations, division by zero, invalid calls, and return outside a function.
Shared TinyScriptRuntimeError type with optional line/column so messages are easier to read.
Wires everything together. When you run a file, program print output is captured so the OUTPUT banner only appears after a successful run; failures go to stderr without a misleading empty output block. Also contains the auto-update client (see below).
python tinyscript.py example.tspython tinyscript.py --replThen type code interactively:
>>> x = 10
>>> print(x * 2)
20
Show tokens:
python tinyscript.py example.ts --show-tokensShow AST:
python tinyscript.py example.ts --show-astDisable optimization:
python tinyscript.py example.ts --no-optimize- Success when running a file: process exits with
0. - Syntax error, runtime error, file not found, or I/O error: exits with
1. - Error messages are written to stderr; normal program output from
print()goes to stdout.
TinyScript ships with a small updater built into tinyscript.py. It talks to a tiny update server (a Cloudflare Worker) that only ever tells the client which version exists per channel and where to download it β the update server itself never hosts the files. The actual release ZIPs live on GitHub Releases.
# Check for updates and apply them before running (or entering the REPL)
python tinyscript.py --auto-update
# Only check/apply the update, then exit (don't run a file or start the REPL)
python tinyscript.py --self-update
# Force a re-apply even if the local version already matches
python tinyscript.py --auto-update --force-update
# Point at a different update server or channel
python tinyscript.py --auto-update --update-url https://your-worker.workers.dev --update-channel betaAvailable channels: stable, beta, nightly.
- The client sends
GET {update-url}/update?channel={channel}to the Worker. - The Worker replies with JSON:
version,url(the GitHub Release asset),sha256,published_at,minimum_version,mandatory, andnotes. tinyscript.pydownloads the file fromurl, computes its own SHA-256, and compares it against the hash the server sent. If they don't match, the update is aborted β nothing is written to disk.- Only after the hash check passes does it safely extract the archive (with protection against path-traversal / zip-slip) and copy the files into place. Existing files are overwritten, new files are added, nothing is deleted.
- The applied version is remembered locally in
.tinyscript_update_state.json, so re-running--auto-updateis a no-op once you're already current.
This means TinyScript never trusts a download just because it came from the expected URL β the SHA-256 the Worker reports has to match the actual bytes every single time.
- Numbers:
42,3.14 - Strings:
"hello world"
- Arithmetic:
+,-,*,/ - Comparison:
==,!=,<,>,<=,>= - Assignment:
=
if,else- Conditionalswhile- Loopsfunc- Function definitionreturn- Return from functionprint()- Print output
# This is a comment
x = 10 # Comments can go after code
- Statements can end with newlines or optional semicolons (
;); extra;-only lines are allowed - You can break lines before
=in assignments and inside( )for calls and grouped expressions (the parser skips newline tokens between related tokens) - Code blocks use curly braces
{ } - Function calls need parentheses:
print(x) - Function parameters must have unique names (duplicates are a syntax error)
returnis only valid inside a function body
Start here:
- Read
lexer.py- Learn how code becomes tokens - Read
parser.py- Learn how tokens become a tree - Read
interpreter.py- Learn how the tree executes - Read
optimizer.py- Learn how code gets faster - Read
errors.py- See how runtime errors attach source locations - Read the
tinyscript-update-workerproject - See how a minimal, stateless update server can be built on Cloudflare Workers
-
Add new operators
- Try adding
%(modulo) operator - Add
**(power) operator
- Try adding
-
Add new statements
- Add
forloops - Add
breakandcontinue
- Add
-
Add new data types
- Add lists/arrays
- Add boolean values (
true/false)
-
Improve the optimizer
- Add more optimization rules
- Detect infinite loops
-
Extend the update system
- Add OS/architecture-specific downloads (
?channel=stable&os=linux&arch=x64) - Add percentage-based rollout
- Add digital signature verification on top of SHA-256
- Add OS/architecture-specific downloads (
βββ lexer.py # Tokenizes source code
βββ parser.py # Builds Abstract Syntax Tree
βββ interpreter.py # Executes the AST
βββ optimizer.py # Optimizes the AST
βββ errors.py # TinyScriptRuntimeError and message helpers
βββ tinyscript.py # Main compiler/interpreter (CLI + wiring + auto-update client)
βββ example.ts # Example programs
βββ advanced_example.ts
βββ README.md # This file
The update server itself lives in a separate small project, tinyscript-update-worker/ (Cloudflare Worker, ES Modules):
tinyscript-update-worker/
βββ wrangler.toml
βββ package.json
βββ src/
βββ index.js # Routing: GET /update, GET /health
βββ versions.js # Per-channel version data (version, url, sha256, notes...)
βββ http.js # Shared JSON response helpers
func fibonacci(n) {
if n <= 1 {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
counter = 0
while counter < 10 {
print(fibonacci(counter))
counter = counter + 1
}
func gcd(a, b) {
while b != 0 {
temp = b
b = a - (a / b) * b
a = temp
}
return a
}
print(gcd(48, 18)) # Output: 6
- No floating-point precision in division (uses Python's
/) - No string operations yet (concatenation, slicing)
- No arrays or data structures
- No classes or objects
- No imports or modules
These are great opportunities for you to add features!
Source Code (UTF-8)
β
[Lexer] β Tokens
β
[Parser] β Abstract Syntax Tree (AST)
β
[Optimizer] β Optimized AST
β
[Interpreter] β Execution & Output (errors β TinyScriptRuntimeError / stderr)
Update flow (separate from program execution):
tinyscript.py --auto-update
β
GET {update-url}/update?channel={channel} β Cloudflare Worker
β
JSON: { version, url, sha256, mandatory, notes, ... }
β
Download file from `url` β verify SHA-256 β safe-extract β copy into place
- "Crafting Interpreters" by Robert Nystrom (free online)
- "Writing An Interpreter In Go" by Thorsten Ball
- "Engineering a Compiler" by Keith Cooper
- Python's
astmodule - See how Python itself parses code
This is an educational project! Feel free to:
- Add new features
- Fix bugs
- Improve documentation
- Create more example programs
Free to use for learning! Do whatever you want with it.
Building a programming language is one of the most rewarding projects you can do as a programmer. You'll learn:
- How compilers work
- How parsers work
- How optimization works
- How programming languages are designed
Now go build something amazing! π