npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

runar-compiler

v0.2.0

Published

Rúnar reference compiler (TypeScript): nanopass pipeline from TS to Bitcoin Script

Readme

runar-compiler

Rúnar reference compiler: TypeScript to Bitcoin Script via a 6-pass nanopass pipeline.

This package is the canonical compiler implementation. It reads .runar.ts, .runar.sol, .runar.move, and .runar.py source files, runs them through six sequential passes, and produces a compiled artifact containing the Bitcoin Script bytecode, the canonical ANF IR, and metadata.


Installation

pnpm add runar-compiler

API Usage

import { compile } from 'runar-compiler';

const source = `
import { SmartContract, assert, PubKey, Sig, hash160, checkSig } from 'runar-lang';

export class P2PKH extends SmartContract {
  readonly pubKeyHash: ByteString;

  constructor(pubKeyHash: ByteString) {
    super(pubKeyHash);
    this.pubKeyHash = pubKeyHash;
  }

  public unlock(sig: Sig, pubKey: PubKey) {
    assert(hash160(pubKey) === this.pubKeyHash);
    assert(checkSig(sig, pubKey));
  }
}
`;

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

console.log(result.success);      // true if no errors
console.log(result.scriptHex);    // hex-encoded Bitcoin Script
console.log(result.scriptAsm);    // human-readable ASM
console.log(result.artifact);     // full RunarArtifact
console.log(result.contract);     // parsed ContractNode AST
console.log(result.anf);          // ANF IR program
console.log(result.diagnostics);  // warnings and errors

compile() runs all 6 passes: Parse, Validate, Type-check, ANF Lower, Stack Lower (+ peephole optimize), and Emit (+ artifact assembly). It never throws -- all errors are caught and returned as diagnostics in the CompileResult. If a pass produces errors, subsequent passes are skipped and the partial result is returned.

CompileOptions

interface CompileOptions {
  /** Source file name for error messages and parser dispatch. Defaults to "contract.ts". */
  fileName?: string;

  /** If true, stop after parsing (Pass 1). */
  parseOnly?: boolean;

  /** If true, stop after validation (Pass 2). */
  validateOnly?: boolean;

  /** If true, stop after type-checking (Pass 3). */
  typecheckOnly?: boolean;

  /** Bake property values into the locking script (replaces placeholders). */
  constructorArgs?: Record<string, bigint | boolean | string>;
}

The fileName extension controls parser dispatch:

  • .runar.ts — TypeScript parser (ts-morph)
  • .runar.sol — Solidity-like parser (hand-written recursive descent)
  • .runar.move — Move-style parser (hand-written recursive descent)
  • .runar.py — Python parser (hand-written tokenizer with INDENT/DEDENT + recursive descent)

CompileResult

interface CompileResult {
  /** The ANF IR program (null if compilation stopped early or failed). */
  anf: ANFProgram | null;

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

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

  /** True if there are no error-severity diagnostics. */
  success: boolean;

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

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

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

Diagnostics

interface CompilerDiagnostic {
  message: string;
  loc?: SourceLocation;
  severity: Severity;
}

type Severity = 'error' | 'warning';

Both CompilerDiagnostic and the Severity type alias are exported from runar-compiler. No error code system — diagnostics use plain-text messages with optional source locations.

Constructor Slots and Argument Baking

The compiled artifact includes constructorSlots, which record the byte offsets of constructor parameter placeholders in the emitted script:

interface ConstructorSlot {
  paramIndex: number;   // index of the constructor parameter
  byteOffset: number;   // byte offset in the script hex where the placeholder lives
}

When constructorArgs are provided in CompileOptions, the compiler replaces ANF property initialValue fields before stack lowering. This produces a complete locking script with real push-data values instead of OP_0 placeholders. Without constructorArgs, the script contains placeholder bytes that must be spliced at deploy time using the constructorSlots offsets from the artifact.


Individual Pass Functions

Passes 1--4 are also exported individually for fine-grained use (passes 5--6 are internal):

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

// Pass 1: Parse
const parseResult = parse(source, 'MyContract.runar.ts');

// parse() may return null on fatal parse errors
if (!parseResult.contract) {
  console.error('Parse failed:', parseResult.errors);
} else {
  // Pass 2: Validate
  const validationResult = validate(parseResult.contract);

  // Pass 3: Type-check
  const typeCheckResult = typecheck(parseResult.contract);

  // Pass 4: ANF Lower
  const anf = lowerToANF(parseResult.contract);
}

Pass Return Types

Each pass function returns a structured result type (all exported from runar-compiler):

interface ParseResult {
  contract: ContractNode | null;   // null on fatal parse errors
  errors: CompilerDiagnostic[];
}

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

interface TypeCheckResult {
  typedContract: ContractNode;     // same AST, types verified
  errors: CompilerDiagnostic[];
}

lowerToANF returns an ANFProgram directly (throws on internal errors rather than returning diagnostics).


Pipeline Overview

  Source (.runar.ts / .runar.sol / .runar.move / .runar.py)
       |
       v
  +-----------+     +-----------+     +------------+
  |  Pass 1   | --> |  Pass 2   | --> |  Pass 3    |
  |  PARSE    |     |  VALIDATE |     |  TYPECHECK |
  +-----------+     +-----------+     +------------+
       |                 |                  |
    Rúnar AST        Validated AST      Typed AST
                                           |
                                           v
                     +------------+     +-----------+
                     |  Pass 4    | --> |  Pass 5   |
                     |  ANF LOWER |     |  STACK    |
                     +------------+     |  LOWER    |
                          |             +-----------+
                       ANF IR                |
                     (canonical JSON)     Stack IR
                                        (stack offsets)
                                             |
                                             v
                     +------------+     +------------+
                     |  Pass 6    | <-- |  Peephole  |
                     |  EMIT +    |     |  Optimize  |
                     |  Artifact  |     |  (always)  |
                     +------------+     +------------+
                          |
                     Bitcoin Script
                     (hex bytes)
                     + RunarArtifact

The peephole optimizer runs on Stack IR between passes 5 and 6 (always enabled). The constant folding optimizer is available between passes 4 and 5 but disabled by default to preserve ANF conformance.

Dedicated Codegen Modules

Certain operations have specialized code generators that produce optimized Bitcoin Script sequences:

  • src/passes/ec-codegen.ts — EC point operations (ecAdd, ecMul, ecMulGen, ecNegate, ecOnCurve, etc.)
  • src/passes/slh-dsa-codegen.ts — SLH-DSA (SPHINCS+) signature verification (verifySLHDSA_SHA2_* for all 6 FIPS 205 parameter sets)

These modules are invoked by the stack lowering pass (Pass 5) when the corresponding built-in calls are encountered.


Error Reporting

The compiler pipeline does not throw exceptions. All passes report errors by pushing CompilerDiagnostic objects into CompileResult.diagnostics via makeDiagnostic().

import { compile } from 'runar-compiler';

const result = compile(source);
if (result.diagnostics.length > 0) {
  for (const d of result.diagnostics) {
    console.error(`${d.severity}: ${d.message}`);
  }
}

The exported error classes (CompilerError, ParseError, ValidationError, TypeError) are available as types for consumer code but are never instantiated by the pipeline itself.


Design Decisions

Why Nanopass

Each pass is a self-contained module doing exactly one transformation. Bugs are localized: if the ANF IR is correct but the script is wrong, the problem is in Pass 5 or 6.

Why ANF over CPS/SSA

ANF is the natural fit for a stack machine target: it names every intermediate value (giving the stack scheduler something to work with), preserves evaluation order, and keeps control flow explicit (if/loop nodes map directly to OP_IF/OP_ENDIF).