Help us shape the future of Go! Take the 2026 Go Developer Survey

lang

package
v0.50.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

README

extensions/lang — inline polyglot execution

Add @require lang to a .nflow file and you can run code in any language the bundle ships directly from the pipeline:

@require lang

{ "x": 2, "y": 3 } -> py"import json, sys; v=json.load(sys.stdin); print(json.dumps({'value': v['x'] + v['y']}))"

{ "x": 2, "y": 3 } -> py {
    import sys, json
    data = json.load(sys.stdin)
    print(json.dumps({"value": data["x"] + data["y"]}))
}

Each tag (py, rs, go, ...) is a keyword in the nflow grammar — same dispatch mechanism as loop and until. The tag compiles to a single LangBlock AST node. The runtime materializes the source to a temp file, resolves the interpreter, and delegates execution to extensions/external. No new subprocess engine lives in this bundle.

Existing scripts can be called without writing external.exec options. The path is resolved beside the .nflow file; it is passed directly to the selected interpreter, not through a shell:

@require lang

{ "samples": [1.2, 1.8, 2.1] } -> py.file "./analysis.py"

The script reads the incoming JSON from stdin and writes its result as JSON to stdout, just like an inline block. Imports and the original script filename are preserved. @include remains for importing .nflow modules; use py.file for Python programs. Other runner aliases also provide a .file form, such as js.file "./report.js" or rs.file "./tool.rs".

Missing files remain an error unless that call opts in with :autocreate:

{ "orders": [] } -> go.file "./normalize.go" :autocreate

The runner creates the file beside the .nflow source from that language's file_template. It uses exclusive creation, so an existing file is never overwritten. The starter reads one JSON value from stdin and writes one JSON value to stdout; edit the generated source to add processing. If a runner has no template, autocreate returns an error instead of guessing source code.

The @ prefix is not used for tags — the nflow grammar already uses @ for atom modifiers (@config, @prompt). Keywords are the established idiom for in-expression dispatch.

Data-driven runners (no Go code per language)

Every language is defined by a runner file under runners/*.nflow, not by Go code. The bundle embeds these files via //go:embed and loads them at init() through LoadRunners(). This mirrors the nexssp/ai pattern (SourceModules exposed through compiler.Bundle).

Adding a language = adding one runners/<id>.nflow file. Zero Go changes.

Runner file format
# runners/python.nflow (strict JSON; `_comment` is an optional note)
{
    "_comment": "Python (default).",
    "id": "python",
    "ext": ".py",
    "aliases": ["py", "python"],
    "find": ["python3", "python", "py -3"],
    "stdin": "json",
    "stdout": "json",
    "file_template": "import json\\nimport sys\\n\\ninput_value = json.load(sys.stdin)\\noutput_value = input_value  # TODO: transform input\\njson.dump(output_value, sys.stdout, separators=(\\\",\\\", \\\":\\\"))\\nsys.stdout.write(\\\"\\\\n\\\")\\n",
    "install": {
        "windows": ["winget", "install", "Python.Python.3.12"],
        "darwin": ["brew", "install", "python@3.12"]
    }
}

Versioned variants are separate files:

# runners/python3.nflow
{
    "id": "python",
    "version": "3",
    "ext": ".py",
    "aliases": ["py3", "python3"],
    "find": ["python3", "py -3"],
    "stdin": "json",
    "stdout": "json"
}
Fields
Field Required Description
ext yes File extension for materialized source, incl. the dot
aliases no Keywords (py, python) that resolve to this spec
find yes Candidate command lines tried in order; first on PATH wins
stdin no json (default) or text/raw
stdout no json (default) or text/raw
file_template no Starter source for .file :autocreate; requires JSON stdin and stdout
install no Per-OS auto-install recipe; omit an OS entry to skip

Runner files are strict JSON: keys and string values must be quoted, and trailing commas are not accepted. Use the optional _comment field for notes. The same optional file_template field is accepted by @lang.define and @lang.runner, so new languages can participate without Go-side template code.

Tags and aliases

Short and long forms both work. Aliases are derived from the runner files at init() — the table is built, not hand-maintained.

Tag Runner file Version
py runners/python.nflow default
python runners/python.nflow default
py2 runners/python2.nflow 2
python2 runners/python2.nflow 2
py3 runners/python3.nflow 3
python3 runners/python3.nflow 3
rs runners/rust.nflow default
rust runners/rust.nflow default
go runners/go.nflow default
golang runners/go.nflow default
ts runners/typescript.nflow default
typescript runners/typescript.nflow default
js runners/javascript.nflow default
node runners/javascript.nflow default
sh runners/shell.nflow default
bash runners/shell.nflow default

Inline Python dependencies

Python scripts can declare dependencies using PEP 723. If the source contains the # /// script marker, the runner uses uv run so uv can read the metadata and manage the environment:

@require lang
py {
# /// script
# dependencies = ["httpx"]
# ///
import json, httpx
print(json.dumps(httpx.__version__))
}

Preflight checks that uv is on PATH and reports it if missing; it does not install uv. Without the marker, Python uses the ordinary interpreter selected by the runner profile.

Preflight — the pre-run gate

Before any LangBlock executes, the compiler checks every interpreter used by the flow. It first searches PATH; if one is missing, it runs that runner's per-OS install recipe and resolves it again. If installation is unavailable or fails, the preflight groups the remaining missing interpreters into one report and the flow does not run.

lang preflight: missing interpreters — install before running:
  lang.python@3 (keyword py3)
  lang.rust (keyword rs)

The normal @require lang compile preflight uses the runner's installer automatically. A direct call to Preflight.Run remains find-only unless the caller supplies an installer explicitly:

pf := lang.Preflight{Installer: myInstaller}
report := pf.Run(blocks)
// report.OK() is true if every interpreter was either present or installed

Install recipes are data-driven in each runner profile. Python's default and py3 profiles provide Windows, macOS, and Linux recipes. Installation requires the relevant package manager and its normal permissions; on failure, NotInstalledError carries the cause. PEP 723 scripts still require uv, which is checked separately and is not automatically installed.

Stream contract

By default, the incoming stream value is JSON-encoded to stdin and stdout is JSON-decoded back into the next atom's input. sh and any language declared with "stdin": "text" use raw strings instead.

A language with no output produces nil in the stream — not an error. Empty stdin is also nil, not null.

@lang.define — register a language inline

Same data model as a runner file, declared in-flow:

@lang.define "inlinepy" {
    "id": "inlinepy",
    "ext": ".py",
    "aliases": ["inlinepy"],
    "find": ["python3"],
    "stdin": "json",
    "stdout": "text"
}
{ "value": "x" } -> inlinepy"import sys; sys.stdout.write('ok')"

Definitions use strict JSON and their aliases are made available while that flow is compiled. They remain in the process-wide language registry after compilation, so long-running hosts must not recompile definitions with the same ID or alias; use unique definitions or register runners once in host code.

Trust model

py"..." is arbitrary code execution. So was external.shell before this bundle existed — the threat model does not change, only the ergonomics of doing the dangerous thing get better. A sandbox is not provided.

Why this bundle is opt-in

The bundle is linked and registered for the CLI's @require loader, but is not mounted in native.Bundles(). Only flows that add @require lang receive its directives, keywords, and preflight checks.

Package layout

The Go sources implement the bundle, parser primary, runner registry, preflight validator, temp-file lifecycle, and adapter to extensions/external. Runner profiles live in runners/; executable conformance flows live in nflows/. The CLI keeps this bundle out of its default mount set, and the repository self-test suite runs its fixtures while skipping only when a required interpreter is absent.

Documentation

Overview

Package lang — file-scope directives for runner definitions.

@lang.runner and @lang.define are compiler.Directive extensions, like @include and @assert. They run at preprocess time (line-based), before the parser sees the expression grammar.

@lang.runner is the file-scope form of what runners/*.nflow ships as data: it lets a flow declare a language inline. @lang.define is the runtime-only form (no install recipe) — the data model is shared.

Package lang ships inline polyglot execution through the `lang` bundle, using directives and bare keyword aliases such as py, rs, go, and ts.

Each tag compiles to a single LangBlock AST node. The runtime materializes the source to a temp file, resolves the interpreter, and delegates execution to extensions/external via the executor seam (see exec.go). No new subprocess engine lives in this package.

Typical use:

@require lang
{ "x": 2, "y": 3 } -> py"import json, sys; v=json.load(sys.stdin); print(json.dumps({'value': v['x'] + v['y']}))"

py {
    import sys, json
    data = json.load(sys.stdin)
    print(json.dumps({"len": len(data)}))
}

The bundle is registered for @require but is not mounted in the default native bundle set. Add @require lang to a flow that wants the keywords.

Package lang — parser integration.

Index

Constants

View Source
const ID = "lang"

ID is the bundle identifier. Short, stable, never an import path. Two extensions with the same ID panic at compiler.Register.

Variables

View Source
var DefineDirective = compiler.Directive{
	Name:    "lang.define",
	Example: `@lang.define "gleam" { "id": "gleam", "ext": ".gleam", "find": ["gleam", "run"] }`,
	Handler: handleDefineDirective,
}

DefineDirective is the @lang.define directive. Same data model, but intended for runtime-only languages (no install recipe). The body format is identical to @lang.runner.

View Source
var ErrNotFound = errors.New("interpreter not found")

ErrNotFound is the sentinel from lookPathCmd. Tests assert against this rather than a string match.

View Source
var Registry = map[string]*Language{}

Registry is the process-global table of known languages. Populated at init() by LoadRunners (from embedded runners/*.nflow) and at parse time by RegisterDefine (define.go) when @lang.define is used.

Keyed by (ID, Version) — versioned variants of the same language (python "", python "2", python "3") are distinct entries. The unversioned Lookup(id, "") returns the default variant.

Two languages with the same (ID, Version) panic — silent override would let a stale registration win and produce confusing runtime behavior.

View Source
var RunnerDirective = compiler.Directive{
	Name:    "lang.runner",
	Example: `@lang.runner "python" { "id": "python", "ext": ".py", "find": ["python3"] }`,
	Handler: handleRunnerDirective,
}

RunnerDirective is the @lang.runner directive. It parses the runner spec from the directive body and registers it via registerSpec (the same path LoadRunners uses for embedded runner files).

Syntax (file-scope, one per line until the closing }):

@lang.runner "python" {
    "id": "python",
    "ext": ".py",
    "aliases": ["py", "python"],
    "find": ["python3", "python"],
    "stdin": "json",
    "stdout": "json",
    "install": {
        "windows": ["winget", "install", "Python.Python.3.12"],
        "darwin": ["brew", "install", "python@3.12"]
    }
}

The body is strict JSON, as in embedded runners/*.nflow files. Use an optional `_comment` field for notes. The same ParseRunner handles both.

Functions

func Bundle

func Bundle(_ map[string]string) compiler.Bundle

Bundle wires the language tag keywords, the @lang.runner and @lang.define directives, and the embedded runner source modules.

It returns the contract documented in extensions/README.md: ID, Libraries, Directives, Primaries, Fixtures, SourceModules.

  • Primaries: contributed by OnPreprocess from the registered aliases. Adding an alias to a runner file makes it available without a Go edit.
  • Directives: @lang.runner and @lang.define for inline declarations. See lang_directive.go.
  • SourceModules: embedded runners/*.nflow, mirroring the nexssp/ai pattern. Consumers can @include runner files as data.

Feature logic lives in sibling files; this file does only wiring.

func Install

func Install(id, version string) (cmd string, args []string, err error)

Install is the package-level entry point used by `nflow lang install <id>`. Wraps installEnsure so callers don't construct the type.

func LoadRunners

func LoadRunners() error

LoadRunners parses every runners/*.nflow file from the embed and registers each as a Language. Called once at init(); idempotent across reloads (duplicate registration panics — that's the project rule for builtins).

One bad runner file does NOT kill the binary: registerSpec's panic is recovered and returned as an error, so the caller sees "file X failed because Y" rather than a process crash. This is the resilience rule — a malformed data file is user input, not a reason to panic the host.

func Register

func Register(l Language)

Register adds a language to the Registry. Duplicate (ID, Version) panic, matching the project rule: "collisions panic; missing contracts fail loudly."

func RegisterDefine

func RegisterDefine(d Define) error

RegisterDefine adds a user-defined language to the Registry.

Called by the @lang.define directive. Go hosts may also register a language directly before compiling flows.

Returns an error rather than panicking: user-defined languages fail gracefully, while builtins (which the developer shipped intentionally) panic on duplicate registration via Register.

func Resolve

func Resolve(lang *Language, version string) (cmd string, args []string, err error)

Resolve is the single entry point for "give me a runnable command."

Tries the language's Select (find-only). On miss, calls ensureHook, which installs and re-resolves by default or returns a NotInstalledError if the runner has no recipe or installation fails. Context-scoped internal callers may disable installation without changing this production default.

Preflight, runLangBlock, and the CLI all go through here. There is no other call site for lang.Select — Resolve is the gate.

func WithAutoInstallDisabled added in v0.48.0

func WithAutoInstallDisabled(ctx context.Context) context.Context

WithAutoInstallDisabled marks a context so language preflight and runtime resolution report missing interpreters without invoking package managers. Self-test uses this to avoid changing the host environment.

Types

type Alias

type Alias struct {
	Tag     string // "py2", "python3", "golang"
	ID      string // canonical language ID, e.g. "python"
	Version string // "", "2", "3"
}

Alias maps a tag (e.g. "py") to a (Language.ID, Version) pair. The runtime never sees aliases; resolveAlias is the only exit point.

Aliases are derived from runner files at init() via registerAlias — the table is built, not hand-maintained. The TestAliasInvariants test asserts the derived table matches what the runner files declare.

type Check

type Check struct {
	ID      string
	Version string
	Tag     string // first alias that resolved to this (ID, Version), for hints
	Cmd     string // resolved command, "" if missing
	Err     error  // non-nil if missing
}

Check is one language examined during preflight.

type Define

type Define struct {
	ID           string   // required, e.g. "gleam"
	Version      string   // optional, e.g. "3"
	Ext          string   // required, including dot, e.g. ".gleam"
	Aliases      []string // optional, e.g. ["gleam", "beam"]
	Find         []string // required, e.g. ["gleam", "run"]
	Stdin        string   // "json" (default) | "text" | "raw"
	Stdout       string   // "json" (default) | "text" | "raw"
	FileTemplate string   // optional JSON-in/JSON-out source for .file :autocreate
	Install      OSRecipe // optional, per-OS install commands
}

Define is the in-flow @lang.define directive's data model.

It lets users register a language the bundle doesn't ship built-in, without forking the repo or writing a runner file. Example DSL:

@lang.define "gleam" {
    "id": "gleam",
    "ext": ".gleam",
    "aliases": ["gleam"],
    "find": ["gleam", "run"],
    "stdin": "json",
    "stdout": "text"
}
{ "value": "x" } -> gleam"..."

This is the runtime equivalent of a runners/*.nflow file — same fields, same RunnerSpec underneath. Definitions update the process-wide registry and remain registered for the process lifetime; each (ID, Version) and alias must be declared only once.

type Ensure

type Ensure interface {
	Ensure(lang *Language, version string) (cmd string, args []string, err error)
}

Ensure is the auto-install seam.

Resolve calls Ensure when a language's Select (find-only) fails. The default installEnsure runs the runner's per-OS recipe and re-resolves the interpreter; tests or hosts can replace the hook when needed.

Pure Go: no nexssp/ensure, no Node.js. Recipes call winget/brew/apt/npm directly. See install.go.

type ExecError

type ExecError struct {
	ID     string
	Cmd    string
	Cause  error
	Stderr string
}

ExecError is the type returned when the interpreter subprocess fails. Stderr is a string (not []byte) — matches external.ExecResult.Stderr and avoids the []byte(string) copy.

func (*ExecError) Error

func (e *ExecError) Error() string

func (*ExecError) Unwrap

func (e *ExecError) Unwrap() error

type LangBlock

type LangBlock struct {
	ID         string // "python" — canonical, never the tag
	Version    string // "", "2", "3"
	Source     string // source code text
	Tag        string // source keyword, including ".file" when using a script file
	SourceFile string // absolute path for a file-backed script; empty for inline source
	// Pos is the source position supplied by the compiler parser.
	Pos any
}

LangBlock is the AST node produced by language keywords such as py, rs, and go.

All aliases (py, python, python3) resolve to a LangBlock with the same ID and a Version reflecting the alias. The runtime never sees the tag — by the time we reach materialize/exec, the canonical ID is the only thing in scope.

func ParseFileTag

func ParseFileTag(tag, sourceFile, source string, pos any) (*LangBlock, error)

ParseFileTag creates a language block that runs an existing source file. The caller resolves the path relative to the containing .nflow file and supplies the file contents so preflight can inspect dependency metadata.

func ParseTag

func ParseTag(tag, source string, pos any) (*LangBlock, error)

ParseTag converts a keyword like "py" or "python3" into a LangBlock ready for the runtime. The parser hook reads the keyword and source literal/block, then calls this function.

ParseTag fails fast on one condition, surfaced to the user as a compile-time error rather than a runtime surprise:

  1. Unknown keyword (not in the alias table).

Interpreter presence is NOT checked here — that's preflight's job. ParseTag produces the AST node; the compiler calls Preflight.Run on the collected blocks before any runtime invocation. This separation means a flow with py and rs where rust is missing reports both checks in one pass, not whichever keyword happened to parse first.

The caller is expected to wrap the returned error in compiler.SourceError(pos, ...) before it leaves the parser. We don't do that here because compiler.SourceError's signature varies across versions and we don't want this file coupled to it.

type Language

type Language struct {
	// ID is the canonical language identifier, e.g. "python".
	ID string

	// Ext is the file extension used when materializing source to disk,
	// including the leading dot, e.g. ".py".
	Ext string

	// Select resolves a version string to a runnable command. Select is
	// FIND-ONLY: it never installs. Resolve() is the gate that calls
	// Ensure on miss. Preflight uses Select (not Resolve) to detect missing
	// interpreters without triggering install.
	Select func(version string) (cmd string, args []string, err error)

	// Encode serializes the incoming stream value to bytes for stdin.
	// JSON is the default; shell/raw languages use text.
	Encode func(v any) ([]byte, error)

	// Decode parses stdout back into a typed value the next atom sees.
	// Takes string (not []byte) to avoid the []byte(string) copy from
	// external.ExecResult.Stdout. JSON is the default; shell/raw return string.
	Decode func(s string) (any, error)
	// contains filtered or unexported fields
}

Language is the runtime view of a runner spec. One per (language, version). Tags are gone by here — runner file aliases are applied at parse time, and from this struct onward every code path speaks the canonical ID ("python"), never the tag ("py").

One struct, every language. New languages add a runners/<id>.nflow file, zero Go changes. See runners.go.

func Lookup

func Lookup(id, version string) (*Language, error)

Lookup returns the registered language for an (ID, Version) pair. The exact version is matched — Lookup("python", "3") returns the v3 variant, Lookup("python", "") returns the default.

func (*Language) Version

func (l *Language) Version() string

Version returns the language's version string. Reads from spec.Version when a spec is attached (the runner-file path); "" otherwise (the @lang.define path stores version on the spec too via RegisterDefine).

type NotInstalledError

type NotInstalledError struct {
	ID, Version string
	Cause       error // optional: why an auto-install attempt failed
}

NotInstalledError is the unified "interpreter missing" error. Preflight groups these into a single Report; the CLI prints the report and aborts.

func (*NotInstalledError) Error

func (e *NotInstalledError) Error() string

func (*NotInstalledError) Unwrap

func (e *NotInstalledError) Unwrap() error

type OSRecipe

type OSRecipe struct {
	Windows []string `json:"windows,omitempty"`
	Darwin  []string `json:"darwin,omitempty"`
	Linux   []string `json:"linux,omitempty"`
}

OSRecipe holds per-OS install commands. Each entry is a full command line; the first element is the binary, the rest are args.

func (OSRecipe) RecipeFor

func (r OSRecipe) RecipeFor() []string

RecipeFor returns the install command for the current OS, or nil if no recipe is defined. Pure Go: callers run it via os/exec.

type Preflight

type Preflight struct {
	// Installer, if non-nil, is invoked for each Missing entry before
	// the Report is returned. Use this to enable auto-install: preflight
	// still reports what was missing, but installs first. Default nil
	// (no auto-install — the safe default).
	Installer Ensure
}

Preflight is the pre-run gate. Before any LangBlock executes, the compiler calls Preflight with every LangBlock in the flow. Preflight resolves each (find-only — no install), groups missing interpreters, and returns a single Report. If the Report has any Missing entries, the flow does not run.

Why find-only here: preflight is a CHECK, not an install trigger. Hosts may explicitly call lang.Install or provide an Ensure implementation. Preflight surfaces what's missing; it doesn't fix it by default.

Why a single Report: a flow with py, rs, go, and ts that's missing rust, typescript, AND python3 should print one consolidated list, not fail on the first one and force the user to iterate.

func (Preflight) Run

func (p Preflight) Run(blocks []*LangBlock) Report

Run executes the preflight pass over the given blocks. Dedupes by (ID, Version) so a flow that calls py ten times only checks once.

blocks is the list of LangBlock AST nodes extracted from the flow by the compiler's AST validator, which calls Preflight.Run before any runtime invocation.

type Report

type Report struct {
	// Checked is every (ID, Version) pair examined, deduped. Includes
	// both present and missing.
	Checked []Check
	// Missing is the subset of Checked where the interpreter was not
	// found and no successful install happened. Length 0 = ready.
	Missing []Check
}

Report is the result of a preflight pass. Zero Missing entries means "ready to run." One or more means "abort and show this to the user."

func (Report) Error

func (r Report) Error() string

Error renders the report as a multi-line message suitable for compiler.SourceError or CLI stderr. Satisfies the error interface so callers can `return preflight.Report{...}` directly.

func (Report) OK

func (r Report) OK() bool

OK returns true if everything resolved.

func (Report) SelfTestSkipReason

func (r Report) SelfTestSkipReason() string

SelfTestSkipReason lets the repository self-test runner skip a language fixture when an interpreter is absent, while still failing on an unknown language or a broken preflight contract.

type RunnerSpec

type RunnerSpec struct {
	ID           string   `json:"id"`
	Version      string   `json:"version,omitempty"`
	Ext          string   `json:"ext"`
	Aliases      []string `json:"aliases,omitempty"`
	Find         []string `json:"find"`
	Stdin        string   `json:"stdin,omitempty"`
	Stdout       string   `json:"stdout,omitempty"`
	FileTemplate string   `json:"file_template,omitempty"`
	Install      OSRecipe `json:"install,omitzero"`
	Deps         string   `json:"deps,omitempty"`
	DepsMarker   string   `json:"deps_marker,omitempty"`
}

RunnerSpec is the data model parsed from a @lang.runner block in a .nflow file. One RunnerSpec per (language, version). The Go side is generic — adding a language means shipping a new runners/<id>.nflow file, zero Go changes.

File format (one block per file, multiple files for versioned variants):

@lang.runner "python" {
    ext: ".py"
    aliases: ["py", "python"]
    find: ["python3", "python"]
    stdin: "json"
    stdout: "json"
    file_template: "..." // optional source scaffold for :autocreate
    install: {
        windows: ["winget", "install", "Python.Python.3.12"],
        darwin:  ["brew", "install", "python@3.12"]
    }
}

@lang.runner "python" "3" {
    "id": "python",
    "version": "3",
    "ext": ".py",
    "aliases": ["py3", "python3"],
    "find": ["python3", "py -3"],
    "stdin": "json",
    "stdout": "json"
}

Fields:

  • ID, Version: the canonical identity. Version "" means "default".
  • Ext: file extension for materialized source, including the dot.
  • Aliases: short keywords (py, python) that resolve to this spec.
  • Find: candidate command lines tried in order. Each entry is a space-split command + args; the first that resolves on PATH wins.
  • Stdin, Stdout: "json" (default) or "text".
  • FileTemplate: optional JSON-in/JSON-out starter source used by `<alias>.file "path" :autocreate` when the target does not exist.
  • Install: optional per-OS recipes for auto-install. Omit or leave the OS entry empty to skip auto-install for that platform.
  • Deps, DepsMarker: optional command template and source marker for per-script dependency tools such as uv; {file} is passed as one arg.

func ParseRunner

func ParseRunner(src string) (*RunnerSpec, error)

ParseRunner extracts the JSON object from a runner .nflow file and parses it into a RunnerSpec.

The file format is: an optional `@lang.runner "id" ["version"]` header line, followed by a strict JSON object. The JSON object carries all fields including "id" and "version". The header is for human/IDE readability only — it's ignored by the parser, which trusts the JSON.

This uses encoding/json directly (battle-tested by every Go program in existence). No hand-rolled brace counter, no comment stripper, no trailing-comma fixer. If a runner file needs documentation, it uses a "_comment" field inside the JSON — same convention as package.json.

The parser finds the first '{' in the source and decodes from there via json.NewDecoder, which handles all JSON syntax correctly including nested objects and strings containing braces.

func (RunnerSpec) Decode

func (s RunnerSpec) Decode() (func(string) (any, error), error)

Decode resolves the stdout codec name to a function.

func (RunnerSpec) Encode

func (s RunnerSpec) Encode() (func(any) ([]byte, error), error)

Encode resolves the stdin codec name to a function.

func (RunnerSpec) FindCmd

func (s RunnerSpec) FindCmd() (cmd string, args []string, err error)

FindCmd tries each entry in Find via lookPathCmd. The first candidate that resolves to a usable interpreter wins; on Windows, py.exe is probed with its selected version so an empty launcher is treated as missing.

Each Find entry is split on whitespace; the first token is the binary, the rest are pre-args (e.g. "py -3" → cmd="py", args=["-3"]). The materialized source path is appended later by the runtime.

type UnknownLanguageError

type UnknownLanguageError struct {
	ID, Version string
}

UnknownLanguageError is returned by Lookup and ParseTag when the alias resolves to an (ID, Version) pair the Registry has no entry for. It surfaces as a compile-time SourceError via the parser hook.

func (*UnknownLanguageError) Error

func (e *UnknownLanguageError) Error() string

type UnknownTagError

type UnknownTagError struct{ Tag string }

UnknownTagError is returned by ParseTag when the keyword is not in the alias table. The user sees this wrapped in a SourceError at the keyword.

func (*UnknownTagError) Error

func (e *UnknownTagError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL