Skip to content

Compiler

The Devlish compiler (devlish_core) parses English-like source into typed statements, then emits bytecode instructions targeting the Devlish VM. The compiler is a single-pass system written in Rust.

The parser recognizes 32 statement types that cover the full language surface:

Category Statements
I/O Input, Output, FileWrite, FileRead, ReadXlsxCell, ReadPdfText, ReadDocxText
Assignment Assignment, ConditionalAssignment, SetField, Definition
Control flow Branch, ForEach, WhileLoop, UntilLoop, Break, Continue
Error handling TryRecover, Fail, Require
Validation Validate, Assertion, ExportAssertions
HTTP HttpRequest, HttpDownload
Filesystem FileCopy, FileMove, FileMkdir, FileDelete, FileExists, FileStat, FileList, FileGlob
Structure Import, Load, Extract, RespondWith, Checkpoint, Trigger
Collections Append, Pop
Services ServiceCall, Route, Bind

Each statement carries its source line number and original text for diagnostics.

The compiler emits 42 opcodes in the current instruction set:

CONST LOAD STORE ADD SUB
MUL DIV AND OR NOT
EQ NEQ LT LTE GT
GTE JUMP JUMP_IF_FALSE PRINT
ASK EXPORT RETURN VALIDATE ASSERT
EXPORT_ASSERTIONS REQUIRE FAIL TRY_PUSH
TRY_POP RESPOND SERVICE_CALL HTTP_REQUEST
READ_FILE WRITE_FILE CALL_BUILTIN SET_FIELD
FOR_EACH CHECKPOINT FILE_COPY FILE_MOVE FILE_MKDIR
FILE_DELETE FILE_EXISTS FILE_STAT FILE_LIST FILE_GLOB

Each instruction is a JSON object with an op field and operands:

{"op": "CONST", "dest": "r0", "const": 0}
{"op": "STORE", "name": "threshold", "src": "r0"}
{"op": "VALIDATE", "target": "amount", "rule": "minimum", "value": "r1"}
{"op": "JUMP_IF_FALSE", "cond": "r2", "target": 15}

Every instruction maps back to its source line for error reporting:

{
"source_map": [
{"line": 1, "source": "threshold equals 1000000"},
{"line": 2, "source": "amount must be at least threshold"},
{"line": 4, "source": "If status equals \"approved\":"}
]
}

When the VM encounters a runtime error, it uses the source map to report the original Devlish line, not a bytecode address.

If a program declares Permissions, Boundaries, or Callers, the compiler extracts them into a manifest field:

Permissions:
Read files from "/data/"
Write files to "/output/"
Callers:
Any MCP client

Compiles to:

{
"manifest": {
"permissions": [
{"kind": "read_file", "scope": "/data/"},
{"kind": "write_file", "scope": "/output/"}
],
"boundaries": [],
"callers": ["Any MCP client"]
}
}

The compiler produces structured diagnostics with line numbers:

line 5: undefined variable: customer_name: Print customer_name
line 8: expected indented block after If: If score > 70:

Each diagnostic includes the line number, error message, and the original source text.

Class programs compile each method into a separate instruction stream. The bytecode package includes a methods array:

{
"methods": [
{
"name": "calculate wages",
"ruby_name": "calculate_wages",
"params": ["hours_worked", "hourly_rate"],
"is_private": false,
"instructions": [...],
"source_map": [...]
}
]
}

The --method flag at runtime selects which method to execute.

Inspect compiled bytecode with human-readable output:

Terminal window
devlish disassemble program.dvlc.json
0000 CONST r0, 10 ; line 2: base score equals 10
0001 STORE base_score, r0 ; line 2
0002 CONST r1, 5 ; line 3: bonus score equals 5
0003 STORE bonus_score, r1 ; line 3
0004 LOAD r2, base_score ; line 4
0005 LOAD r3, bonus_score ; line 4
0006 ADD r4, r2, r3 ; line 4: total equals base_score plus bonus_score