LM lmscript.io Writing LMCode
Step-by-Step Author Guide

Writing LMCode Programs

This tutorial walks through everything you can write inside an _lmcode_ ... end _lmcode_ block and how outer LMScript compiles, inspects, and runs the resulting program.

1. Opening an _lmcode_ Block: cli= vs schema=

Every block binds to a single host schema using either cli="PROGRAM [SUBCOMMAND...]" or schema="path/to/schema.json". A real run may resolve the CLI with PROGRAM ... --lmcode-schema --json; --check and --dry-run instead use explicit schema files or cached schemas, never discovery subprocesses or downloads. In a real run, an optional install="github:OWNER/REPO@TAG" downloads and SHA-256 verifies a missing CLI binary into ~/.cache/lmscript/tools/.

block_openers.lms
# Option A: Bind directly to an installed (or auto-installed) host CLI
p1 = _lmcode_ cli="lmaws" install="github:lmctl-ai/lmaws@v0.1.34";
  aws.demo().zones();
end _lmcode_

# Option B: Bind to a schema file on disk (great for offline compilation & tests)
p2 = _lmcode_ schema="host.schema.json";
  let base = money(500);
  shop.add("book", base);
end _lmcode_

2. Executing Programs: run_lmcode, lmcode_build & load_schema

execution_modes.lms
# 1. Single program execution
r = run_lmcode(p1);
if (r.ok) {
  println("Success result: " + json_stringify(r.result));
} else {
  error(f"Failure: {r.get('message', 'host execution failed')}");
}

# 2. Multi-slot builder execution (runs multiple compiled slots in one host invocation)
b = lmcode_build("lmaws");
b.add(p1);
b.add(p1);
batch = run_lmcode(b);
# batch.result is a list of per-slot results; batch.parts == 2

# 3. Inspect host schema metadata at runtime
schema = load_schema(cli="lmaws" install="github:lmctl-ai/lmaws@v0.1.34");
println(type(schema));

3. Static Types, Wrapping int64 & Explicit Conversions

LMCode has four primitive value types (int, float, string, bool), void for non-returning functions, standard Json, plus schema-declared object and enum types.

4. Quote-Lists (qw, qi, qf), Map Literals (%(...)) & JSON Heredocs

rich_literals.lms
prog = _lmcode_ cli="lmaws" install="github:lmctl-ai/lmaws@v0.1.34";
  # 1. Quote-word list -> StrList (0-indexed!)
  let regions = qw(
    us-east-1
    us-west-2   # comments inside qw/qi/qf are stripped
    eu-west-1
  );

  # 2. Quote-int list -> IntList (accepts full signed int64 range!)
  let limits = qi(-9223372036854775808 0 1024 9223372036854775807);

  # 3. Quote-float list -> FloatList (accepts scientific notation & integers)
  let weights = qf(1 0.5 2.5e-3);

  # 4. Homogeneous map literals -> StrMap, IntMap, or FloatMap
  let headers = %(
    cache_control => "public, max-age=300",
    content_type  => "text/html; charset=utf-8",
  );
  let ports = %(http => 80, https => 443);

  # 5. Dedented raw heredoc -> string
  let policy_note = <<~TXT;
    Managed by lmscript.io
    Do not edit manually.
  TXT

  # 6. Validated JSONC heredoc -> immutable Json object (checked at compile time!)
  let config = json <<~JSON;
    {
      // JSONC comments and trailing commas are allowed here!
      "domain": "lmscript.io",
      "ttl": [60, 300,],
    }
  JSON

  lmcode_return_put("first_region", regions.at(0));
  lmcode_return_put("https_port", ports.get("https"));
  lmcode_return_put("domain", config.get("domain").as_string());
end _lmcode_

5. ${expr} Splices from Outer LMScript

Before compiling an _lmcode_ block, LMScript expands ${expr} splices using values from the enclosing script. Splices occupy whole expression positions; do not place them inside quoted strings. Numbers, booleans, strings, lists, and maps are captured as typed literals. (Raw heredocs <<TAG and <<~TAG never interpolate ${...}.)

splice_expansion.lms
target_domain = "lmscript.io";
max_ttl_sec   = 3600;

prog = _lmcode_ cli="lmaws" install="github:lmctl-ai/lmaws@v0.1.34";
  let dom = ${target_domain};
  let ttl = ${max_ttl_sec};
  lmcode_return_put("domain", dom);
  lmcode_return_put("ttl", ttl);
end _lmcode_

6. Control Flow, switch, Loops & Compile-Time Inlined Functions

LMCode supports if / else if / else, switch (expr) { case K: { ... } default: { ... } } (braced arms, no fallthrough), while (cond) { ... }, for (i in range(start, end)), and for (x in collection) over IntList, FloatList, StrList, and Json arrays.

Helper functions declared with fn name(a: T) -> R { ... } are inlined at compile time (DSLB v6 has no runtime call stack for user functions, so direct or mutual recursion is rejected at compile time).

functions_and_switch.lms
prog = _lmcode_ cli="lmaws" install="github:lmctl-ai/lmaws@v0.1.34";
  fn clamp_ttl(sec: int) -> int {
    let result = sec;
    if (sec < 60) {
      result = 60;
    } else if (sec > 86400) {
      result = 86400;
    }
    return result;
  }

  let env_name = "prod";
  let ttl = 0;
  switch (env_name) {
    case "dev": { ttl = clamp_ttl(30); }
    case "staging": { ttl = clamp_ttl(30); }
    case "prod": { ttl = clamp_ttl(300); }
    default: { ttl = 120; }
  }
  lmcode_return_put("effective_ttl", ttl);
end _lmcode_

7. Host Context Objects, Operator Mapping, Conversions & Enums

A host schema can expose pre-bound context objects (such as aws in lmaws or shop and ledger in the shop samples), custom binary/unary operators (+, -, *, neg, r+, r-, r*), single-step conversions (e.g., int -> Money via money(int)), and strongly typed enums (Currency.USD).

8. Annotated SAM Rule Entries (@name fn ... implements Interface)

When a host needs to invoke callbacks inside your compiled bytecode (such as pricing rules, CAD parametric constraints, or HTTP route handlers in the rest module), declare a top-level function with an @annotation and an implements InterfaceName clause:

rule_entries.lms
prog = _lmcode_ schema="host.schema.json";
  @seasonal
  fn discount_rule(m: Money, qty: int) -> Money implements Adjuster {
    let out = m;
    if (qty >= 10) {
      out = m - 100;   # int converts to Money via the schema conversion
    }
    return out;        # return is only allowed as the final top-level statement
  }

  @loyalty
  fn score_order(m: Money) -> int implements Score {
    return m.cents() / 100;
  }
end _lmcode_

9. Error Phases & Located Diagnostics

PhaseWhen It HappensBehavior
Compile (dsl.ts)During lmscript --check or before script executionExits with code 2 and exact error:FILE:LINE:COL: _lmcode_: ... diagnostic.
CLI (phase: "cli")Invalid host flags or manifest mismatchHost writes failure envelope with phase="cli" (exit 2); run_lmcode returns { ok: false, phase: "cli", ... }.
Load (phase: "load")Schema hash mismatch or unbound importHost verifier catches before execution and returns LOAD_SCHEMA_MISMATCH or LOAD_UNBOUND (exit 3).
Run (phase: "run")Division by zero, index out of bounds, or host method failureHost maps bytecode PC through the region's line table and returns exact 1-based line and column (exit 3).
App (phase: "app")Post-body domain validation failure in the hostHost reports domain error with phase="app" (exit 4).