Pipeline Syntax Features Get Started Download GitHub
Build.
v0.1.0  ·  Meta-Language for Parse()

Describe it.
Parse it.
Build it.

One .parse file. Define the lexer, write the grammar, configure semantics, emit C++23, and the bootstrap compiler gives you back a fully working native compiler. Nothing else required.

Scroll to explore
Pratt Parser Scope Trees C++23 IR Zig Backend Win64 + Linux64

ParseLang is a meta-language: a language for defining languages. Write the description once. Get a complete compiler back. No runtime, no framework, no Delphi code written by you.

Built on top of the Parse() Compiler Construction Toolkit
01
Describe.
One .parse file.
Your entire language.

Write a single .parse file. Declare every token your lexer needs, every grammar rule your parser needs, every semantic check your analyser needs, and every C++23 construct your code generator needs, all in ParseLang's own scripting language.

mylang.parse / lexer
language MyLang;

keywords casesensitive
  'var'    -> 'keyword.var';
  'func'   -> 'keyword.func';
  'return' -> 'keyword.return';
  'if'     -> 'keyword.if';
  'while'  -> 'keyword.while';
  'end'    -> 'keyword.end';
end

operators
  ':=' -> 'op.assign';
  '+'  -> 'op.plus';
  '('  -> 'delim.lparen';
  ')'  -> 'delim.rparen';
end

typemap
  'type.int'    -> 'int64_t';
  'type.string' -> 'std::string';
end
Phase 1: the bootstrap reads your file.
Phase 2: your compiler runs.

The PLC bootstrap reads your .parse file and configures a live TParse instance, wiring every lexer token, Pratt grammar handler, semantic rule, and emitter. Phase 2: that configured instance compiles your source file, validates it semantically, and emits C++23 via a fluent IR builder.

mylang.parse / grammar
registerLiterals;

binaryop 'op.plus' power 20 op '+';
binaryop 'op.star' power 30 op '*';

prefix 'identifier' as 'expr.ident'
  parse
    result := createNode();
    setAttr(result, 'ident.name',
      current().text);
    consume();
  end
end

statement 'keyword.var'
    as 'stmt.var_decl'
  parse
    result := createNode();
    consume();
    setAttr(result, 'decl.name',
      current().text);
    consume();
    expect('delim.colon');
    setAttr(result, 'decl.type',
      current().text);
    consume();
    expect('op.assign');
    addChild(result, parseExpr(0));
    expect('delim.semi');
  end
end
02
Parse.
03
Build.
C++23 out. Native binary in.
Zig/Clang handles the rest.

Your emit rules drive a fluent C++23 IR builder that produces a .h and .cpp. ParseLang calls Zig/Clang to compile them into a native exe, lib, or dll. Win64 or Linux64, debug or release. Windows version info and icons configurable from emit rules.

mylang.parse / emit
emit 'program.root'
  setPlatform('win64');
  setBuildMode('exe');
  setOptimize('release-fast');
  include('cstdint', target.header);
  include('string',  target.header);
  func('main', 'int');
  emitChildren(node);
  returnVal('0');
  endFunc();
end

emit 'stmt.var_decl'
  declVar(
    getAttr(node, 'decl.name'),
    typeToIR(getAttr(node, 'sem.type')),
    exprToString(getChild(node, 0)));
end

emit 'stmt.if'
  ifStmt(exprToString(
    getChild(node, 0)));
  emitBlock(getChild(node, 1));
  endIf();
end

From .parse file to running binary.

Phase 1 reads your language description and configures the compiler in memory. Phase 2 uses that compiler to process source files and produce native binaries via Zig.

Phase 1
mylang.parse
Your language definition, all sections in one file
Phase 1
PLC Bootstrap
Executes .parse file, wires all handlers onto TParse
Phase 1 Done
Live TParse
Your compiler configured and ready in memory
Phase 2
Source File
myprogram.ml parsed by your grammar rules into AST
Phase 2
C++23 Emit
Fluent IR builder → .h + .cpp dual-file output
Output
Native Binary
Zig/Clang → exe · lib · dll · Win64 · Linux64

What ParseLang gives you.

01 / Core
Single .parse file: complete compiler

One file replaces every compiler component: lexer config, Pratt grammar rules, semantic handlers, C++23 emitters, pipeline config (platform, build mode, optimize), Windows version info, and icon embedding. No Delphi code written by you.

02 / Grammar
Pratt parsing built in

Top-down operator precedence ready to use. prefix, infix left/right, statement. The binaryop shorthand registers parse + emit in one line.

03 / Semantics
Scope trees and symbol resolution

Built-in nested scope trees. declare, lookup, lookupLocal. Type-text-to-kind resolution. Structured errors with source location and error codes.

04 / Emit
Fluent C++23 IR builder

Structured builder API. Functions, control flow, variable declarations, includes. exprToString() converts expression subtrees. Dual-file: source or header per call.

05 / Scripting
Full scripting language inside rules

Variables, if, while, for, repeat, string concatenation. Typed helper functions defined once, called from any rule block.

06 / Backend
Zig/Clang native targets

Compiles to exe, lib, or dll. Win64 and Linux64. debug, release-safe, release-fast, release-small. Windows version info and icons embeddable from emit rules.

Four sections.
Every compiler stage.

The entire compiler: tokeniser, Pratt parser, semantic analyser, code generator, configured in one file using ParseLang's scripting language.

01 / Lexer
Declare your tokens

Register every token the language needs. Keywords with case sensitivity control, multi-character operators (longest-match), string styles with independent escape config, comment styles, type keywords, and a typemap that resolves them to C++ type names.

  • Case-sensitive or insensitive keywords, per language
  • Multi-character operators: declare longest first
  • Multiple string styles with independent escape config
  • typemap resolves kind strings to C++ type names
  • structural sets terminator and block-close tokens
mylang.parse / lexer section
language MyLang;

keywords casesensitive
  'var'    -> 'keyword.var';
  'func'   -> 'keyword.func';
  'return' -> 'keyword.return';
  'if'     -> 'keyword.if';
  'else'   -> 'keyword.else';
  'while'  -> 'keyword.while';
  'end'    -> 'keyword.end';
  'true'   -> 'keyword.true';
  'false'  -> 'keyword.false';
end

operators
  ':=' -> 'op.assign';
  '<=' -> 'op.lte';
  '<>' -> 'op.neq';
  '+'  -> 'op.plus';
  '-'  -> 'op.minus';
  '*'  -> 'op.star';
  '('  -> 'delim.lparen';
  ')'  -> 'delim.rparen';
  ','  -> 'delim.comma';
  ':'  -> 'delim.colon';
  ';'  -> 'delim.semi';
end

strings
  '"' '"' -> 'literal.string' escape true;
end

comments
  line '--';
end

structural
  terminator 'delim.semi';
  blockclose  'keyword.end';
end

types
  'int'    -> 'type.int';
  'string' -> 'type.string';
end

typemap
  'type.int'    -> 'int64_t';
  'type.string' -> 'std::string';
end
02 / Grammar
Register Pratt handlers

Grammar rules are written in ParseLang's scripting language. prefix fires at expression start, infix left/right handles binary positions, statement fires at the top level. Binding powers control precedence.

  • registerLiterals wires all literal prefixes in one call
  • binaryop shorthand registers parse and emit together
  • result is the node to return; left is the infix left operand
  • Full scripting inside every rule block
  • Typed helper functions callable from any rule
mylang.parse / grammar
registerLiterals;

binaryop 'op.plus'  power 20 op '+';
binaryop 'op.minus' power 20 op '-';
binaryop 'op.star'  power 30 op '*';

prefix 'identifier' as 'expr.ident'
  parse
    result := createNode();
    setAttr(result, 'ident.name',
      current().text);
    consume();
  end
end

infix left 'delim.lparen'
    power 80 as 'expr.call'
  parse
    result := createNode();
    setAttr(result, 'call.name',
      getAttr(left, 'ident.name'));
    consume();
    if not check('delim.rparen') then
      addChild(result, parseExpr(0));
      while match('delim.comma') do
        addChild(result, parseExpr(0));
      end
    end
    expect('delim.rparen');
  end
end

statement 'keyword.var'
    as 'stmt.var_decl'
  parse
    result := createNode();
    consume();
    setAttr(result, 'decl.name',
      current().text);
    consume();
    expect('delim.colon');
    setAttr(result, 'decl.type_text',
      current().text);
    consume();
    expect('op.assign');
    addChild(result, parseExpr(0));
    expect('delim.semi');
  end
end
03 / Semantic
Analyse the tree

Semantic rules fire during the analysis pass. Nodes without a handler are walked transparently. Register only what you need; children are visited automatically.

  • Nested scope trees: pushScope / popScope
  • declare(name, node) returns false on duplicates
  • lookup walks full chain; lookupLocal stays in scope
  • Structured errors with source location and codes
  • typeTextToKind() resolves type text via lexer types
mylang.parse / semantic
semantic 'program.root'
  pushScope('global', node);
  visitChildren(node);
  popScope(node);
end

semantic 'stmt.func_decl'
  ok := declare(
    getAttr(node, 'func.name'), node);
  if not ok then
    error(node, 'ML001',
      'Duplicate function: '
      + getAttr(node, 'func.name'));
  end
  pushScope(
    getAttr(node, 'func.name'),
    node);
  visitChildren(node);
  popScope(node);
end

semantic 'stmt.var_decl'
  ok := declare(
    getAttr(node, 'decl.name'), node);
  if not ok then
    error(node, 'ML002',
      'Duplicate variable: '
      + getAttr(node, 'decl.name'));
  end
  setAttr(node, 'sem.type',
    typeTextToKind(
      getAttr(node, 'decl.type_text')));
  visitChildren(node);
end

semantic 'expr.ident'
  sym := lookup(
    getAttr(node, 'ident.name'));
  if sym = nil then
    error(node, 'ML003',
      'Undeclared: '
      + getAttr(node, 'ident.name'));
  end
end
04 / Emit
Generate C++23

Emit rules fire during code generation. Statement nodes call IR builder procedures. Expression nodes set the implicit result to their C++ string. Targets the source or header file per call.

  • Fluent IR: func, ifStmt, whileStmt, declVar, returnVal
  • Dual-file: target.source vs target.header
  • exprToString(node) converts expression trees to C++
  • typeToIR() resolves kind strings via the typemap
  • Pipeline config from emit rules: platform, mode, optimize
mylang.parse / emit
emit 'program.root'
  setPlatform('win64');
  setBuildMode('exe');
  setOptimize('debug');
  include('cstdint', target.header);
  include('string',  target.header);
  func('main', 'int');
  emitChildren(node);
  returnVal('0');
  endFunc();
end

emit 'stmt.var_decl'
  declVar(
    getAttr(node, 'decl.name'),
    typeToIR(getAttr(node, 'sem.type')),
    exprToString(getChild(node, 0)));
end

emit 'stmt.if'
  ifStmt(exprToString(
    getChild(node, 0)));
  emitBlock(getChild(node, 1));
  if childCount(node) > 2 then
    elseStmt();
    emitBlock(getChild(node, 2));
  end
  endIf();
end

emit 'expr.call'
  args := '';
  i := 0;
  while i < childCount(node) do
    if i > 0 then args := args + ', '; end
    args := args
      + exprToString(getChild(node, i));
    i := i + 1;
  end
  result := getAttr(node, 'call.name')
    + '(' + args + ')';
end

From download to
running compiler.

01
Download ParseLang

Grab the latest release from GitHub. Includes the PLC bootstrap compiler and the full Zig toolchain. No separate downloads needed.

02
Write the lexer section

Start with a language declaration. Add keywords, operators, strings, comments, types, and typemap blocks.

03
Register grammar rules

Call registerLiterals, then write binaryop, prefix, infix, and statement rules.

04
Add emit and semantic rules

Write an emit block for each AST node kind. Add semantic rules for scope and type analysis where needed.

05
Run PLC

Execute PLC mylang.parse myprogram.ml. ParseLang bootstraps your compiler, emits C++23, and Zig links a native binary.

hello.parse / complete example
language Hello;

-- Lexer ─────────────────────────
keywords casesensitive
  'print' -> 'keyword.print';
end
operators
  '(' -> 'delim.lparen';
  ')' -> 'delim.rparen';
end
strings
  '"' '"' -> 'literal.string' escape true;
end
comments
  line '--';
end

-- Grammar ─────────────────────
registerLiterals;

statement 'keyword.print'
    as 'stmt.print'
  parse
    result := createNode();
    consume();
    expect('delim.lparen');
    addChild(result, parseExpr(0));
    expect('delim.rparen');
  end
end

-- Emit ───────────────────────────
emit 'program.root'
  setPlatform('win64');
  setBuildMode('exe');
  include('cstdio', target.header);
  include('string', target.header);
  func('main', 'int');
  emitChildren(node);
  returnVal('0');
  endFunc();
end

emit 'stmt.print'
  val := exprToString(
    getChild(node, 0));
  stmt('printf("%s\n", '
    + val + '.c_str())');
end
Get ParseLang

Download.
Write.
Compile.

The release includes the PLC bootstrap compiler, all supporting binaries, and the full Zig toolchain, everything bundled. Unzip and start writing .parse files immediately.

RequirementMinimumNotes
Host OSWindows 10/11 x64Supported
Delphi (source builds)Delphi 12 AthensRequired only to build ParseLang from source
Linux targetWSL2 + Ubuntuwsl --install -d Ubuntu. ParseLang locates it automatically