writ

package module
v0.0.0-...-7aee52b Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 10 Imported by: 0

README

Writ

Go Reference

Writ is a Lisp embedded in Go. This module is the language library and a CLI.

Install

go install deedles.dev/writ/cmd/writ@latest

CLI

writ                        # REPL
writ help                   # list commands
writ help run               # command help
writ repl                   # REPL
writ -I DIR                 # REPL with import search path
writ run FILE.writ
writ fmt FILE.writ          # formatted source on stdout
writ fmt -w FILE.writ       # rewrite the file
writ check FILE.writ        # type-check; non-zero exit on error

writ help and writ -h / writ --help print the same overview. writ help <command> and writ <command> -h print the same command help.

With no arguments, writ starts a REPL (writ repl is the same). Unclosed (, [, strings, and tick symbols continue on the next line. -I DIR sets the import search path, as with writ run, and is valid on writ or writ repl. When stdin and stdout are a terminal, the REPL uses line editing, history, and tab completion of keywords and builtins. History is stored under the user config directory. Ctrl+C cancels the current line. Ctrl+Z is ignored (it does not suspend).

writ run evaluates top-level forms, then calls main if it was defined. The CLI registers print, which writes to stdout.

(def (main)
  (print "hello"))

Language

Integers are arbitrary-precision. 1 is an integer; 1.0 is a float. +, -, and * stay integers when every operand is an integer. / of integers stays an integer when the division is exact; otherwise it is a float. Division by zero is an error.

true, false, and nil are interned symbols. true and false are booleans; nil is not. All three satisfy symbol?.

(+ 1 2)
(def (add a b) (+ a b))
(let [x: 1 y: 2] (+ x y))
(if (int? n) (+ n 1) else 0)
(pipe xs (list-map (fn * #1 2)) (list-reduce 0 (fn + #1 #2)))

Lists are [a b c]. Maps are [k: v] or empty [:]. Mixing plain items and k: pairs in one [] is a parse error. Map keys are symbols: (map-get m 'k). Required field access is m.k or (. m k); a missing key or a non-map left side is an error. map-get still returns nil for a missing key. Dots in a symbol name are written with ticks: `io.write`. ', ,, and @ apply to the whole dotted token ('io.write is quote of (. io write)); unquote of only the left is (. ,m write).

A defm body is expand-time fragments. Each result is one form at the call site. A top-level @ splices a list of forms into that sequence. In a fn / let / if / after / on body, and at the top of a script, a call that expands to several forms is those forms in place. In expression position they run in order and the value is the last form.

(defm (example @rest)
  '(print "Example.")
  @rest)

Import

(import "path") evaluates another script once per runtime and returns a map of that script's top-level def and defm names. Names that start with - are private and are not exported. It is an expression and may appear in let. A defm in that map is a macro: (m.unless …) expands with unevaluated arguments, for both keyed import and (let [m: (import "m")] …). Passing a macro to list-map (or otherwise applying it as a function) is an error.

At the top of a script, keyed import binds names without exporting them:

(import io: "io" lib: "lib.writ")
(lib.double 21)

Each key is the local name; each value is a path expression. Keyed import must appear before any other top-level form (def, defm, on, or a boot expression), including forms produced by macro expansion in the same compile. Several keyed imports may be consecutive. The REPL does not apply that file-order rule across sequential Eval calls. A later def re-exports a name if needed.

Relative paths are resolved from the importing file. Search directories can be set on the runtime (WithSearchPath) or with writ run -I DIR / writ check -I DIR / writ repl -I DIR.

Resolution order for a path without a known suffix:

  1. An in-process package registered with RegisterPackage
  2. A .writ file under the importing file’s directory and WithSearchPath (cwd is used only when there is no importing file and no search path)
  3. A native plugin (.so / .dylib / .dll) only if the host called WithNativePlugins

Absolute paths and .. that leave those roots are rejected unless WithAllowAbsoluteImports is set. Untrusted Eval should leave plugins off and set an explicit search path. The default unlimited eval budget is not a sandbox.

(let [lib: (import "lib.writ")]
  (lib.double 21))

Embed

import (
    "deedles.dev/writ"
    "deedles.dev/writ/runtime"
    "deedles.dev/writ/types"
)

rt := writ.New()
rt.RegisterPrint()
rt.RegisterPackage("mathx", runtime.Package{
    Funcs: map[string]runtime.Func{
        "double": func(args []runtime.Value) (runtime.Value, error) {
            return runtime.Int64(args[0].BigInt().Int64() * 2), nil
        },
    },
})
rt.RegisterEvent("tick", types.PayloadKey{Name: "n", Type: types.IntType()})
rt.RegisterAlias("color", "red", "blue")
rt.SetScheduler(func(d time.Duration, fn func()) { /* own loop */ })

if _, err := rt.EvalFile("script.writ"); err != nil { ... }
_ = rt.Fire("tick", map[string]runtime.Value{"n": runtime.Int64(1)})
res := rt.Check(src) // diagnostics and type hints

A native plugin exports:

func WritPackage() runtime.Package

Build with go build -buildmode=plugin. In-process RegisterPackage is the embedding path that works everywhere, including WASM.

Parse, format, and tokens

Editors and formatters import the package that owns the name:

forms, err := parser.Parse(src)
text, err := parser.Format(src)
toks := scanner.Tokenize(src)
res := rt.Check(src)

Documentation

Overview

Package writ is the high-level embedding API for the Writ Lisp.

Integers are arbitrary precision (int64 fast path, math/big otherwise). Floats are IEEE float64. Inexact division of large integers is float64 and may round. true, false, and nil are interned symbols; true and false are booleans; nil is not. Eval with the default unlimited step budget is not a sandbox. Native plugins are off until WithNativePlugins.

(+ 1 2)
(def (add a b) (+ a b))
(let [x: 1] (+ x 2))

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Check

func Check(src string) types.CheckResult

Check type-checks src with a fresh runtime. print is registered so scripts that use it type-check the same way as `writ check`.

Types

type Option

type Option func(*Runtime)

Option configures a Runtime.

func WithAfterError

func WithAfterError(fn func(error)) Option

WithAfterError sets a hook for errors from (after ...) callbacks.

func WithAllowAbsoluteImports

func WithAllowAbsoluteImports() Option

WithAllowAbsoluteImports allows absolute import paths and paths that leave the importing script directory / search path (including "..").

func WithEvalLimit

func WithEvalLimit(n int) Option

WithEvalLimit caps eval/expand steps across expand, eval, import, after, Fire, and Apply until the next public call resets the remaining budget. Zero (the default) means unlimited and is not a sandbox.

func WithNativePlugins

func WithNativePlugins() Option

WithNativePlugins allows (import) of Go plugin files (.so/.dylib/.dll). Off by default. Plugins are never opened during Check.

func WithScheduler

func WithScheduler(s runtime.Scheduler) Option

WithScheduler replaces the default time.AfterFunc scheduler.

func WithSearchPath

func WithSearchPath(paths ...string) Option

WithSearchPath sets directories used to resolve import paths.

func WithStdout

func WithStdout(w io.Writer) Option

WithStdout sets the writer used by the print builtin if registered.

type Runtime

type Runtime struct {
	// contains filtered or unexported fields
}

Runtime evaluates scripts, holds the script store, and hosts packages. Public methods are safe for concurrent use.

func New

func New(opts ...Option) *Runtime

New constructs a runtime.

func (*Runtime) AliasType

func (rt *Runtime) AliasType(name string) (types.Type, bool)

AliasType returns the type last registered under name.

func (*Runtime) Apply

func (rt *Runtime) Apply(fn runtime.Value, args []runtime.Value) (runtime.Value, error)

Apply calls fn with positional args.

func (*Runtime) Check

func (rt *Runtime) Check(src string) types.CheckResult

Check type-checks src.

func (*Runtime) CheckFile

func (rt *Runtime) CheckFile(path string) types.CheckResult

CheckFile type-checks a file.

func (*Runtime) Eval

func (rt *Runtime) Eval(src string) (runtime.Value, error)

Eval compiles and evaluates src (boot forms only). Defs, macros, and handlers persist on this runtime until Runtime.Reset; later Eval calls expand using those macros. Redefining a function replaces it (clauses are not merged across calls). A name cannot be both a function and a macro. (on ...) handlers accumulate across Eval calls.

func (*Runtime) EvalFile

func (rt *Runtime) EvalFile(path string) (runtime.Value, error)

EvalFile evaluates a file's boot forms. It does not call main. Persistence is the same as Runtime.Eval; call Runtime.Reset before reloading a file if (on ...) handlers should not stack.

func (*Runtime) Fire

func (rt *Runtime) Fire(event string, payload map[string]runtime.Value) error

Fire runs matching (on event ...) handlers. Missing payload keys are nil.

func (*Runtime) GetProp

func (rt *Runtime) GetProp(path ...string) runtime.Value

GetProp reads the script store.

func (*Runtime) Lookup

func (rt *Runtime) Lookup(name string) (runtime.Value, bool)

Lookup returns a top-level binding.

func (*Runtime) RegisterAlias

func (rt *Runtime) RegisterAlias(name string, members ...string)

RegisterAlias names a closed union of exact strings for type display and host domain types.

func (*Runtime) RegisterBuiltin

func (rt *Runtime) RegisterBuiltin(name string, call runtime.Func, arrows ...types.Arrow) error

RegisterBuiltin adds a host function visible as a call head.

func (*Runtime) RegisterEvent

func (rt *Runtime) RegisterEvent(name string, keys ...types.PayloadKey)

RegisterEvent declares an event for (on ...) and Runtime.Fire. If any events are registered, unknown event names are type errors.

func (*Runtime) RegisterPackage

func (rt *Runtime) RegisterPackage(name string, pkg runtime.Package)

RegisterPackage installs an in-process package. (import "name") loads it without touching the filesystem.

func (*Runtime) RegisterPrint

func (rt *Runtime) RegisterPrint()

RegisterPrint installs a print builtin that writes to stdout (or WithStdout).

func (*Runtime) RegisterTypeAlias

func (rt *Runtime) RegisterTypeAlias(name string, t types.Type)

RegisterTypeAlias names an arbitrary type.

func (*Runtime) Reset

func (rt *Runtime) Reset()

Reset clears the script store, top-level env, macros, handlers, and import cache.

func (*Runtime) SetAfterError

func (rt *Runtime) SetAfterError(fn func(error))

SetAfterError sets a hook for errors from (after ...) callbacks.

func (*Runtime) SetProp

func (rt *Runtime) SetProp(val runtime.Value, path ...string) error

SetProp writes the script store. nil deletes.

func (*Runtime) SetScheduler

func (rt *Runtime) SetScheduler(s runtime.Scheduler)

SetScheduler replaces the after scheduler.

Directories

Path Synopsis
cmd
writ command
example
nativehello command
A native package for (import ...) via go build -buildmode=plugin.
A native package for (import ...) via go build -buildmode=plugin.
Package parser reads Writ source into runtime values and pretty-prints it.
Package parser reads Writ source into runtime values and pretty-prints it.
Package repl is an interactive evaluator for Writ source.
Package repl is an interactive evaluator for Writ source.
Package runtime is the Writ value universe and evaluator.
Package runtime is the Writ value universe and evaluator.
Package scanner tokenizes Writ source for the parser and for editors.
Package scanner tokenizes Writ source for the parser and for editors.
Package types is Writ's type algebra and checker.
Package types is Writ's type algebra and checker.

Jump to

Keyboard shortcuts

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