Skip to content

Commit bdfe521

Browse files
committed
all files
1 parent 09a8da2 commit bdfe521

27 files changed

+2450
-1
lines changed

.github/workflows/ci.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#runs pytest on GitHub Actions for push/pr stability
2+
name: CI
3+
4+
on:
5+
push:
6+
branches: [ main ]
7+
pull_request:
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Set up Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: '3.11'
19+
- name: Install dependencies
20+
run: |
21+
python -m pip install --upgrade pip
22+
pip install .[dev]
23+
- name: Run tests
24+
run: |
25+
pytest

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
__pycache__/
2+
.pytest_cache/
3+
*.py[cod]
4+
.DS_Store
5+
.env
6+
.venv
7+
*.egg-info/
8+
dist/
9+
build/
10+
*.bc
11+
out/

README.md

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,139 @@
1-
# decaf-bytecode
1+
# Decaf: a tiny bytecode-compiled language
2+
3+
Decaf is a lean, portfolio-friendly project that walks from source code to a working bytecode VM:
4+
5+
- hand-written lexer, Pratt-style parser, and AST with source spans
6+
- semantic resolver with lexical scopes, mutability checks, and call validation
7+
- bytecode compiler targeting a compact stack machine
8+
- virtual machine with globals, call frames, tracing, and disassembly tooling
9+
- CLI for `compile`, `run`, and `disasm`, plus JSON serialization for bytecode artifacts
10+
11+
## Quick start
12+
13+
```bash
14+
# clone, then install in editable mode with dev tooling
15+
python -m pip install --upgrade pip
16+
pip install -e .[dev]
17+
18+
# run the acceptance programs
19+
decaf run examples/sum_loop.decaf
20+
21+
# inspect the generated bytecode
22+
decaf disasm examples/sum_loop.decaf
23+
24+
# enable VM tracing while executing
25+
decaf run examples/sum_loop.decaf --trace
26+
```
27+
28+
> Working locally without installation? Prefix commands with `PYTHONPATH=src python -m decaf.cli ...` instead.
29+
30+
## Language snapshot
31+
32+
- **Type system**: 32-bit signed integers only; truthiness follows C rules (0 is false, non-zero true).
33+
- **Declarations**: immutable `let` and mutable `var` at global or block scope; immutables reject reassignment.
34+
- **Expressions**: integer literals, identifiers, `+ - * /`, parentheses, call expressions.
35+
- **Statements**: expression statements, `print`, blocks, `if/else`, `while`, and `return`.
36+
- **Functions**: first-order, positional parameters, lexical scoping, required `return` on every path; `main()` is the entry point.
37+
38+
### Grammar sketch
39+
40+
```
41+
program := (fnDecl | varDecl)* EOF
42+
fnDecl := "fn" IDENT "(" params? ")" block
43+
varDecl := ("let" | "var") IDENT "=" expr ";"
44+
stmt := block | "print" expr ";" | ifStmt | whileStmt | returnStmt | expr ";"
45+
expr := assignment
46+
assignment:= IDENT "=" assignment | term
47+
term := factor (("+" | "-") factor)*
48+
factor := unary (("*" | "/") unary)*
49+
unary := "-" unary | call
50+
```
51+
52+
## Bytecode & VM
53+
54+
The compiler lowers functions into isolated chunks that share a constant pool. Values live on a stack; locals are addressed by slot, globals by index.
55+
56+
| Opcode | Stack effect | Notes |
57+
| --- | --- | --- |
58+
| `PUSH_CONST c` | `+1` | push constant index `c` |
59+
| `LOAD_LOCAL i` / `STORE_LOCAL i` | `±1` | locals live in a contiguous frame |
60+
| `LOAD_GLOBAL g` / `STORE_GLOBAL g` | `±1` | globals stored in a module-wide array |
61+
| `ADD`, `SUB`, `MUL`, `DIV` | `-1` | arithmetic pops two values, pushes result (`DIV` truncates toward zero) |
62+
| `JMP addr` | `0` | unconditional branch |
63+
| `JMP_IF_FALSE addr` | `-1` | pop condition, jump when zero |
64+
| `CALL f argc` | `1-argc` | push args left→right, call function `f` |
65+
| `RET` | `-1` | pop return value, restore caller |
66+
| `PRINT` | `-1` | pop value, print decimal |
67+
| `POP` | `-1` | discard top of stack |
68+
| `HALT` | `0` | stop execution (entry chunk only) |
69+
70+
### Disassembly snapshot
71+
72+
```
73+
$ decaf disasm examples/sum_loop.decaf
74+
== fn 0 main ==
75+
0000 line 2 PUSH_CONST #0 (0)
76+
0003 line 2 STORE_LOCAL 0
77+
0006 line 3 PUSH_CONST #1 (0)
78+
0009 line 3 STORE_LOCAL 1
79+
...
80+
0053 line 8 LOAD_LOCAL 1
81+
0056 line 8 PRINT
82+
0057 line 9 LOAD_LOCAL 1
83+
0060 line 9 RET
84+
== fn 1 <entry> ==
85+
0000 line 1 CALL 0 main argc=0
86+
0004 line 1 POP
87+
0005 line 1 HALT
88+
```
89+
90+
### Trace mode
91+
92+
```
93+
$ decaf run examples/sum_loop.decaf --trace
94+
[trace] ip=0 fn=<entry> op=CALL stack=[<empty>]
95+
[trace] ip=0 fn=main op=PUSH_CONST stack=[0,0]
96+
[trace] ip=3 fn=main op=STORE_LOCAL stack=[0,0,0]
97+
...
98+
[trace] ip=53 fn=main op=LOAD_LOCAL stack=[...,-1,0,5,10]
99+
[trace] ip=56 fn=main op=PRINT stack=[...,-1,0,5,10,10]
100+
10
101+
```
102+
Trace output shows the instruction pointer, function, opcode, and the tail of the operand stack.
103+
104+
## Examples
105+
106+
| Program | Description | Output |
107+
| --- | --- | --- |
108+
| `examples/arithmetic.decaf` | expression precedence and return | `14` |
109+
| `examples/variables.decaf` | global mutation vs. local `let` | `13` |
110+
| `examples/if_else.decaf` | conditional execution | `5` |
111+
| `examples/sum_loop.decaf` | accumulation in a `while` loop | `10` |
112+
| `examples/factorial.decaf` | recursive function + multiplication | `120` |
113+
114+
## Development
115+
116+
- **Tests**: `pytest` exercises the lexer, parser, resolver, compiler, VM, and serializer. A GitHub Actions workflow runs them on every push/PR targeting `main`.
117+
- **Formatting**: the project sticks to concise, intentional comments (e.g. `#describes...`) when needed; otherwise the code aims for readability without extra tooling.
118+
- **Bytecode snapshots**: disassembler output doubles as golden coverage—update the README samples if you tweak the instruction set.
119+
120+
### Suggested workflow
121+
122+
```bash
123+
# run unit tests
124+
pytest
125+
126+
# regenerate bytecode JSON for shipping artifacts
127+
decaf compile examples/sum_loop.decaf -o out/sum_loop.bc
128+
129+
# execute the saved bytecode
130+
decaf run --bytecode out/sum_loop.bc
131+
```
132+
133+
## Future ideas
134+
135+
- boolean literals and comparison opcodes that return 0/1
136+
- simple CFG-aware optimizer (constant folding, dead-branch pruning)
137+
- bytecode verifier and max-stack precomputation per function
138+
- structured error diagnostics (source snippets, suggestions)
139+
- richer value types (strings) once the VM has a heap strategy

examples/arithmetic.decaf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//noteshows precedence of arithmetic and return semantics
2+
fn main() {
3+
print 2 + 3 * 4;
4+
return 0;
5+
}

examples/factorial.decaf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//noterecursive factorial example stressing calls
2+
fn fact(n) {
3+
if (n - 1) {
4+
return n * fact(n - 1);
5+
} else {
6+
return 1;
7+
}
8+
}
9+
10+
fn main() {
11+
print fact(5);
12+
return 0;
13+
}

examples/if_else.decaf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//notetests conditional printing via if/else branches
2+
fn classify(n) {
3+
if (n) {
4+
print n;
5+
} else {
6+
print 0;
7+
}
8+
return n;
9+
}
10+
11+
fn main() {
12+
return classify(5);
13+
}

examples/sum_loop.decaf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//notesums the first five integers using a while loop
2+
fn main() {
3+
var i = 0;
4+
var total = 0;
5+
while (i - 5) {
6+
total = total + i;
7+
i = i + 1;
8+
}
9+
print total;
10+
return total;
11+
}

examples/variables.decaf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//notedemonstrates global mutation alongside local let
2+
var g = 10;
3+
fn main() {
4+
let base = g;
5+
var i = 0;
6+
while (i - 3) {
7+
g = g + i;
8+
i = i + 1;
9+
}
10+
print g;
11+
return base;
12+
}

pyproject.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[build-system]
2+
requires = ["setuptools", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "decaf-bytecode"
7+
version = "0.1.0"
8+
authors = [{name = "Your Name"}]
9+
description = "Decaf: a tiny bytecode-compiled language"
10+
readme = "README.md"
11+
requires-python = ">=3.11"
12+
license = {text = "MIT"}
13+
classifiers = [
14+
"Programming Language :: Python :: 3",
15+
"License :: OSI Approved :: MIT License",
16+
"Operating System :: OS Independent",
17+
]
18+
19+
[project.optional-dependencies]
20+
dev = [
21+
"pytest>=7.4",
22+
]
23+
24+
[tool.pytest.ini_options]
25+
pythonpath = ["src"]
26+
testpaths = ["tests"]
27+
28+
[tool.setuptools]
29+
package-dir = {"" = "src"}
30+
31+
[tool.setuptools.packages.find]
32+
where = ["src"]
33+
34+
[project.scripts]
35+
decaf = "decaf.cli:main"

src/decaf/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Decaf: a tiny bytecode-compiled language."""
2+
3+
#makes package exports explicit for downstream imports
4+
from . import ast, compiler, disasm, lexer, parser, semantic, token, vm
5+
6+
__all__ = [
7+
"ast",
8+
"compiler",
9+
"disasm",
10+
"lexer",
11+
"parser",
12+
"semantic",
13+
"token",
14+
"vm",
15+
]

0 commit comments

Comments
 (0)