Define a language in script. Get a working compiler.
Scriptable language workbench -- tokens to native binary -- all in .lvm
.lvm
➜
tokens
➜
grammar
➜
semantics
➜
emitters
➜
MIR
➜
x64
➜
binary
Under active development
Turing-complete
Backend-in-script
Define a language. Get a compiler.

Write a .lvm script that defines your language's tokens, grammar, semantics, and code generation. LangVM executes it and becomes a working implementation.

say.lvm -- define a language
// A complete language in one .lvm script
language SayLang version "1.0";

tokens {
  casesensitive = true;
  token keyword.say = "say";
  token delimiter.semicolon = ";";
  token string.default = "\"";
}

grammar {
  rule stmt.say {
    expect keyword.say;
    let text: string = currentText();
    advance();
    setAttr(getResultNode(), "message", text);
    requireToken("delimiter.semicolon");
  }
}

emitters {
  on stmt.say {
    let msg: string = getAttr(node, "message");
    mirString("msg_0", msg);
    mirInsn("call", "p_printf",
           "printf", "_", "msg_0");
  }
}
Terminal
$ lvm -l say.lvm -s hello.say

hello
world

// hello.say contained:
//   say "hello";
//   say "world";
//
// Output: a native PE executable
The .lvm scripting language, in code

LangVM scripts are Turing-complete. These examples show the scripting language that powers the pipeline.

// tokens{} -- define the lexer at runtime
// LangVM's generic lexer configures itself from these declarations

tokens {
  casesensitive = true;

  // Keywords
  token keyword.say     = "say";
  token keyword.let     = "let";
  token keyword.if      = "if";
  token keyword.while   = "while";
  token keyword.routine = "routine";

  // Operators and delimiters
  token operator.assign  = "=";
  token operator.plus    = "+";
  token operator.less    = "<";
  token delimiter.semi   = ";";
  token delimiter.lparen = "(";
  token delimiter.rparen = ")";

  // Literals and comments
  token string.default = "\"";
  token comment.line   = "//";
}
// grammar{} -- define Pratt parser rules
// prefix, infix, and statement rules parse YOUR language's source

grammar {
  // Statement rule: say "text";
  rule stmt.say {
    expect keyword.say;
    let text: string = currentText();
    advance();
    let nd: handle = getResultNode();
    setAttr(nd, "message", text);
    requireToken("delimiter.semi");
  }

  // Statement rule: let x = expr;
  rule stmt.var_decl {
    expect keyword.let;
    let name: string = currentText();
    advance();
    requireToken("operator.assign");
    parseExpr();
    requireToken("delimiter.semi");
  }

  // Infix rule: a + b
  rule infix.add {
    trigger operator.plus;
    precedence = 50;
    parseExpr(precedence);
  }
}
// semantics{} -- analysis passes over the AST
// Scope management, symbol declaration, type checking

semantics {
  on stmt.var_decl {
    let name: string = getAttr(node, "name");

    // Check for duplicate declaration
    if scopeHas(name) {
      diagError("duplicate: " + name, node);
    }

    // Declare in current scope
    scopeDeclare(name, "variable");

    // Walk child nodes (the initializer expression)
    walkChildren(node);
  }

  on expr.identifier {
    let name: string = getAttr(node, "name");
    if !scopeHas(name) {
      diagError("undeclared: " + name, node);
    }
  }

  on stmt.say {
    // Validate: no-op for this simple language
  }
}
// emitters{} -- walk the AST, emit output
// For native compilation, emit MIR instructions

emitters {
  on stmt.say {
    let msg: string = getAttr(node, "message");
    // Strip surrounding quotes
    let text: string = substr(msg, 1, len(msg) - 2);
    let text_nl: string = text + "\n";

    // Emit string data into MIR
    let str_name: string = "msg_" + toString(g_str_count);
    mirString(str_name, text_nl);
    g_str_count = g_str_count + 1;

    // Emit: call printf with the string
    mirInsn("call", "p_printf",
           "printf", "_", str_name);
  }
}
// MIR -- a virtual CPU assembly language
// Written inline or built programmatically by emitters

mir {
  m0: module
    import printf, ExitProcess
    export main

    p_printf: proto void, p:fmt, ...
    p_exit:   proto void, i32:code

    msg: string "Hello MIR!\n"

    main: func i64
      local i64:n
      mov  n, 0
      add  n, n, 1
      call p_printf, printf, msg
      call p_exit, ExitProcess, 0
      ret  n
    endfunc
  endmodule
}

// on-handlers lower each MIR event to real x64:
mir {
  on module {
    g_ib = ib_create();
    g_cb = cb_create(1024);
    g_db = db_create();
  }
  on func {
    cb_emit_sub_rsp_imm8(g_cb, 0x28);
  }
  on insn {
    if opcode == "call" {
      cb_emit_call_rip_disp32(g_cb);
    }
  }
  on endfunc {
    cb_emit_add_rsp_imm8(g_cb, 0x28);
    cb_emit_ret(g_cb);
  }
  on endmodule {
    let image: map = pe_build_exe(...);
    bufSave(image, exe_path);
    ExitCode = runPE(exe_path);
  }
}
// Buffer builtins make binary data construction possible
// This is how PE/ELF images are built from script

let buf: any = buffer(4096);

// Write PE DOS header
bufWriteU16(buf, 0, 0x5A4D);      // MZ signature
bufWriteU32(buf, 0x3C, 0x80);     // PE header offset

// Write PE signature
bufWriteU32(buf, 0x80, 0x00004550); // "PE\0\0"

// Write machine code directly
bufWriteU8(buf, offset, 0x48);     // REX.W prefix
bufWriteU8(buf, offset+1, 0x83);   // sub r/m64, imm8
bufWriteU8(buf, offset+2, 0xEC);   // ModRM: rsp
bufWriteU8(buf, offset+3, 0x28);   // 40 bytes shadow

// Copy between buffers
bufCopyBytes(dst, dst_off, src, src_off, count);

// Save to disk and execute
bufSave(buf, "output.exe");
let code: int = runPE("output.exe");
// The .lvm scripting language is Turing-complete
// Variables, control flow, routines, data structures

let count: int = 0;
let names: list = ["alpha", "beta", "gamma"];
let config: map = {"debug": true, "level": 3};

// For loop with range
for i = 0 to len(names) - 1 {
  println(names[i]);
}

// While loop
while count < 10 {
  count = count + 1;
  if count == 5 { continue; }
  println(toString(count));
}

// Routines with return values
routine fib(n: int): int {
  if n <= 1 { return n; }
  return fib(n - 1) + fib(n - 2);
}

println("fib(10) = " + toString(fib(10)));
A complete language workbench. In one VM.

Everything needed to go from language idea to native binary -- lexer, parser, semantic analysis, code generation, and backend -- all scriptable.

⚙
Five-Stage Pipeline
Define tokens{}, grammar{}, semantics{}, emitters{}, and mir{} blocks. LangVM configures its generic lexer and Pratt parser at runtime from your definitions.
💻
MIR Virtual Assembly
A virtual CPU assembly language sits between your emitters and the native backend. mirInsn, mirString, mirBeginFunc -- build programs in MIR, lower them to x64.
⚡
Backend-in-Script
The entire x86_64 backend is ~165KB of .lvm script. x64 instruction encoder, Win64/SysV ABI, PE and ELF image writers -- all in script, not in the VM.
🧠
Turing-Complete Scripting
Variables, control flow, routines, recursion, lists, maps, records, closures. The .lvm scripting language is a full programming language, not a configuration format.
🔌
Embeddable
Drop LangVM.pas into your Delphi project. Create a TLangVM, load a .lvm script, call Run('main'). Your application gains language-implementation capabilities.
📦
Zero Dependencies
One Delphi unit (~13K lines). No external libraries, no runtime, no package manager. Everything the VM needs is self-contained.
🔧
Buffer Builtins
bufWriteU8, bufWriteU32, bufCopyBytes, bufSave -- construct arbitrary binary data from script. PE headers, ELF segments, machine code, all from .lvm.
📊
~200 Builtins
String manipulation, list/map operations, math, file I/O, AST construction, MIR emission, DLL loading, buffer management. Everything pipeline scripts need.
🌍
Proven End-to-End
The full chain is proven: source text to tokens to grammar to semantics to emitters to MIR to x64 to PE executable -- working, tested, in 181 lines of .lvm.
Five stages. All in script.

Each pipeline stage is defined by a block in your .lvm script. LangVM's runtime engine executes them in sequence.

01
tokens{}
Define the lexer. Keywords, operators, string styles, comments, directives. LangVM's generic lexer configures itself from these declarations at runtime. No code generation -- just data.
02
grammar{}
Define the parser. Pratt parser rules -- prefix, infix, and statement -- parse your language's source into an AST. Each rule calls parseExpr(), advance(), setAttr() to build nodes.
03
semantics{}
Define analysis passes. On-handlers (on stmt.var_decl, on expr.call) walk the AST for scope management, symbol declaration, type checking, and attribute stamping.
04
emitters{}
Define code generation. On-handlers walk the AST and emit output. For native compilation, call mirInsn(), mirString(), mirBeginFunc() to build a MIR program.
05
mir{}
Define the backend. On-handlers (on module, on func, on insn, on endmodule) lower MIR events to machine code using encoder, ABI, and PE/ELF scripts -- all written in .lvm.
One unit. Your app gets a language engine.

Drop LangVM.pas into your Delphi project. The entire VM -- lexer, parser, interpreter, pipeline engine, ~200 builtins -- ships as a single unit with zero external dependencies.

MyApp.pas -- host application
uses
  LangVM;

var
  LVM: TLangVM;
begin
  LVM := TLangVM.Create();
  try
    // Set up callbacks
    LVM.SetOnPrint(MyPrintHandler);
    LVM.SetOnDiag(MyDiagHandler);

    // Load a language definition
    LVM.LoadScriptFile('mylang.lvm');

    // Tell the script which file to process
    LVM.SourceFilename := 'input.src';

    // Execute the main routine
    LVM.Run('main');

    // Read the script's exit code
    WriteLn('Exit: ', LVM.ExitCode);
  finally
    LVM.Free();
  end;
end.
TLangVM API surface
// Lifecycle
TLangVM.Create()
TLangVM.Free()

// Loading
.LoadScriptFile(AFilename)
.LoadScript(ASource, AName)

// Execution
.Run(ARoutineName)
.Call(ARoutineName, AArgs)

// Host-VM data exchange
.GetVar(AName): TLVMValue
.SetVar(AName, AValue)
.ExitCode: Int64
.SourceFilename: string

// Extension
.RegisterBuiltin(AName, AFunc)

// Callbacks
.SetOnPrint(ACallback)
.SetOnDiag(ACallback)

The host is just the launcher. The intelligence is in the .lvm scripts. TLangVM does not know or care what language you are defining or what platform you are targeting -- that is all in your scripts.

Built for people who build languages

Whether you are designing a DSL, writing a compiler, or teaching how they work.

Language Designers
Prototype a language without building a compiler from scratch. Define tokens, grammar, and semantics in a .lvm script. Change the script, change the language. No recompilation of the VM.
Compiler Writers
Use LangVM as a scriptable frontend and backend. The five-stage pipeline gives you a configurable lexer, Pratt parser, semantic walker, emitter framework, and MIR backend -- all extensible from script.
Educators
Teach compiler construction with a system where every stage is visible and modifiable. Students see the full chain from source text to native binary, and can experiment by changing any pipeline block.
Tool Builders
Embed LangVM in your application and give it language-implementation capabilities. One Delphi unit, zero dependencies, clean API. Your tool gains the ability to define, parse, and compile languages at runtime.
See LangVM in action

Infographic, walkthroughs, and a deep dive into the pipeline architecture.

LangVM Infographic
🎵
Deep Dive -- Pipeline Architecture
Use the expand button to view full size
Three steps to a working language

No SDK, no package manager, no setup wizard.

01
Write a language definition
// hello.lvm language Hello version "1.0"; tokens { token keyword.say = "say"; token string.default = "\""; token delimiter.semi = ";"; } grammar { rule stmt.say { ... } }
02
Run it
$ lvm -l hello.lvm -s test.src Hello, world! // test.src contained: // say "Hello, world!";
03
Extend it
// Add emitters{} and mir{} blocks // to generate native binaries // Add semantics{} for type checking // Import the x86_64 backend: import "x86_64.lvm"; // Your language now compiles // to native Win64 and Linux64