/add-stdlib-package — New Standard Library Package Skill
Creates a new stdlib package for the ELPS interpreter. Follows the established pattern from existing packages like libmath.
Trigger
Use when asked to add a new standard library package (e.g., file I/O, crypto, HTTP client, etc.).
Workflow
1. Plan the Package
Before writing code, define:
- •Package name (lowercase, e.g.,
"file","crypto") - •Functions to export with their signatures
- •Any global constants to expose
- •Package-level documentation
2. Create the Package Directory
Create lisp/lisplib/lib<name>/lib<name>.go:
go
package lib<name>
import (
"github.com/luthersystems/elps/lisp"
"github.com/luthersystems/elps/lisp/lisplib/libutil"
)
const DefaultPackageName = "<name>"
func LoadPackage(env *lisp.LEnv) *lisp.LVal {
name := lisp.Symbol(DefaultPackageName)
e := env.DefinePackage(name)
if !e.IsNil() {
return e
}
e = env.InPackage(name)
if !e.IsNil() {
return e
}
env.SetPackageDoc(`One-line description of the package.
Detailed description of what the package provides.`)
// Optional: global constants
// env.PutGlobal(lisp.Symbol("my-const"), lisp.Int(42))
// env.SetSymbolDoc("my-const", "Description of the constant.")
// env.Runtime.Package.Exports("my-const")
for _, fn := range builtins {
env.AddBuiltins(true, fn)
}
return lisp.Nil()
}
var builtins = []*libutil.Builtin{
libutil.FunctionDoc("my-func",
lisp.Formals("arg1", "arg2"),
builtinMyFunc,
`Description of what my-func does.
Returns the result of ...`),
}
func builtinMyFunc(env *lisp.LEnv, args *lisp.LVal) *lisp.LVal {
arg1 := args.Cells[0]
arg2 := args.Cells[1]
// Implementation
_ = arg1
_ = arg2
return lisp.Nil()
}
Key Rules
- •Always use
libutil.FunctionDoc()— neverlibutil.Function(). All functions must have docstrings. CI enforces this viaelps doc -m. - •Use
lisp.Formals()for argument declarations. Available modifiers:- •Required:
lisp.Formals("arg1", "arg2") - •Optional:
lisp.Formals("required", lisp.FmtString("&optional"), "opt-arg") - •Variadic:
lisp.Formals("required", lisp.FmtString("&rest"), "rest-args") - •Keyword:
lisp.Formals("required", lisp.FmtString("&key"), "key-arg")
- •Required:
- •Error propagation: Return
env.Errorf("message: %v", detail)for errors — never use Go'serrorinterface - •Export symbols: Call
env.Runtime.Package.Exports("func1", "func2", ...)for all public symbols
3. Register in LoadLibrary()
Edit lisp/lisplib/lisplib.go:
- •Add import:
"github.com/luthersystems/elps/lisp/lisplib/lib<name>" - •Add to the package loader list in
LoadLibrary():
go
e = lib<name>.LoadPackage(env)
if !e.IsNil() {
return e
}
4. Write Tests
Create a lisp test file at lisp/lisplib/lib<name>/lib<name>_test.go:
go
package lib<name>_test
import (
"testing"
"github.com/luthersystems/elps/elpstest"
)
func TestLib<Name>(t *testing.T) {
runner := &elpstest.Runner{}
runner.RunTestFile(t, "testdata/<name>_test.lisp")
}
Create the lisp test at lisp/lisplib/lib<name>/testdata/<name>_test.lisp:
lisp
(use-package 'testing) (use-package '<name>) (test "my-func basic usage" (assert-equal (my-func "arg1" "arg2") expected-result)) (test "my-func edge case" (assert-nil (my-func "" ""))) (test "my-func error handling" (assert-error (my-func))) ; missing args
5. Verify
bash
go test ./lisp/lisplib/lib<name>/... # Package tests pass make test # Full suite passes ./elps doc -m # No missing docstrings ./elps doc <name> # Package docs render correctly
Checklist
- • Package directory created at
lisp/lisplib/lib<name>/ - •
LoadPackage()follows DefinePackage → InPackage → SetPackageDoc → AddBuiltins pattern - • All functions use
libutil.FunctionDoc()(notFunction()) - • All public symbols exported via
Exports() - • Package registered in
lisplib.goLoadLibrary() - • Tests written with
elpstest.Runner+.lisptest file - •
elps doc -mpasses (no missing docstrings) - •
make testpasses - •
docs/lang.mdupdated if package is user-facing