LipVM is a prototype of a generic virtual machine for executing DSLs (Domain Specific Languages) programs, that are modeled with pyecore (an EMF/Ecore implementation in Python).
Instead of compiling a language to some bytecode, each language is described as an Ecore
metamodel (abstract syntax + runtime state) whose classes know how to evaluate themselves.
The VM turns this evaluation into a chain of resumable Operations, which makes it possible to:
- step through a program operation by operation,
- pause/resume execution,
- and apply edit scripts to the running abstract syntax tree to hot-swap or part of a program while it executes (live programming).
Two example languages are currently implemented:
- Minilogo (partially-implemented, functions and loops are missing): a graphics language that commands a pen to move accross a canvas coordinates.
- Robot : a maze-navigation language that commands a robot to turn a step through a maze path.
- Test safepoint declaration. (in-progress)
- Add the mecanism to prepare and declare a migration after a code change. (in-progress)
- Add the probing mecanism and expose it through LESP. (in-progress)
- Add connectors for RPC calls from Java (LESP?)
core/ Generic, language-independent VM machinery
language.py Base metamodel: AbstractSyntaxElement, RuntimeState,
RuntimeStateElement, ASTElementPosition, SafepointCondition...
identifiers.py Structural identifier helpers (FNV-1a hashing, canonical
value rendering) used by AbstractSyntaxElement.assign_identifiers()
operations.py Operation / OperationArguments / OperationVariables and the
@operation decorator and loop() helper used to build chains
of resumable operations
edit.py EditScript and edit operations (Insert/Update/Delete) used to
modify a running abstract syntax tree
vm.py VirtualMachine: runs programs and applies EditScripts
(with RESTART / HOTSWAP option) to a running (live) execution
lesp.py Work-in-progress JSON-RPC server for a "live execution"
protocol (editor integration)
languages/ One package per supported language
minilogo/
runtime.py Runtime state metamodel (Drawing, PenState, Scope, ...)
syntax.py Abstract syntax metamodel + evaluate() implementations
robot/
runtime.py Runtime state metamodel (Maze, Robot, MazeCell, ...)
syntax.py Abstract syntax metamodel + evaluate() implementations
tools/
export.py Exports pythonic style pyecore classes to a single .ecore file
exploratory.py Scenario: manages multiple VirtualMachine instances (start,
resume, restart, edit) when exploring a language
tests/ pytest test suite; the Robot languages is exercised through the
VirtualMachine API as usage examples (see tests/test_robot.py)
Step 1: install uv, the Python package and project manager.
On Unix systems:
curl -LsSf https://astral.sh/uv/install.sh | shRefer to UV's documentation for more information.
Step 2: create a Python virtual environment
uv venvStep 3: activate the virtual environment
On Unix systems:
source .venv/bin/activateStep 4: pull the project's dependencies
uv syncThe test suite is the best place to see the VM in action, run it using the following command:
python -m pytestNote: running the pytest command as a python module is important so that other modules required by the test suite are recognized.
tests/test_robot.py builds a Robot program (a maze definition plus a sequence of commands), runs
it through VirtualMachine.init()/run(), and asserts on the resulting RuntimeState. It also
demonstrates applying an EditScript to replace part of the program while it runs, and RESTART
it.
A language composed of pyecore metamodels and evaluate() implementations. There is no
parser/grammar generation step at the moment: abstract syntax trees are built directly as pyecore
object graphs (e.g. in tests). In the future they could be produced by a front-end which could be
plugged-in later.
To add a language languages/<name>:
Step 1 — Runtime state (languages/<name>/runtime.py)
Define the EClasses that represent the state the program manipulates while running. Each top-level
state element is a RuntimeStateElement (from core.language) with a name; these elements are
looked up by name on RuntimeState (e.g. runtime.maze, runtime.penstate). Use plain
EObject/EClass/EEnum for any supporting value types (positions, colors, cells, ...).
Step 2 — Abstract syntax (languages/<name>/syntax.py)
Define the EClasses of your abstract syntax tree, each extending AbstractSyntaxElement from
core.language. Every node that can be executed implements:
def evaluate(self, runtime: RuntimeState) -> None:
...- For a leaf/terminal command, decorate
evaluatewith@operation()(fromcore.operations) and read/mutateruntimedirectly — seeTurnLeft,MoveForwardorPeninlanguages/robot/syntax.py/languages/minilogo/syntax.py. - For nodes whose evaluation depends on the result of evaluating sub-expressions, use
@operation(arg_name=lambda node, runtime: node.sub_expr.evaluate(runtime), ...)— the results are injected as keyword arguments intoevaluate, seeBinaryExpressionorMove. - For a sequence of commands (e.g. a
Programor a block body), returnloop(self.commands, lambda cmd: cmd.evaluate(runtime))to chain their operations. - For a conditional repetition (while-loop), pass a
conditionOperation as the third argument toloop(...), seeRepeatWhile. - For branching (if/else), splice the chosen branch's operations into the chain using the
_opkeyword parameter, seeIfDoElse.
The root Program node's evaluate() should initialize runtime.elements with the language's
RuntimeStateElement instances before returning the chain of operations for its commands.
Step 3 — Run it
from core.vm import VirtualMachine
vm = VirtualMachine()
vm.scenario_syntax = my_program_or_commands
vm.program_syntax = my_definitions
vm.init()
vm.run()
state = vm.state # the RuntimeState after executionTo run and manage several such executions side by side (e.g. while exploring a language
interactively), wrap them in a Scenario from tools.exploratory:
from tools.exploratory import Scenario
scenario = Scenario()
identifier = scenario.run_instance(instance=vm)
# later, resume/restart that same instance
scenario.run_instance(identifier=identifier)Assign each AbstractSyntaxElement a unique identifier if you intend to target it later with an
EditScript (InsertSyntaxOperation, UpdateSyntaxOperation, DeleteSyntaxOperation), applied
through vm.udpate(...).
Step 4 — (optional) Export the metamodel
python -m tools.export languages.<name>.runtime languages.<name>.syntax -o <name>.ecore -n <name>This collects all EClasses/EEnums defined in the given modules (and anything they reference) into a
single, self-contained .ecore file, e.g. robot.ecore.
tool/export.py was generated by Claude Sonnet 4.6