Python
The fastest one to write. The syntax is almost desktop Python, and changing a line and running again is a pleasure.
How to run it on your phone
vml run examples/python/test/sysinfo.py
Compiling takes a while (one to two minutes for C, a few seconds for scripting languages). Once the program is running, the gamepad is at the bottom of the screen - the D-pad and the four action buttons are all there. Tap the back arrow in the top-right corner to return to the Shell.
What to know before you write
- Call
ui_*directly; no declaration needed - Compiles fast (a few seconds), good for repeated edits
- Use the shared library's integer grid for mutable grids (boards, maps), not a Python list (see the pitfall below)
Examples
| File | What it shows |
|---|---|
app/tetris.py | Tetris |
test/sysinfo.py | Device information |
Also in this folder: app/breakout.py, app/calendar.py, app/notepad.py, app/stock.py, app/weather.py, app/wordle.py, demo/demo_std.py, test/libs.py, tty/demo_tty.py, ui/demo_ui.py
Pitfalls we hit on real devices
- ⚠ List writes do not stick: after
b[i] = v, reading it back still gives 0 (nested lists are wrong too). For things like a board, use the shared library's integer grid instead:python ui_grid_clear() ui_grid_set(3, 1) # cell 3 v = ui_grid_get(3)There are 256 cells, enough for a 10x20 board.
The rest of this page is taken straight from the VML source (third_party/vml/VMLPrepares/PythonCompiler/): the README covers what this front end supports and how to compile it, and the language reference covers the syntax itself. When upstream changes, regenerating this page brings it up to date.
Language reference
Python language compiler specification
Version: v1.0 | Date: 2026-07-06 | Revised by: Shenzhen Tanso Intelligent Technology Co., Ltd.
Specification standard
| Field | Value |
|---|---|
| Target standard | Python 3.0 (2008) |
| Release year | 2008 |
| Completeness | ~90% |
| Updates | 2026-05-18: list/dict/tuple/set are all implemented with heap allocation |
| Tests | 6 passing |
Version 1.0 | 2026-04-19 | Compiler path: VMLPrepares/PythonCompiler/
1. Goals and scope
Compile a subset of Python into VML assembly that runs on the VML virtual machine. VML is a hybrid register-plus-stack architecture with 16 general-purpose registers (R0–R15), supporting integer and floating-point arithmetic.
Not implemented: class, async/await, decorators, type annotations, list comprehensions, the generator runtime
2. Reserved words
| Reserved word | Semantics | Parser | CodeGen |
|---|---|---|---|
| False/True/None | literals | ✅ | ✅→0/1/0 |
| and/or/not | logical operators | ✅ short-circuit | ✅ jump chain |
| if/elif/else | conditionals | ✅ | ✅ conditional jumps |
| while/for | loops | ✅ with else | ✅ loop + else |
| break/continue | loop control | ✅ | ✅ JMP |
| def | function definition | ✅ | ✅ stack frame convention |
| return | return | ✅ | ✅ epilogue |
| pass | empty statement | ✅ | ✅ NOP |
| lambda | anonymous function | ✅ | ⚠️ stub |
| assert | assertion | ✅ | ⚠️ stub |
| del | delete | ✅ | ⚠️ stub |
| global/nonlocal | scope | ✅ | ⚠️ stub |
| import/from | module import | ✅ | ⚠️ stub |
| raise | raise an exception | ✅ | ⚠️ stub |
| try/except/finally | exceptions | ✅ | ⚠️ stub |
| with | context manager | ✅ | ⚠️ stub |
| yield | generators | ✅ | ⚠️ stub |
| match/case | pattern matching | ✅ constant patterns | ⚠️ stub (jumps already generated) |
| in/is | membership/identity | ✅ comparison | ⚠️ stub (→0) |
| as | alias | ✅ | — |
| // | division | ✅ | ✅ // |
| _ | wildcard | ✅ | ⚠️ →0 |
3. Data types
3.1 Literals
| Type | Syntax | Lexer Token | CodeGen |
|---|---|---|---|
| Decimal integer | 42, 1_000 | INTEGER | LOAD RR0 #42 |
| Hexadecimal | 0xFF | INTEGER | LOAD RR0 #255 |
| Octal | 0o77 | INTEGER | LOAD RR0 #63 |
| Binary | 0b1010 | INTEGER | LOAD RR0 #10 |
| Float | 3.14, 1e10 | FLOAT | LOADF RR0 #3.14 |
| String | "hello", 'world' | STRING | dataSection + LOAD RR0 [addr] |
| Triple-quoted | """multi""" | STRING | dataSection |
| Boolean | True/False | TRUE/FALSE | LOAD RR0 #1/#0 |
| None | None | NONE | LOAD RR0 #0 |
| List | [1, 2, 3] | LBRACKET | ✅ heap allocated [length + elements] |
| Dictionary | {k:v} | LBRACE | ✅ heap allocated [pair count + key/value pairs] |
| Tuple | (1, 2) | LPAREN | ✅ heap allocated [length + elements] |
| Set | {1, 2, 3} | LBRACE | ✅ heap allocated [length + elements] |
Dictionary vs set ambiguity: {expr} is a set, {expr:expr} is a dictionary, {} is an empty dictionary.
3.2 Type compatibility
VML has no dynamic typing; at runtime every value is an integer (int) or a float (float). bool→int, None→0. A string is a special data-segment reference (an address), and runtime string operations are not supported yet.
4. Operator precedence (lowest to highest)
Level 1 lambda x: expr # loosest, right associative
Level 2 x if cond else y # ternary
Level 3 or # short-circuit
Level 4 and # short-circuit
Level 5 not x # unary
Level 6 in | is | < <= > >= != == # comparison (chained: a<b<c → (a<b)&(b<c))
Level 7 | # bitwise or
Level 8 ^ # bitwise xor
Level 9 & # bitwise and
Level 10 << >> # shifts
Level 11 + - # add/subtract
Level 12 * / // % # multiply/divide/floor-divide/modulo
Level 13 -x +x ~x # unary
Level 14 ** # power (right associative)
Level 15 await x # unary (runtime not implemented yet)
Level 16 x[i] x.attr f(args) # subscript/attribute/call (highest)
VML mapping of the operators
| Operator | VML instruction | Notes |
|---|---|---|
| + | ADD RR0 RR0 RR1 | |
| - | SUB RR0 RR0 RR1 | |
| * | MUL RR0 RR0 RR1 | |
| / | DIV RR0 RR0 RR1 | float |
| // | DIV RR0 RR0 RR1 + truncation | floor division |
| % | MOD RR0 RR0 RR1 | |
| ** | loop MUL | simplified for now |
| & | AND RR0 RR0 RR1 | |
| | | OR RR0 RR0 RR1 | |
| ^ | XOR RR0 RR0 RR1 | |
| ~ | NOT RR0 RR0 | |
| << | SHL RR0 RR0 RR1 | |
| >> | SHR RR0 RR0 RR1 | |
| -x (neg) | SUB RR0 #0 RR0 | |
| == | CMP + JE/JNE | |
| != | CMP + JNE/JE | |
| < | CMP + JL | |
| <= | CMP + JLE | |
| > | CMP + JG | |
| >= | CMP + JGE | |
| and | short-circuit jump chain | if A is 0, skip B |
| or | short-circuit jump chain | if A is non-zero, skip B |
| not | CMP RR0 #0 + invert |
5. Statement and VML semantics mapping
5.1 Function definition
def foo(a, b, c):
return a + b + c
VML stack frame convention:
caller: PUSH c, PUSH b, PUSH a (right to left)
CALL foo (pushes the return address)
callee prologue: PUSH R15, PUSH R12, MOVE R12 R13
callee epilogue: MOVE R13 R12, POP R12, POP R15, RET
stack layout (R12 = frame pointer):
arg0 ← R12+12
arg1 ← R12+8
arg2 ← R12+4
ret ← R12+0 (implicit)
saved R15 ← R12-4
saved R12 ← R12-8
local0 ← R12-12
local1 ← R12-16
5.2 if/elif/else
if a > 0:
x = 1
elif a == 0:
x = 0
else:
x = -1
VML:
LOAD RR0 [R12-a]
CMP RR0 #0
JLE elif_0
LOAD RR0 #1
STORE [R12-x] RR0
JMP endif_0
elif_0:
LOAD RR0 [R12-a]
CMP RR0 #0
JNE else_0
LOAD RR0 #0
STORE [R12-x] RR0
JMP endif_0
else_0:
LOAD RR0 #-1
STORE [R12-x] RR0
endif_0:
5.3 while loops
while i < n:
i = i + 1
else:
result = -1
VML:
while_0:
LOAD RR0 [R12-i]
CMP RR0 [R12-n]
JGE endwhile_0
# body: i = i + 1
LOAD RR0 [R12-i]
PUSH RR0
LOAD RR0 #1
MOVE RR1 RR0
POP RR0
ADD RR0 RR0 RR1
STORE [R12-i] RR0
JMP while_0
endwhile_0:
# else branch (runs when the while condition is False and no break left the loop)
LOAD RR0 #-1
STORE [R12-result] RR0
while-else semantics: the else runs when the loop ends normally (the condition is false); it does not run when a break leaves the loop. Implementation: break jumps to just after endwhile.
5.4 for loops
for i in range(n):
x = i * 2
VML:
LOAD RR0 #0 # i = 0
STORE [R12-i] RR0
for_0:
LOAD RR0 [R12-i]
CMP RR0 [R12-n]
JGE endfor_0
# body: x = i * 2
LOAD RR0 [R12-i]
PUSH RR0
LOAD RR0 #2
MOVE RR1 RR0
POP RR0
MUL RR0 RR0 RR1
STORE [R12-x] RR0
# increment
LOAD RR0 [R12-i]
PUSH RR0
LOAD RR0 #1
MOVE RR1 RR0
POP RR0
ADD RR0 RR0 RR1
STORE [R12-i] RR0
JMP for_0
endfor_0:
5.5 match/case
match n:
case 1:
print(100)
case 2:
print(200)
case _:
print(999)
VML:
LOAD RR0 [R12-n]
PUSH RR0
LOAD RR0 #1
CMP RR0 RR1
JE case_0_end
LOAD RR0 #100
PUSH RR0
SYSCALL #4
ADD RR13 RR13 #4
JMP match_end
case_0_end:
LOAD RR0 #2
CMP RR0 RR1
JE case_1_end
LOAD RR0 #200
PUSH RR0
SYSCALL #4
ADD RR13 RR13 #4
JMP match_end
case_1_end:
# _ wildcard: always matches
LOAD RR0 #999
PUSH RR0
SYSCALL #4
ADD RR13 RR13 #4
JMP match_end
match_end:
POP RR0
Semantics: push the matched value, then compare it in each case; the _ wildcard matches unconditionally. After a case body runs successfully, JMP match_end.
5.6 lambda
f = lambda x, y: x + y
VML:
lambda_f_0:
PUSH R15, PUSH R12, MOVE R12 R13
# x = R12+12, y = R12+8
LOAD RR0 [R12+12]
PUSH RR0
LOAD RR0 [R12+8]
MOVE RR1 RR0
POP RR0
ADD RR0 RR0 RR1
MOVE RR13 R12, POP R12, POP R15, RET
# f points at the address of lambda_f_0
5.7 assert
assert x > 0, "x must be positive"
VML:
# evaluate x > 0 → bool in RR0
CMP RR0 #0
JNE assert_ok_0
# failure: print message, halt
LOAD RR0 #-1
SYSCALL #99 # abnormal exit
assert_ok_0:
5.8 Assignment and augmented assignment
x = 10
x += 5
VML:
LOAD RR0 #10
STORE [R12-x] RR0
LOAD RR0 [R12-x]
PUSH RR0
LOAD RR0 #5
MOVE RR1 RR0
POP RR0
ADD RR0 RR0 RR1
STORE [R12-x] RR0
5.9 Ternary expression
result = 1 if a > 10 else 0
VML:
LOAD RR0 [R12-a]
CMP RR0 #10
JG cmp_true_0
LOAD RR0 #0
JMP cmp_end_1
cmp_true_0:
LOAD RR0 #1
cmp_end_1:
STORE [R12-result] RR0
6. Built-in functions
| Function | VML | Arguments | Return value |
|---|---|---|---|
| print(x) | SYSCALL #4 | 1 int on the stack | none |
| input() | SYSCALL #7 | none | RR0 = integer |
| len(x) | stub | — | RR0 = 0 |
| range(n) | for loop unrolling | — | special-cased |
| abs(x) | inlined | RR0 | RR0 |
| int(x) | inlined | RR0 | RR0 |
| str(x) | stub | — | RR0 = 0 |
7. Indentation rules (Lexer)
- Leading spaces > currentIndent → emit an INDENT token, update currentIndent
- Leading spaces < currentIndent → emit DEDENT tokens in a loop, currentIndent -= 4
- No leading spaces and currentIndent > 0 → emit DEDENT tokens until 0
- The indent unit is fixed at 4 spaces, tab = 4 spaces
- At end of file, every unclosed INDENT is matched with a DEDENT
Floating-point and 64-bit compilation modes
The VML toolchain controls how floating point and 64-bit integers are handled through three compilation flags:
| Flag | Possible values | Default | Description |
|---|---|---|---|
--float32 | hard / soft / none | hard | 32-bit float handling mode |
--float64 | hard / soft / none | soft | 64-bit float (float) handling mode |
--int64 | hard / soft / none | soft | 64-bit integer (int) handling mode |
32-bit float (float32)
Python's float type is mapped to a 64-bit double-precision float when VML compiles it. 32-bit float arithmetic is used for internal intermediate results:
hardmode (default): uses the VML native float instructionsMOVEF/ADDF/SUBF/MULF/DIVF/CMPF/NEGF, operating directly through the sixteen float registers F0-F15.softmode: uses the Q15.16 fixed-point software emulation librarysoftfloat.c, emulating float arithmetic through the__vml_float_add/sub/mul/div/neg/abs/cmpfunctions and so on.nonemode: floating-point arithmetic is disabled.
64-bit float (double)
Python's float type is natively an IEEE 754 double-precision 64-bit float and compiles as follows:
softmode (default): uses the IEEE 754 double-precision software emulation librarysoftdouble.c, emulating through the__vml_double_add/sub/mul/div/neg/abs/cmp,__vml_int2double/double2int,__vml_float2double/double2floatfunctions and so on. Compatible with every platform (MCU included).hardmode: uses the VML double-precision instructionsMOVED/ADDD/SUBD/MULD/DIVD/CMPD/NEGD, operating through the eight double-precision registers D0-D7.nonemode: floating-point arithmetic is disabled.
64-bit integer (int64)
Python 3's int type is an arbitrary-precision integer. When VML compiles it, integers within the 64-bit range are handled as follows:
softmode (default): uses the dual-register software emulation librarysoftint64.c, emulating 64-bit integer arithmetic through the__vml_i64_add/sub/neg/and/or/xor/not/shl/shrfunctions and so on.hardmode: reserved; a future VML version will support native 64-bit integer instructions.nonemode: degrades to 32-bit integers.
Software emulation libraries
The software emulation libraries above all live in the Lib/shared/ directory, are written in C, are compiled to VML by the C compiler, and are shared by every language:
| Library file | Purpose | Core functions |
|---|---|---|
softfloat.c | Q15.16 fixed-point 32-bit float emulation | __vml_float_add/sub/mul/div/neg/abs/cmp, __vml_int2float/float2int |
softdouble.c | IEEE 754 double-precision 64-bit float emulation | __vml_double_add/sub/mul/div/neg/abs/cmp, __vml_int2double/double2int, __vml_float2double/double2float |
softint64.c | 64-bit integer dual-register emulation | __vml_i64_add/sub/neg/and/or/xor/not/shl/shr |
8. Unsupported Python features
| Feature | Reason |
|---|---|
| class / __init__ / inheritance | the class system is too complex |
| async / await | needs an event loop |
| decorators @ | low-priority syntactic sugar |
| type annotations x: int | compile-time checking is not needed yet |
| f-string | string interpolation is complex |
| list comprehensions [x*2 for x in ...] | needs iterators |
| the yield generator runtime | needs a state machine |
| with __enter__/__exit__ | simplified to an empty implementation |
| full match patterns (destructuring/guards) | constants + wildcards only |
| *args/**kwargs | variadic parameters |
| multiple assignment a, b = 1, 2 | unpacking |
| the walrus operator := | not implemented yet |
9. Change log
| Date | Version | Change |
|---|---|---|
| 2026-04-19 | 0.1 | initial version |
| 2026-04-19 | 1.0 | added the VML semantics mapping, the stack frame convention, the indentation rules, and while/for-else semantics |
🆕 String type (v1.65.19)
This language compiler uses .wstring (UTF-16LE) by default as its internal string storage.
| Mode | Default encoding | VML directive | SYSCALL output |
|---|---|---|---|
| MCU (default) | .string (UTF-8) | .string | #1 |
| OS | .wstring (UTF-16LE) | .wstring | #391 |
Predefined macro: VML_WSTRING — defined automatically in OS mode, undefined in MCU mode Output function: OS mode automatically uses shared_print_wstr (automatic UTF-16LE→UTF-8 conversion)
// user code can determine the encoding through the macro
#ifdef VML_WSTRING
// the default string is a wstring (UTF-16LE)
#else
// the default string is UTF-8
#endif
Compiler README
Python 3 compiler
Path: VMLPrepares/PythonCompiler/ Completeness: ~90% | 🟢 production ready Standard library: Lib/python/
Features
- ✅ Parsing + code generation (Lexer/Parser/CodeGenerator)
- ✅ Control flow (if/while/for)
- ✅ Function/class definition and calls
- ✅ list/dict/tuple/set — heap-allocated implementation
- ✅ POKE/PEEK memory access (MMIO)
- ✅ Shared built-in function library (builtins.vml)
Compilation modes
MCU mode (default --mode mcu)
MCU mode is tuned for microcontrollers and bare-metal environments (Arduino/STM32/8051 and so on), and automatically skips features that are incompatible with an operating system.
Skipped (no code is generated for this syntax):
- async, yield, import
Kept (implemented underneath by the BIOS):
- list/dict/tuple/set (heap), class
- POKE/PEEK memory-mapped I/O (MMIO)
- Basic type arithmetic, control flow, function calls
- printf/puts mapped to the UART
OS mode (--mode os, reserved)
OS mode targets environments that have an operating system (such as embedded Linux or an RTOS), and will support every language feature (file system, threads, asynchronous I/O, exceptions, reflection and so on).
RAM levels
--ram k: kilobyte level (2KB~64KB, such as 8051/PIC/AVR)--ram m: megabyte level (64KB~1MB, such as ARM Cortex-M, default)--ram g: gigabyte level (such as x86/DDR systems)--stack-size <bytes>: specify the stack size by hand (by default it is allocated automatically from--ram)
MCU-safe coding tips
- Limited stack space (256-4096 bytes is typical), so avoid deep recursion
- Deep recursion is not allowed (beyond 10 levels the stack must be evaluated)
- Dynamic loading (import/dofile/eval) is not allowed
- Floating-point arithmetic may require the soft-float library
Usage
dotnet run --project VMLPrepares/PythonCompiler input.Py -o output.vml
Tests
Test/Py/ — 0 test files (the test directory is yet to be created)