Rúnar

Compiler API

The Runar compiler can be invoked programmatically in addition to the CLI. This page documents the public API for integrating the compiler into custom build pipelines, editor tooling, and automated workflows. The compiler is distributed as the runar-compiler npm package.

Importing the Compiler Module

import {
  compile,
  parse,
  parseSolSource,
  parseMoveSource,
  parsePythonSource,
  parseGoSource,
  parseRustSource,
  validate,
  typecheck,
  lowerToANF,
} from 'runar-compiler';

All functions are synchronous and pure — they take input, produce output, and have no side effects. They do not read from the filesystem or network. If you need to compile a file, read it first and pass the source string.

compile()

The primary entry point. Takes a source string and returns a compiled artifact.

Signature

function compile(source: string, options?: CompileOptions): CompileResult;

Parameters

source: string — The Runar contract source code. Can be TypeScript, Solidity, Move, Python, Go, or Rust syntax. The language is auto-detected from the fileName option’s file extension.

options?: CompileOptions — Optional configuration:

interface CompileOptions {
  // Source file name for error messages and parser dispatch.
  // The file extension determines which parser is used:
  // .runar.ts → TypeScript, .runar.sol → Solidity, .runar.move → Move,
  // .runar.py → Python, .runar.go → Go, .runar.rs → Rust.
  // Defaults to "contract.ts".
  fileName?: string;

  // If true, stop after the parse pass (Pass 1).
  parseOnly?: boolean;

  // If true, stop after the validate pass (Pass 2).
  validateOnly?: boolean;

  // If true, stop after the type-check pass (Pass 3).
  typecheckOnly?: boolean;

  // Bake constructor argument values into the locking script,
  // replacing OP_0 placeholders with actual values.
  constructorArgs?: Record<string, bigint | boolean | string>;

  // If true, skip the ANF constant folding pass.
  // Constant folding is enabled by default.
  disableConstantFolding?: boolean;
}

Return Value

interface CompileResult {
  // True if there are no error-severity diagnostics.
  success: boolean;

  // The parsed contract AST (available after Pass 1).
  contract: ContractNode | null;

  // The ANF IR program (available after Pass 4, null if compilation stopped early or failed).
  anf: ANFProgram | null;

  // All diagnostics (errors and warnings) from all passes.
  diagnostics: CompilerDiagnostic[];

  // The compiled artifact (available if all 6 passes succeed).
  artifact?: RunarArtifact;

  // Hex-encoded Bitcoin Script (available if all 6 passes succeed).
  scriptHex?: string;

  // Human-readable ASM representation (available if all 6 passes succeed).
  scriptAsm?: string;
}

Example

import { compile } from 'runar-compiler';
import { readFileSync } from 'node:fs';

const source = readFileSync('./contracts/P2PKH.runar.ts', 'utf8');

const result = compile(source, {
  fileName: 'P2PKH.runar.ts',
});

if (result.success) {
  console.log('Contract:', result.artifact.contractName);
  console.log('Script hex:', result.scriptHex);
  console.log('ASM:', result.scriptAsm);
} else {
  for (const diag of result.diagnostics) {
    console.error(`${diag.message}`);
  }
}

parse()

Parses a Runar source string into a ContractNode AST. The parser is auto-dispatched based on the fileName extension (.runar.ts, .runar.sol, .runar.move, .runar.py, .runar.go, .runar.rs). Does not perform validation or type checking.

Signature

function parse(source: string, fileName?: string): ParseResult;

Parameters

source: string — The Runar contract source code in any supported language.

fileName?: string — File name used for error messages and to determine which parser to use. Defaults to "contract.ts".

Return Value

interface ParseResult {
  contract: ContractNode | null;  // The root AST node (null if parsing failed)
  errors: CompilerDiagnostic[];   // Parse errors
}

Example

const parseResult = parse(source, 'P2PKH.runar.ts');
if (parseResult.contract) {
  console.log('Contract name:', parseResult.contract.name);
  console.log('Methods:', parseResult.contract.methods.map(m => m.name));
}

Language-Specific Parsers

Runar supports six frontend languages. Each has a dedicated parser that produces the same ContractNode AST. In addition to parse() (which auto-dispatches by file extension), each parser is also available as a standalone function:

parseSolSource()

function parseSolSource(source: string): ParseResult;

Parses Solidity-syntax Runar contracts. Accepts standard Solidity contract declarations with Runar-specific annotations.

const result = parseSolSource(`
  pragma runar ^0.1.0;
  contract P2PKH {
    Ripemd160 pubKeyHash;
    function unlock(Sig sig, PubKey pubKey) public {
      require(hash160(pubKey) == pubKeyHash);
      require(checkSig(sig, pubKey));
    }
  }
`);

parseMoveSource()

function parseMoveSource(source: string): ParseResult;

Parses Move-syntax Runar contracts. Accepts Move module declarations with Runar-specific types.

const result = parseMoveSource(`
  module P2PKH {
    struct Contract has key {
      pub_key_hash: Ripemd160,
    }
    public fun unlock(sig: Sig, pub_key: PubKey, self: &Contract) {
      assert!(hash160(pub_key) == self.pub_key_hash);
      assert!(check_sig(sig, pub_key));
    }
  }
`);

parsePythonSource()

function parsePythonSource(source: string): ParseResult;

Parses Python-syntax Runar contracts. Accepts class definitions with type annotations.

const result = parsePythonSource(`
  class P2PKH(SmartContract):
    pub_key_hash: Ripemd160

    def unlock(self, sig: Sig, pub_key: PubKey):
      assert hash160(pub_key) == self.pub_key_hash
      assert check_sig(sig, pub_key)
`);

parseGoSource()

function parseGoSource(source: string): ParseResult;

Parses Go-syntax Runar contracts. Accepts Go struct definitions with runar struct tags.

parseRustSource()

function parseRustSource(source: string): ParseResult;

Parses Rust-syntax Runar contracts. Accepts Rust struct definitions with #[runar::contract] attribute macros.

validate()

Performs semantic validation on a parsed AST. Checks for disallowed language features, missing constructors, improper super() calls, and structural correctness.

Signature

function validate(contract: ContractNode): ValidationResult;

Return Value

interface ValidationResult {
  errors: CompilerDiagnostic[];
  warnings: CompilerDiagnostic[];
}

Example

const parseResult = parse(source);
if (parseResult.errors.length === 0) {
  const validationResult = validate(parseResult.contract);
  if (validationResult.errors.length > 0) {
    for (const err of validationResult.errors) {
      console.error(`Validation: ${err.message}`);
    }
  }
}

typecheck()

Performs type checking on a validated AST. Verifies type correctness, affine type usage, and on-chain type compatibility.

Signature

function typecheck(contract: ContractNode): TypeCheckResult;

Return Value

interface TypeCheckResult {
  typedContract: ContractNode;
  errors: CompilerDiagnostic[];
}

Example

const typeResult = typecheck(parseResult.contract);
if (typeResult.errors.length > 0) {
  for (const err of typeResult.errors) {
    console.error(`Type error: ${err.message}`);
  }
}

lowerToANF()

Lowers a type-checked AST to Administrative Normal Form (ANF) intermediate representation. This is the IR that feeds into the Bitcoin Script code generator.

Signature

function lowerToANF(contract: ContractNode): ANFProgram;

Return Value

interface ANFProgram {
  name: string;
  methods: ANFMethod[];
  stateFields: ANFField[];
  constructorParams: ANFField[];
}

interface ANFMethod {
  name: string;
  params: ANFField[];
  body: ANFExpression[];
}

Example

const anf = lowerToANF(parseResult.contract);
console.log('ANF methods:', anf.methods.map(m => m.name));
console.log('ANF body ops:', anf.methods[0].body.length);

The ANF IR is useful for custom analysis tools, optimization passes, or feeding into the RunarInterpreter for reference execution.

Error Types

All error objects share a common shape:

interface CompilerDiagnostic {
  message: string;        // Human-readable error description
  loc?: SourceLocation;   // Source location (file, line, column)
  severity: 'error' | 'warning';
}

interface SourceLocation {
  file?: string;
  line?: number;
  column?: number;
}

Error Codes

CodeDescription
E001Parse error: syntax error
E002Parse error: unexpected token
E010Validation: disallowed language feature
E011Validation: missing constructor
E012Validation: missing super() call
E020Type error: type mismatch
E021Type error: affine type used more than once
E022Type error: unknown type
E030Codegen: script too large
E031Codegen: estimated stack overflow
W001Warning: large script size
W002Warning: unused variable
W003Warning: shadowed variable

Plugin and Extension Points

The compiler pipeline is structured as a sequence of phases: parse, validate, typecheck, lower, optimize, emit. Each phase is a standalone function, so you can insert custom processing between any two phases:

import { parse, validate, typecheck, lowerToANF } from 'runar-compiler';

// Custom pipeline with analysis between phases
const ast = parse(source).contract;
validate(ast);
typecheck(ast);

// Insert custom analysis here
const complexity = analyzeComplexity(ast);
if (complexity > threshold) {
  console.warn('Contract complexity exceeds recommended threshold');
}

const anf = lowerToANF(ast);
// Feed ANF to custom code generator or analysis tool

This modular design makes it straightforward to build editor integrations (parse + typecheck for diagnostics), documentation generators (parse + extract metadata), or custom optimizers (lower + transform + emit).