feather

package module
v0.0.0-...-0571afe Latest Latest
Warning

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

Go to latest
Published: Dec 30, 2025 License: JSON Imports: 14 Imported by: 0

README

The Feather Programming Language

This repository holds Feather: a minimal, clean implementation of the TCL core language, suitable for embedding into modern applications.

Runs everywhere

Go logo
Go
Native integration with Go applications.
Full access to the host runtime.
WebAssembly logo JavaScript logo
JavaScript / WebAssembly
Run in any browser or Node.js.
See js/ for the WASM host.

Feather stays mostly faithful to the original, but adds new functionality where user ergonomics count, such as exposing objects from the host to Feather programs.

Omissions from Tcl 9:

  • I/O functions,
  • an event loop,
  • an OO system.

Installation

go get github.com/feather-lang/feather

Feather in your program

Go is the only supported host language at the moment.

See ./cmd/feather-httpd/main.go for an example:

// feather-httpd is an example HTTP server configurable via the feather TCL interpreter.
//
// Usage:
//
// feather-httpd [script.tcl]
//
// If a script is provided, it is evaluated at startup. Then, a REPL is started
// for interactive configuration. The server can be controlled via TCL commands:
//
// route GET /path {script}   - register a route handler
// listen 8080                - start the HTTP server on a port
// stop                       - stop the HTTP server
// response body              - set response body (in handler context)
// status code                - set HTTP status code (in handler context)
// header name value          - set response header (in handler context)
// request method             - get request method (in handler context)
// request path               - get request path (in handler context)
// request header name        - get request header (in handler context)
// request query name         - get query parameter (in handler context)
//
// Example session:
//
// % route GET / {response "Hello, World!"}
// % route GET /time {response [clock format [clock seconds]]}
// % listen 8080
// Listening on :8080
// % stop
// Server stopped

Feather vs Lua

Lua is great for programming extensions to software in the large.

Feather is filling the niche where you have an existing program, and want to add an interactive console or a networked REPL for poking and prodding the program state:

  • giving agents access to your program (like the Chrome Dev Tools, but for YOUR application),
  • configuring an HTTP server while it's running
  • Quake-style consoles,
  • as a configuration file format,
  • allowing users to customize software after you shipped it.

Using the example

Since Feather is designed to be embedded into other programming languages, you need a host for running feather.

The default host for feather, used for developing feather, is written in Go:

[!CAUTION] The feather-tester interpreter provided here is only for Feather's internal use and will change frequently -- only use this for playing around.

# build the example interpreter in bin/feather-tester
mise build

# start the repl
mise exec -- feather-tester

In the REPL:

% set greeting "Hello, World!"
Hello, World!
% string length $greeting
13

% proc greet {name} {
    return "Hello, $name!"
}
% greet TCL
Hello, TCL!

% set numbers {1 2 3 4 5}
1 2 3 4 5
% lindex $numbers 2
3
% llength $numbers
5

% foreach n $numbers {
    if {$n > 3} { break }
    echo $n
}
1
2
3

% expr {2 + 2 * 3}
8
% set x 10
10
% incr x 5
15

% dict set config host localhost
host localhost
% dict set config port 8080
host localhost port 8080
% dict get $config port
8080
Run the test harness
$ mise test
[build:harness] $ cd harness
[build:core] $ mkdir -p build
[build:harness] Finished in 207.2ms
[build:core] Finished in 800.3ms
[build:feather-tester] $ go build -o $MISE_CONFIG_ROOT/bin/feather-tester ./cmd/feather-tester
[build:feather-tester] Finished in 503.8ms
[test] $ harness run --host bin/feather-tester testcases/
[test]
[test] 1024 tests, 1024 passed, 0 failed
[test] Finished in 9.88s
Finished in 11.19s

Philosophy

TCL was conceived at a time when most networked software was written in C at the core, the internet was young, user expectations were looser.

It is a tiny language full of great ideas, but features that were useful 20 years ago are a hindrance today:

  • I/O in the language is an obstacle, as the host is more than likely to already have taken a stance on how it wants to handle I/O,
  • a built-in event loop for multiplexing I/O and orchestrating timers was useful when no host could easily provide this, but event loops are widespread and having to integrate multiple event loops in one application is error-prone.
  • reference counting with lots of calls to malloc and free works great for standalone TCL, but the emergence of zig and wasm incentivizes being in control of allocations.

So what ideas are worth preserving?

A pure form of metaprogramming, syntax moldable like clay, with meaning to be added at a time and in a form that is convenient for that particular use case.

A transparent execution environment: every aspect of a running TCL program can be inspected from within that program, and often even modified.

A focus on expressing computation in the form of instructions to carry out.

The latter point is key: agentic coding benefits from an inspectable and moldable environment. Having the agent talk to your running program gives it highly valuable feedback for a small amount of tokens.

The browser is one example of this model being successful, but what about all the other applications? Your job runner, web server, database, your desktop or mobile app.

feather wants to be the thin glue layer that's easy to embed into your programs, so that you can talk to them while they are running.

Another way to look at TCL is this: it is a Lisp-2 with fexprs that extend to the lexical syntax level. Maybe that is more exciting.

Here you will find a faithful implementation of:

  • control flow and execution primitives: proc, foreach, for, while, if, return, break, continue, error, tailcall, try, throw, catch, switch
  • introspection capabilities: info, errorCode, errorInfo, trace
  • values and expressions: expr, incr, set, unset, global, variable
  • metaprogramming: upvar, uplevel, rename, unknown, namespace
  • data structures: list, dict, string, apply
  • string manipulation: split, subst, concat, append, regexp, regsub, join

Notable omissions (all to be covered by the host):

  • I/O: chan, puts, gets, refchan, transchan, after, vwait, update These are better provided by the host in the form of exposed commands.

  • OO: feather intended use case is short, interactive programs similar to bash. Programming in the large is explicitly not supported.

  • Coroutines: feather interpreter objects are small and lightweight so you can have of them if you need something like coroutines.

Notables qualities of the implementation:

This implementation is pure: it does not directly perform I/O or allocation or interact with the kernel at all. It only provides TCL parsing and semantics.

All memory is allocated, accessed, and released by the embedding host. The embedding host is highly likely to already have all the building blocks we care about in the implementation and there is little value in building our own version of regular expressions, lists, dictionaries, etc.

While this requires the host to implement a large number of functions, the implementation is largely mechanical, which makes it a prime candidate for delegating to agentic coding tools.

Documentation

Overview

Package feather provides an embeddable TCL interpreter for Go applications.

Architecture

feather has a layered architecture:

  • C core: The parsing and evaluation engine written in C
  • Handle layer: Internal numeric handles (FeatherObj, ObjHandle) for C interop
  • Obj layer: The public Go API using *Obj values

As a user of this package, you work exclusively with *Obj values. The Handle types exist only for internal implementation and may change between versions.

Quick Start

interp := feather.New()
defer interp.Close()

// Evaluate TCL scripts
result, err := interp.Eval("expr {2 + 2}")
if err != nil {
    log.Fatal(err)
}
fmt.Println(result.String()) // "4"

// Register Go functions as TCL commands
interp.Register("env", func(name string) string {
    return os.Getenv(name)
})

result, _ = interp.Eval(`env HOME`)
fmt.Println(result.String()) // "/home/user"

Thread Safety

An *Interp is NOT safe for concurrent use from multiple goroutines. Each goroutine that needs to evaluate TCL must have its own interpreter:

// WRONG: sharing interpreter between goroutines
interp := feather.New()
go func() { interp.Eval("...") }() // data race!
go func() { interp.Eval("...") }() // data race!

// CORRECT: one interpreter per goroutine
go func() {
    interp := feather.New()
    defer interp.Close()
    interp.Eval("...")
}()

For server applications, use a pool of interpreters or create one per request. *Obj values are also tied to their interpreter and must not be shared.

Supported TCL Commands

feather implements a substantial subset of TCL 8.6. Available commands:

Control flow:

if, while, for, foreach, switch, break, continue, return, tailcall

Procedures and evaluation:

proc, apply, eval, uplevel, upvar, catch, try, throw, error

Variables and namespaces:

set, unset, incr, append, global, variable, namespace, rename, trace

Lists:

list, llength, lindex, lrange, lappend, lset, linsert, lreplace,
lreverse, lrepeat, lsort, lsearch, lmap, lassign, split, join, concat

Dictionaries:

dict (with subcommands: create, get, set, exists, keys, values, etc.)

Strings:

string (with subcommands: length, index, range, equal, compare,
        match, map, tolower, toupper, trim, replace, first, last, etc.)
format, scan, subst

Introspection:

info (with subcommands: exists, commands, procs, vars, body, args,
      level, frame, script, etc.)

Math functions (via expr):

sqrt, exp, log, log10, sin, cos, tan, asin, acos, atan, atan2,
sinh, cosh, tanh, floor, ceil, round, abs, pow, fmod, hypot,
double, int, wide, isnan, isinf

NOT implemented: file I/O, sockets, regex, clock, encoding, interp (safe interps), and most Tk-related commands. Use Interp.Register to add these if needed.

Error Handling

Errors from Interp.Eval are returned as *EvalError:

result, err := interp.Eval("expr {1/0}")
if err != nil {
    // err is *EvalError, err.Error() returns the message
    fmt.Println("Error:", err)
}

To return errors from Go commands, use Error or Errorf:

interp.RegisterCommand("fail", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
    // For Go errors, use err.Error() to get the string
    _, err := os.Open("/nonexistent")
    if err != nil {
        return feather.Error(err.Error())
    }
    return feather.OK("success")
})

For functions registered with Interp.Register, return an error as the last value:

interp.Register("openfile", func(path string) (string, error) {
    data, err := os.ReadFile(path)
    return string(data), err  // error automatically becomes TCL error
})

In TCL, use catch or try to handle errors:

if {[catch {openfile /nonexistent} errmsg]} {
    puts "Error: $errmsg"
}

Note: feather does not currently provide stack traces or line numbers in errors. The error message is the only diagnostic information available.

Working with Results

Interp.Eval returns (*Obj, error). The result is the value of the last command executed. Extract values using methods on *Obj or the As* functions:

result, _ := interp.Eval("expr {2 + 2}")

// As string (always works)
s := result.String()  // "4"

// As typed values (may error if not convertible)
n, err := result.Int()       // 4, nil
f, err := result.Double()    // 4.0, nil
b, err := result.Bool()      // true, nil

// For lists, first check if it's already a list or parse it
result, _ = interp.Eval("list a b c")
items, err := result.List()  // []*Obj{"a", "b", "c"}
// Or parse a string as a list:
items, err = interp.ParseList("a b {c d}")

The Result type is only used when implementing commands with Interp.RegisterCommand. Create results with OK, Error, or Errorf.

Memory and Lifetime

*Obj values are managed by Go's garbage collector. You don't need to explicitly free them. However:

  • After Interp.Close, all *Obj values from that interpreter become invalid
  • Don't store *Obj values beyond the interpreter's lifetime
  • Don't share *Obj values between interpreters

For long-lived applications, be aware that string representations are cached. An object that shimmers between int and string keeps both representations until garbage collected.

The Obj Type System

TCL values are represented by *Obj. Each Obj has two representations:

  • String representation: The TCL string form (always available)
  • Internal representation: An efficient native form (optional)

The internal representation is managed through the ObjType interface. Conversion between representations happens lazily through "shimmering": when you request a value as an integer, it parses the string and caches the int; when you later request the string, it's regenerated from the int.

Use the As* functions to convert between types:

n, err := feather.AsInt(obj)      // Get as int64
f, err := feather.AsDouble(obj)   // Get as float64
b, err := feather.AsBool(obj)     // Get as bool
list, err := feather.AsList(obj)  // Get as []*Obj (requires list rep)
dict, err := feather.AsDict(obj)  // Get as *DictType (requires dict rep)

Note: AsList and AsDict only work on objects that already have list/dict representations. To parse a string as a list or dict, use the interpreter:

list, err := interp.ParseList("a b {c d}")   // Parse string to list
dict, err := interp.ParseDict("name Alice")  // Parse string to dict

Custom Object Types

Implement ObjType to create types that participate in shimmering. This is useful when you have a Go type that's expensive to create from its string form, so you want to cache the parsed representation.

The interface has three methods:

type ObjType interface {
    Name() string           // Type name for debugging (e.g., "regex")
    UpdateString() string   // Convert internal rep back to string
    Dup() ObjType           // Clone the internal rep (for Copy)
}

Example: A regex type that caches compiled patterns:

type RegexType struct {
    pattern string
    re      *regexp.Regexp
}

func (r *RegexType) Name() string         { return "regex" }
func (r *RegexType) UpdateString() string { return r.pattern }
func (r *RegexType) Dup() feather.ObjType { return r } // Immutable, share it

func NewRegex(pattern string) (*feather.Obj, error) {
    re, err := regexp.Compile(pattern)
    if err != nil {
        return nil, err
    }
    return feather.NewObj(&RegexType{pattern: pattern, re: re}), nil
}

// Extract the compiled regex from any Obj
func GetRegex(obj *feather.Obj) (*regexp.Regexp, bool) {
    if rt, ok := obj.InternalRep().(*RegexType); ok {
        return rt.re, true
    }
    return nil, false
}

Conversion Interfaces

Custom types can implement conversion interfaces to participate in automatic type coercion. When AsInt is called on an Obj, it first checks if the internal representation implements IntoInt:

type IntoInt interface {
    IntoInt() (int64, bool)
}

If implemented and returns (value, true), that value is used directly without parsing the string representation. This enables efficient conversions between related types.

Available conversion interfaces:

IntoInt    - Convert to int64
IntoDouble - Convert to float64
IntoBool   - Convert to bool
IntoList   - Convert to []*Obj
IntoDict   - Convert to (map[string]*Obj, []string)

Example: A timestamp type that converts to int (Unix epoch):

type TimestampType struct {
    t time.Time
}

func (ts *TimestampType) Name() string         { return "timestamp" }
func (ts *TimestampType) UpdateString() string { return ts.t.Format(time.RFC3339) }
func (ts *TimestampType) Dup() feather.ObjType { return ts }

// Implement IntoInt to support "expr {$timestamp + 3600}"
func (ts *TimestampType) IntoInt() (int64, bool) {
    return ts.t.Unix(), true
}

// Implement IntoDouble for sub-second precision
func (ts *TimestampType) IntoDouble() (float64, bool) {
    return float64(ts.t.UnixNano()) / 1e9, true
}

Foreign Objects

For exposing Go structs with methods to TCL, use RegisterType. Unlike ObjType (which is about caching parsed representations), foreign types create objects that act as TCL commands with methods:

type DB struct {
    conn *sql.DB
}

feather.RegisterType[*DB](interp, "DB", feather.TypeDef[*DB]{
    New: func() *DB {
        conn, _ := sql.Open("sqlite3", ":memory:")
        return &DB{conn: conn}
    },
    Methods: map[string]any{
        "exec":  func(db *DB, sql string) error { _, err := db.conn.Exec(sql); return err },
        "query": func(db *DB, sql string) ([]string, error) { /* ... */ },
    },
    Destroy: func(db *DB) { db.conn.Close() },
})

// In TCL:
// set db [DB new]
// $db exec "CREATE TABLE users (name TEXT)"
// $db destroy

Registering Commands

For simple functions, use Interp.Register with automatic type conversion:

// String arguments
interp.Register("upper", strings.ToUpper)

// Multiple parameters with error return
interp.Register("readfile", func(path string) (string, error) {
    data, err := os.ReadFile(path)
    return string(data), err
})

// Variadic functions
interp.Register("sum", func(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
})

For full control over argument handling, use Interp.RegisterCommand:

interp.RegisterCommand("mycommand", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
    if len(args) < 1 {
        return feather.Errorf("usage: %s value", cmd.String())
    }
    n, err := feather.AsInt(args[0])
    if err != nil {
        return feather.Error(err.Error())
    }
    return feather.OK(n * 2)
})

Configuration

Set the recursion limit to prevent stack overflow from deeply nested calls:

interp.SetRecursionLimit(500)  // Default is 1000

Parsing Without Evaluation

Use Interp.Parse to check if a script is syntactically complete without evaluating it. This is useful for implementing REPLs:

pr := interp.Parse("set x {")
switch pr.Status {
case feather.ParseOK:
    // Complete, ready to evaluate
case feather.ParseIncomplete:
    // Unclosed brace/bracket/quote, prompt for more input
case feather.ParseError:
    // Syntax error, pr.Message has details
}

Internal Types (Do Not Use)

The following types are internal implementation details for C interop. They are exported only because Go requires it for cgo. Do not use them in application code:

  • Handle, FeatherInterp, FeatherObj - Raw numeric handles
  • ObjHandle - Handle wrapper with interpreter reference
  • InternalCommandFunc - Low-level command signature
  • InternalParseStatus, ParseResultInternal - Internal parsing types
  • CallFrame, Namespace, Procedure, Command - Interpreter internals
  • ForeignRegistry, ForeignType - Interpreter internals
  • ListSortContext - Internal sorting state
  • FeatherResult, InternalCommandType - C interop constants

These types may change or be removed in any version.

Example (RegexType)

This example shows how to create and use a custom ObjType for caching compiled regular expressions.

package main

import (
	"fmt"
	"regexp"

	"github.com/feather-lang/feather"
)

// RegexType caches a compiled regular expression.
// This demonstrates the core use case for custom ObjType: avoiding repeated
// parsing of the same value.
type RegexType struct {
	pattern string
	re      *regexp.Regexp
}

func (r *RegexType) Name() string         { return "regex" }
func (r *RegexType) UpdateString() string { return r.pattern }
func (r *RegexType) Dup() feather.ObjType { return r }

// NewRegexObj compiles a pattern and wraps it in an Obj.
func NewRegexObj(pattern string) (*feather.Obj, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return nil, err
	}
	return feather.NewObj(&RegexType{pattern: pattern, re: re}), nil
}

// GetRegex extracts the compiled regex from an Obj.
// Returns nil, false if the Obj doesn't contain a RegexType.
func GetRegex(obj *feather.Obj) (*regexp.Regexp, bool) {
	if rt, ok := obj.InternalRep().(*RegexType); ok {
		return rt.re, true
	}
	return nil, false
}

// registerRegexCommands adds "regex" and "match" commands to an interpreter.
// Factored out so multiple examples can use it.
func registerRegexCommands(interp *feather.Interp) {

	interp.RegisterCommand("regex", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
		if len(args) < 1 {
			return feather.Errorf("wrong # args: should be \"regex pattern\"")
		}
		obj, err := NewRegexObj(args[0].String())
		if err != nil {

			return feather.Error(err.Error())
		}
		return feather.OK(obj)
	})

	interp.RegisterCommand("match", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
		if len(args) < 2 {
			return feather.Errorf("wrong # args: should be \"match regex string\"")
		}
		re, ok := GetRegex(args[0])
		if !ok {

			var err error
			re, err = regexp.Compile(args[0].String())
			if err != nil {
				return feather.Error(err.Error())
			}
		}
		if re.MatchString(args[1].String()) {
			return feather.OK(1)
		}
		return feather.OK(0)
	})
}

func main() {
	interp := feather.New()
	defer interp.Close()
	registerRegexCommands(interp)

	// Use the regex - the pattern is compiled once and reused
	result, _ := interp.Eval(`
		set re [regex {^\d+$}]
		match $re "12345"
	`)
	fmt.Println(result.String())
}
Output:

1
Example (RegexTypeError)

This example shows how errors from Go are propagated back to TCL. When regexp.Compile fails, the error becomes a TCL error that can be caught with "catch".

package main

import (
	"fmt"
	"regexp"

	"github.com/feather-lang/feather"
)

// RegexType caches a compiled regular expression.
// This demonstrates the core use case for custom ObjType: avoiding repeated
// parsing of the same value.
type RegexType struct {
	pattern string
	re      *regexp.Regexp
}

func (r *RegexType) Name() string         { return "regex" }
func (r *RegexType) UpdateString() string { return r.pattern }
func (r *RegexType) Dup() feather.ObjType { return r }

// NewRegexObj compiles a pattern and wraps it in an Obj.
func NewRegexObj(pattern string) (*feather.Obj, error) {
	re, err := regexp.Compile(pattern)
	if err != nil {
		return nil, err
	}
	return feather.NewObj(&RegexType{pattern: pattern, re: re}), nil
}

// GetRegex extracts the compiled regex from an Obj.
// Returns nil, false if the Obj doesn't contain a RegexType.
func GetRegex(obj *feather.Obj) (*regexp.Regexp, bool) {
	if rt, ok := obj.InternalRep().(*RegexType); ok {
		return rt.re, true
	}
	return nil, false
}

// registerRegexCommands adds "regex" and "match" commands to an interpreter.
// Factored out so multiple examples can use it.
func registerRegexCommands(interp *feather.Interp) {

	interp.RegisterCommand("regex", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
		if len(args) < 1 {
			return feather.Errorf("wrong # args: should be \"regex pattern\"")
		}
		obj, err := NewRegexObj(args[0].String())
		if err != nil {

			return feather.Error(err.Error())
		}
		return feather.OK(obj)
	})

	interp.RegisterCommand("match", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
		if len(args) < 2 {
			return feather.Errorf("wrong # args: should be \"match regex string\"")
		}
		re, ok := GetRegex(args[0])
		if !ok {

			var err error
			re, err = regexp.Compile(args[0].String())
			if err != nil {
				return feather.Error(err.Error())
			}
		}
		if re.MatchString(args[1].String()) {
			return feather.OK(1)
		}
		return feather.OK(0)
	})
}

func main() {
	interp := feather.New()
	defer interp.Close()
	registerRegexCommands(interp)

	// Invalid regex pattern - repetition operator at start
	_, err := interp.Eval(`regex {*invalid}`)
	fmt.Println("Direct error:", err)

	// Using catch to handle the error in TCL
	result, _ := interp.Eval(`
		if {[catch {regex {+also-invalid}} errmsg]} {
			set errmsg
		} else {
			set errmsg "no error"
		}
	`)
	fmt.Println("Caught error:", result.String())

}
Output:

Direct error: error parsing regexp: missing argument to repetition operator: `*`
Caught error: error parsing regexp: missing argument to repetition operator: `+`
Example (TimestampType)

This example shows a custom type that implements conversion interfaces, allowing it to work with expr and other numeric contexts.

package main

import (
	"fmt"
	"time"

	"github.com/feather-lang/feather"
)

// TimestampType wraps time.Time and implements conversion interfaces.
// This shows how to make a custom type participate in TCL's type system.
type TimestampType struct {
	t time.Time
}

func (ts *TimestampType) Name() string         { return "timestamp" }
func (ts *TimestampType) UpdateString() string { return ts.t.Format(time.RFC3339) }
func (ts *TimestampType) Dup() feather.ObjType { return ts }

// IntoInt returns Unix timestamp, enabling arithmetic in expr.
func (ts *TimestampType) IntoInt() (int64, bool) {
	return ts.t.Unix(), true
}

// IntoDouble returns Unix timestamp with nanosecond precision.
func (ts *TimestampType) IntoDouble() (float64, bool) {
	return float64(ts.t.UnixNano()) / 1e9, true
}

// NewTimestampObj creates a timestamp object from a time.Time.
func NewTimestampObj(t time.Time) *feather.Obj {
	return feather.NewObj(&TimestampType{t: t})
}

func main() {
	interp := feather.New()
	defer interp.Close()

	// Create a timestamp for a fixed point in time
	ts := NewTimestampObj(time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC))

	// Store it in a variable
	interp.SetVar("ts", ts.String())

	// The string representation is RFC3339
	result, _ := interp.Eval("set ts")
	fmt.Println("String:", result.String())

	// But we can also get the Unix timestamp via IntoInt
	n, _ := feather.AsInt(ts)
	fmt.Println("Unix:", n)

}
Output:

String: 2024-01-15T10:30:00Z
Unix: 1705314600

Index

Examples

Constants

View Source
const (
	// EvalLocal evaluates the script in the current call frame.
	// Variables are resolved in the local scope first, then in enclosing scopes.
	// This is the default behavior for most TCL commands.
	EvalLocal = C.TCL_EVAL_LOCAL

	// EvalGlobal evaluates the script in the global (top-level) scope.
	// Variables are resolved only in the global namespace, ignoring local frames.
	// Use this when a script should not see or modify local variables.
	EvalGlobal = C.TCL_EVAL_GLOBAL
)

Eval flags control variable resolution scope during script evaluation.

View Source
const DefaultRecursionLimit = 1000

DefaultRecursionLimit is the default maximum call stack depth.

Variables

This section is empty.

Functions

func AsBool

func AsBool(o *Obj) (bool, error)

AsBool converts o to a boolean, shimmering if needed.

func AsDouble

func AsDouble(o *Obj) (float64, error)

AsDouble converts o to float64, shimmering if needed.

func AsInt

func AsInt(o *Obj) (int64, error)

AsInt converts o to int64, shimmering if needed.

func ObjDictKeys

func ObjDictKeys(o *Obj) []string

ObjDictKeys returns the keys of a dict object in insertion order. Returns nil if the object is not a dict.

func ObjDictLen

func ObjDictLen(o *Obj) int

ObjDictLen returns the number of keys in a dict object. Returns 0 if the object is nil or not a dict.

func ObjDictSet

func ObjDictSet(o *Obj, key string, val *Obj)

ObjDictSet sets a key-value pair in a dict object. Creates the dict if needed (for nil intrep). Invalidates the string representation.

func ObjListAppend

func ObjListAppend(o *Obj, elem *Obj)

ObjListAppend appends an element to a list object. Converts the object to a list if it has a list-compatible representation. Invalidates the string representation.

func ObjListLen

func ObjListLen(o *Obj) int

ObjListLen returns the length of a list object. Returns 0 if the object is nil or not a list.

func ObjListSet

func ObjListSet(o *Obj, i int, elem *Obj)

ObjListSet sets the element at index i in a list object. Does nothing if the object is not a list or index is out of bounds. Invalidates the string representation.

func RegisterType

func RegisterType[T any](i *Interp, typeName string, def TypeDef[T]) error

RegisterType registers a foreign type with the interpreter.

After registration, the type name becomes a command that supports "new" to create instances. Instances can then call methods using $obj method args.

Example:

type Counter struct {
    value int
}

feather.RegisterType[*Counter](interp, "Counter", feather.TypeDef[*Counter]{
    New: func() *Counter { return &Counter{} },
    Methods: map[string]any{
        "get":  func(c *Counter) int { return c.value },
        "set":  func(c *Counter, v int) { c.value = v },
        "incr": func(c *Counter) int { c.value++; return c.value },
    },
})

// In TCL:
// set c [Counter new]
// $c set 10
// $c incr  ;# returns 11

Types

type CallFrame

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

CallFrame represents an execution frame on the call stack. Each frame has its own variable environment.

type Command

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

Command represents an entry in the unified command table

type CommandFunc

type CommandFunc func(i *Interp, cmd *Obj, args []*Obj) Result

CommandFunc is the signature for custom commands registered with Interp.RegisterCommand.

The function receives:

  • i: the interpreter (for creating objects, accessing variables, etc.)
  • cmd: the command name as invoked
  • args: the arguments passed to the command

Return OK for success or Error/Errorf for failure.

type DictType

type DictType struct {
	Items map[string]*Obj
	Order []string
}

DictType is the internal representation for dictionary values.

func AsDict

func AsDict(o *Obj) (*DictType, error)

AsDict converts o to a dictionary if it has a dict-compatible internal representation. For string-to-dict conversion, use the interpreter's parsing facilities.

func (*DictType) Dup

func (t *DictType) Dup() ObjType

func (*DictType) IntoDict

func (t *DictType) IntoDict() (map[string]*Obj, []string, bool)

func (*DictType) IntoList

func (t *DictType) IntoList() ([]*Obj, bool)

func (*DictType) Name

func (t *DictType) Name() string

func (*DictType) UpdateString

func (t *DictType) UpdateString() string

type DoubleType

type DoubleType float64

DoubleType is the internal representation for floating-point values.

func (DoubleType) Dup

func (t DoubleType) Dup() ObjType

func (DoubleType) IntoDouble

func (t DoubleType) IntoDouble() (float64, bool)

func (DoubleType) IntoInt

func (t DoubleType) IntoInt() (int64, bool)

func (DoubleType) Name

func (t DoubleType) Name() string

func (DoubleType) UpdateString

func (t DoubleType) UpdateString() string

type EvalError

type EvalError struct {
	Message string
}

EvalError represents an evaluation error

func (*EvalError) Error

func (e *EvalError) Error() string

type FeatherInterp

type FeatherInterp Handle

FeatherInterp is a handle to an interpreter instance

type FeatherObj

type FeatherObj Handle

FeatherObj is a handle to an object

type FeatherResult

type FeatherResult uint
const (
	ResultOK       FeatherResult = C.TCL_OK
	ResultError    FeatherResult = C.TCL_ERROR
	ResultReturn   FeatherResult = C.TCL_RETURN
	ResultBreak    FeatherResult = C.TCL_BREAK
	ResultContinue FeatherResult = C.TCL_CONTINUE
)

Result codes matching FeatherResult enum

type ForeignRegistry

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

ForeignRegistry manages foreign type definitions and object instances.

type ForeignType

type ForeignType struct {
	TypeName string
	Value    any
}

ForeignType is the internal representation for foreign (host-language) objects.

func (*ForeignType) Dup

func (t *ForeignType) Dup() ObjType

func (*ForeignType) Name

func (t *ForeignType) Name() string

func (*ForeignType) UpdateString

func (t *ForeignType) UpdateString() string

type Handle

type Handle = uintptr

Handle is the Go type for FeatherHandle

type IntType

type IntType int64

IntType is the internal representation for integer values.

func (IntType) Dup

func (t IntType) Dup() ObjType

func (IntType) IntoBool

func (t IntType) IntoBool() (bool, bool)

func (IntType) IntoDouble

func (t IntType) IntoDouble() (float64, bool)

func (IntType) IntoInt

func (t IntType) IntoInt() (int64, bool)

func (IntType) Name

func (t IntType) Name() string

func (IntType) UpdateString

func (t IntType) UpdateString() string

type InternalCommandFunc

type InternalCommandFunc func(i *Interp, cmd FeatherObj, args []FeatherObj) FeatherResult

InternalCommandFunc is the signature for host command implementations. Commands receive the interpreter, the command name and a list of argument objects.

In case of an error, the command should set the interpreter's error information and return ResultError

To return a value, the command should set the interpreter's result value and return ResultOK

Low-level API. May change between versions.

type InternalCommandType

type InternalCommandType int

CommandType indicates the type of a command

const (
	CmdNone    InternalCommandType = 0 // command doesn't exist
	CmdBuiltin InternalCommandType = 1 // it's a builtin command
	CmdProc    InternalCommandType = 2 // it's a user-defined procedure
)

type InternalParseStatus

type InternalParseStatus uint

InternalParseStatus matching FeatherParseStatus enum

const (
	InternalParseOK         InternalParseStatus = C.TCL_PARSE_OK
	InternalParseIncomplete InternalParseStatus = C.TCL_PARSE_INCOMPLETE
	InternalParseError      InternalParseStatus = C.TCL_PARSE_ERROR
)

type Interp

type Interp struct {

	// Commands holds registered Go command implementations.
	// Low-level API. May change between versions.
	Commands map[string]InternalCommandFunc

	// ForeignRegistry stores foreign type definitions for the high-level API.
	ForeignRegistry *ForeignRegistry
	// contains filtered or unexported fields
}

Interp is a TCL interpreter instance.

Create a new interpreter with New and always call Interp.Close when done. An interpreter is not safe for concurrent use from multiple goroutines.

interp := feather.New()
defer interp.Close()
result, err := interp.Eval("expr 2 + 2")

func New

func New() *Interp

New creates a new TCL interpreter with all standard commands registered.

The interpreter must be closed with Interp.Close when no longer needed to release resources.

interp := feather.New()
defer interp.Close()

func (*Interp) Bool

func (i *Interp) Bool(v bool) *Obj

Bool creates a boolean object, stored as int 1 (true) or 0 (false).

TCL has no native boolean type; booleans are represented as integers.

b := interp.Bool(true)
b.Type()   // "int"
b.String() // "1"

func (*Interp) Call

func (i *Interp) Call(cmd string, args ...any) (*Obj, error)

Call invokes a single TCL command with the given arguments.

Arguments are automatically converted from Go types to TCL values. This is a convenience wrapper around Interp.Eval for single command invocation.

result, err := interp.Call("expr", "2 + 2")
result, err := interp.Call("llength", myList)
result, err := interp.Call("myns::proc", arg1, arg2)

func (*Interp) Close

func (i *Interp) Close()

Close releases resources associated with the interpreter.

After Close is called, the interpreter and all *Obj values created from it become invalid. Always use defer to ensure Close is called.

func (*Interp) Dict

func (i *Interp) Dict() *Obj

Dict creates an empty dict object.

Use dict helper functions to add key-value pairs, or use Interp.DictKV or Interp.DictFrom to create a populated dict.

dict := interp.Dict()
feather.ObjDictSet(dict, "key", interp.String("value"))

func (*Interp) DictFrom

func (i *Interp) DictFrom(m map[string]any) *Obj

DictFrom creates a dict object from a Go map.

Values are auto-converted based on their Go type. Note: Go maps have undefined iteration order, so dict key order may vary.

dict := interp.DictFrom(map[string]any{
    "name": "Alice",
    "age":  30,
})

func (*Interp) DictGet

func (i *Interp) DictGet(h FeatherObj, key string) (FeatherObj, bool)

DictGet retrieves the value for a key in a dict object.

If the object is not already a dict, it attempts to convert from list representation (must have even number of elements).

Returns (handle, true) if the key exists, (0, false) otherwise. Also returns (0, false) if the object cannot be converted to a dict.

func (*Interp) DictGetObj

func (i *Interp) DictGetObj(dict FeatherObj, key string) (FeatherObj, bool)

DictGetObj retrieves a value from a dict by key. Returns the handle and true if found, or 0 and false if not found.

func (*Interp) DictKV

func (i *Interp) DictKV(kvs ...any) *Obj

DictKV creates a dict object from alternating key-value pairs.

Keys should be strings (non-strings are converted via fmt.Sprintf). Values are auto-converted based on their Go type.

dict := interp.DictKV("name", "Alice", "age", 30, "active", true)
dict.String() // "name Alice age 30 active 1"

func (*Interp) DictKeys

func (i *Interp) DictKeys(h FeatherObj) []string

DictKeys returns all keys of a dict object in insertion order.

If the object is not already a dict, it attempts to convert from list representation.

Returns nil if the object cannot be converted to a dict.

func (*Interp) DictSetObj

func (i *Interp) DictSetObj(dict FeatherObj, key string, val FeatherObj) FeatherObj

DictSetObj sets a key-value pair in a dict and returns the dict. If the object is not a dict, returns it unchanged.

func (*Interp) Eval

func (i *Interp) Eval(script string) (*Obj, error)

Eval evaluates a TCL script and returns the result.

Multiple commands can be separated by semicolons or newlines. Returns an error if the script has a syntax error or a command fails.

result, err := interp.Eval("set x 10; expr {$x * 2}")
if err != nil {
    log.Fatal(err)
}
fmt.Println(result.String()) // "20"

func (*Interp) EvalObj

func (i *Interp) EvalObj(obj *Obj) (*Obj, error)

EvalObj evaluates a TCL script contained in an object.

This is equivalent to calling Interp.Eval with obj.String(), but may be more convenient when working with objects that already contain TCL code.

script := interp.String("expr 2 + 2")
result, err := interp.EvalObj(script)

func (*Interp) Float

func (i *Interp) Float(v float64) *Obj

Float creates a floating-point object.

f := interp.Float(3.14)
f.Type()   // "double"
f.String() // "3.14"

func (*Interp) GetForeignMethods

func (i *Interp) GetForeignMethods(typeName string) []string

GetForeignMethods returns the method names for a foreign type. Used by the goForeignMethods callback.

func (*Interp) GetForeignStringRep

func (i *Interp) GetForeignStringRep(obj FeatherObj) string

GetForeignStringRep returns a custom string representation for a foreign object. Used by the goForeignStringRep callback.

func (*Interp) GetVar

func (i *Interp) GetVar(name string) string

GetVar returns the string value of a variable from the current frame, or empty string if not found.

func (*Interp) GetVarHandle

func (i *Interp) GetVarHandle(name string) FeatherObj

GetVarHandle returns the object handle for a variable, preserving its type. Returns 0 if the variable is not found.

func (*Interp) GetVars

func (i *Interp) GetVars(names ...string) map[string]*Obj

GetVars returns multiple variables as a map.

This is a convenience method equivalent to calling Interp.Var for each name. Variables that don't exist will have empty string values in the result.

vars := interp.GetVars("x", "y", "z")

func (*Interp) Handle

func (i *Interp) Handle() FeatherInterp

Handle returns the interpreter's handle

func (*Interp) Int

func (i *Interp) Int(v int64) *Obj

Int creates an integer object.

n := interp.Int(42)
n.Type()   // "int"
n.String() // "42"

func (*Interp) InternString

func (i *Interp) InternString(s string) FeatherObj

InternString stores a string and returns its handle.

func (*Interp) IsForeignHandle

func (i *Interp) IsForeignHandle(h FeatherObj) bool

IsForeignHandle returns true if the object is a foreign object. Also checks if the string representation is a foreign handle name.

func (*Interp) IsNativeDict

func (i *Interp) IsNativeDict(h FeatherObj) bool

IsNativeDict returns true if the object has a native dict representation (not just convertible to dict via shimmering).

func (*Interp) IsNativeList

func (i *Interp) IsNativeList(h FeatherObj) bool

IsNativeList returns true if the object has a native list representation (not just convertible to list via shimmering).

func (*Interp) List

func (i *Interp) List(items ...*Obj) *Obj

List creates a list object from the given items.

list := interp.List(interp.String("a"), interp.Int(1), interp.Bool(true))
list.Type()   // "list"
list.String() // "a 1 1"

func (*Interp) ListAppendObj

func (i *Interp) ListAppendObj(list FeatherObj, item FeatherObj) FeatherObj

ListAppendObj appends an item to a list and returns the list. If the object is not a list, returns it unchanged.

func (*Interp) ListFrom

func (i *Interp) ListFrom(slice any) *Obj

ListFrom creates a list object from a Go slice.

Supported slice types:

  • []string - each element becomes a string object
  • []int - each element becomes an int object
  • []int64 - each element becomes an int object
  • []float64 - each element becomes a double object
  • []any - each element is auto-converted based on its type

Example:

list := interp.ListFrom([]string{"a", "b", "c"})
list.String() // "a b c"

nums := interp.ListFrom([]int{1, 2, 3})
nums.String() // "1 2 3"

func (*Interp) ListIndex

func (i *Interp) ListIndex(h FeatherObj, idx int) FeatherObj

ListIndex returns the element at the given zero-based index in a list.

If the object is not already a list, it parses the string representation. Returns 0 (invalid handle) if:

  • The index is negative or out of bounds
  • The object cannot be converted to a list
  • The object handle is invalid

func (*Interp) ListLen

func (i *Interp) ListLen(h FeatherObj) int

ListLen returns the number of elements in a list object.

If the object is not already a list, it parses the string representation. Returns 0 if the object is nil, empty, or cannot be converted to a list.

Note: For dicts, this returns the number of key-value pairs times 2 (the list representation length).

func (*Interp) NewDictObj

func (i *Interp) NewDictObj() FeatherObj

NewDictObj creates an empty dict object.

func (*Interp) NewDoubleObj

func (i *Interp) NewDoubleObj(val float64) FeatherObj

NewDoubleObj creates a floating-point object.

func (*Interp) NewForeignHandle

func (i *Interp) NewForeignHandle(typeName string, value any) FeatherObj

NewForeignHandle creates a new foreign object with the given type name and Go value. The string representation is generated as "<TypeName:id>". Returns the handle to the new foreign object. Foreign objects are stored in permanent storage (not scratch) for explicit lifecycle.

func (*Interp) NewForeignObj

func (i *Interp) NewForeignObj(typeName string, value any) FeatherObj

NewForeignObj creates a foreign object with the given type name and value.

func (*Interp) NewIntObj

func (i *Interp) NewIntObj(val int64) FeatherObj

NewIntObj creates an integer object.

func (*Interp) NewListObj

func (i *Interp) NewListObj() FeatherObj

NewListObj creates an empty list object.

func (*Interp) Parse

func (i *Interp) Parse(script string) ParseResult

Parse checks if a script is syntactically complete.

This is useful for implementing REPLs that need to detect incomplete input (unclosed braces, brackets, or quotes).

pr := interp.Parse("set x {")
if pr.Status == feather.ParseIncomplete {
    // Prompt for more input
}

func (*Interp) ParseDict

func (i *Interp) ParseDict(s string) (*DictType, error)

ParseDict parses a string into a dict.

Use this when you have a string that needs to be parsed as a TCL dict. For objects that are already dicts, use AsDict instead.

d, err := interp.ParseDict("name Alice age 30")
// d.Items["name"].String() == "Alice"

func (*Interp) ParseInternal

func (i *Interp) ParseInternal(script string) ParseResultInternal

ParseInternal parses a script string and returns the parse status and result. Low-level API. May change between versions.

func (*Interp) ParseList

func (i *Interp) ParseList(s string) ([]*Obj, error)

ParseList parses a string into a list.

Use this when you have a string that needs to be parsed as a TCL list. For objects that are already lists, use AsList instead.

items, err := interp.ParseList("{a b} c d")
// items = []*Obj{"a b", "c", "d"}

func (*Interp) Register

func (i *Interp) Register(name string, fn any)

Register adds a command with automatic argument conversion.

The function's signature determines how arguments are converted:

  • string parameters receive the string representation
  • int/int64 parameters parse the argument as an integer
  • float64 parameters parse as a floating-point number
  • bool parameters use TCL boolean rules
  • []string parameters receive remaining args as a list
  • Variadic parameters (...string, ...int) consume remaining arguments

Return types are also auto-converted:

  • string, int, int64, float64, bool become the command result
  • error causes the command to fail with the error message
  • (T, error) returns T on success or fails on error

Examples:

// Simple function
interp.Register("greet", func(name string) string {
    return "Hello, " + name
})

// With error handling
interp.Register("divide", func(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
})

// Variadic
interp.Register("join", func(sep string, parts ...string) string {
    return strings.Join(parts, sep)
})

func (*Interp) RegisterCommand

func (i *Interp) RegisterCommand(name string, fn CommandFunc)

RegisterCommand adds a command using the low-level CommandFunc interface.

Use this when you need full control over argument handling, access to the interpreter, or custom error messages. For simpler cases, use Interp.Register.

interp.RegisterCommand("sum", func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
    if len(args) < 2 {
        return feather.Errorf("wrong # args: should be \"%s a b\"", cmd.String())
    }
    a, err := feather.AsInt(args[0])
    if err != nil {
        return feather.Error(err.Error())
    }
    b, err := feather.AsInt(args[1])
    if err != nil {
        return feather.Error(err.Error())
    }
    return feather.OK(a + b)
})

func (*Interp) Result

func (i *Interp) Result() string

Result returns the current result string

func (*Interp) ResultHandle

func (i *Interp) ResultHandle() FeatherObj

ResultHandle returns the current result object handle. Creates a scratch handle for C code to use.

func (*Interp) SetError

func (i *Interp) SetError(obj FeatherObj)

SetError sets the interpreter's result to the given object handle (for error results). This is symmetric with SetResult but semantically indicates an error value.

func (*Interp) SetErrorString

func (i *Interp) SetErrorString(s string)

SetErrorString sets the interpreter's result to an error message.

func (*Interp) SetRecursionLimit

func (i *Interp) SetRecursionLimit(limit int)

SetRecursionLimit sets the maximum call stack depth. If limit is 0 or negative, the default limit (1000) is used.

func (*Interp) SetResult

func (i *Interp) SetResult(obj FeatherObj)

SetResult sets the interpreter's result to the given object handle. The object is retrieved from the arena and stored directly.

func (*Interp) SetResultObj

func (i *Interp) SetResultObj(obj *Obj)

SetResultObj sets the interpreter's result to the given *Obj directly.

func (*Interp) SetResultString

func (i *Interp) SetResultString(s string)

SetResultString sets the interpreter's result to a string value.

func (*Interp) SetUnknownHandler

func (i *Interp) SetUnknownHandler(fn CommandFunc)

SetUnknownHandler sets a handler called when a command is not found.

The handler receives the unknown command name and its arguments. It can:

  • Implement the command dynamically
  • Delegate to another system
  • Return an error for truly unknown commands

Set to nil to restore default behavior (return "invalid command" error).

interp.SetUnknownHandler(func(i *feather.Interp, cmd *feather.Obj, args []*feather.Obj) feather.Result {
    // Try to auto-load the command
    if loaded := tryLoadCommand(cmd.String()); loaded {
        return i.Call(cmd.String(), args...)
    }
    return feather.Errorf("unknown command: %s", cmd.String())
})

func (*Interp) SetVar

func (i *Interp) SetVar(name string, val any)

SetVar sets a variable to a value.

The value is automatically converted from Go types to TCL:

  • string, int, int64, float64, bool are converted directly

  • []string becomes a TCL list

  • Other types use fmt.Sprintf("%v", val)

    interp.SetVar("name", "Alice") interp.SetVar("count", 42) interp.SetVar("items", []string{"a", "b", "c"})

func (*Interp) SetVars

func (i *Interp) SetVars(vars map[string]any)

SetVars sets multiple variables at once from a map.

This is a convenience method equivalent to calling Interp.SetVar for each entry.

interp.SetVars(map[string]any{
    "x": 1,
    "y": 2,
    "name": "Alice",
})

func (*Interp) String

func (i *Interp) String(s string) *Obj

String creates a string object.

s := interp.String("hello world")
s.Type()   // "string"
s.String() // "hello world"

func (*Interp) Type

func (i *Interp) Type(h FeatherObj) string

Type returns the native type of an object.

func (*Interp) Var

func (i *Interp) Var(name string) *Obj

Var returns the value of a variable as a *Obj.

Returns an empty string object if the variable does not exist. The returned object preserves the variable's type (int, list, foreign, etc.).

interp.SetVar("x", 42)
v := interp.Var("x")
feather.AsInt(v)  // 42, nil
v.Type()          // "int" (if SetVar preserved type) or "string"

func (*Interp) Wrap

func (i *Interp) Wrap(h FeatherObj) ObjHandle

Wrap creates an ObjHandle for method-based access to handle data.

Low-level API. May change between versions.

type IntoBool

type IntoBool interface {
	IntoBool() (bool, bool)
}

IntoBool can convert directly to a boolean.

type IntoDict

type IntoDict interface {
	IntoDict() (map[string]*Obj, []string, bool)
}

IntoDict can convert directly to a dictionary.

type IntoDouble

type IntoDouble interface {
	IntoDouble() (float64, bool)
}

IntoDouble can convert directly to float64.

type IntoInt

type IntoInt interface {
	IntoInt() (int64, bool)
}

IntoInt can convert directly to int64.

type IntoList

type IntoList interface {
	IntoList() ([]*Obj, bool)
}

IntoList can convert directly to a list.

type ListSortContext

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

ListSortContext holds context for list sorting

type ListType

type ListType []*Obj

ListType is the internal representation for list values.

func (ListType) Dup

func (t ListType) Dup() ObjType

func (ListType) IntoDict

func (t ListType) IntoDict() (map[string]*Obj, []string, bool)

func (ListType) IntoList

func (t ListType) IntoList() ([]*Obj, bool)

func (ListType) Name

func (t ListType) Name() string

func (ListType) UpdateString

func (t ListType) UpdateString() string

type Namespace

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

Namespace represents a namespace in the hierarchy

type Obj

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

Obj is a Feather value. It follows TCL semantics where values have both a string representation and an optional internal representation that can be lazily computed.

func AsList

func AsList(o *Obj) ([]*Obj, error)

AsList converts o to a list if it has a list-compatible internal representation. For string-to-list conversion, use the interpreter's parsing facilities.

func NewDictObj

func NewDictObj() *Obj

NewDictObj creates a new empty dict object.

func NewDoubleObj

func NewDoubleObj(v float64) *Obj

NewDoubleObj creates a new object with a floating-point value.

func NewForeignObj

func NewForeignObj(typeName string, value any) *Obj

NewForeignObj creates a new foreign object with the given type name and Go value.

func NewIntObj

func NewIntObj(v int64) *Obj

NewIntObj creates a new object with an integer value.

func NewListObj

func NewListObj(items ...*Obj) *Obj

NewListObj creates a new object with a list value.

func NewObj

func NewObj(intrep ObjType) *Obj

NewObj creates a new object with a custom ObjType internal representation. Use this when implementing custom shimmering types.

Example:

type RegexType struct {
    pattern string
    re      *regexp.Regexp
}
func (t *RegexType) Name() string         { return "regex" }
func (t *RegexType) UpdateString() string { return t.pattern }
func (t *RegexType) Dup() feather.ObjType { return t }

func NewRegex(pattern string, re *regexp.Regexp) *feather.Obj {
    return feather.NewObj(&RegexType{pattern: pattern, re: re})
}

func NewStringObj

func NewStringObj(s string) *Obj

NewStringObj creates a new object with a string value.

func ObjDictGet

func ObjDictGet(o *Obj, key string) (*Obj, bool)

ObjDictGet returns the value for a key in a dict object. Returns nil, false if the object is not a dict or key doesn't exist.

func ObjListAt

func ObjListAt(o *Obj, i int) *Obj

ObjListAt returns the element at index i in a list object. Returns nil if the object is not a list or index is out of bounds.

func (*Obj) Bool

func (o *Obj) Bool() (bool, error)

Bool returns the boolean value of this object using TCL boolean rules.

func (*Obj) Copy

func (o *Obj) Copy() *Obj

Copy creates a shallow copy of the object. If the object has an internal representation, it is duplicated via Dup().

func (*Obj) Dict

func (o *Obj) Dict() (*DictType, error)

Dict returns the dict representation of this object. Note: This only works on objects that already have a dict representation. To parse a string as a dict, use Interp.ParseDict().

func (*Obj) Double

func (o *Obj) Double() (float64, error)

Double returns the float64 value of this object, shimmering if needed.

func (*Obj) Int

func (o *Obj) Int() (int64, error)

Int returns the integer value of this object, shimmering if needed.

func (*Obj) InternalRep

func (o *Obj) InternalRep() ObjType

InternalRep returns the internal representation of the object. Returns nil for pure string objects.

Use type assertion to access custom ObjType implementations:

if myType, ok := obj.InternalRep().(*MyType); ok {
    // use myType
}

func (*Obj) List

func (o *Obj) List() ([]*Obj, error)

List returns the list elements of this object. Note: This only works on objects that already have a list representation. To parse a string as a list, use Interp.ParseList().

func (*Obj) String

func (o *Obj) String() string

String returns the string representation of the object. If the string representation is empty and there's an internal representation, it regenerates the string from the internal rep.

func (*Obj) Type

func (o *Obj) Type() string

Type returns the type name of the object. Returns "string" for pure string objects (no internal representation).

type ObjHandle

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

ObjHandle wraps a FeatherObj handle with its interpreter for method access. This type exists only in Go and never crosses the CGo boundary.

Use Interp.Handle to create an ObjHandle from a FeatherObj.

Low-level API. May change between versions.

func (ObjHandle) Dict

func (o ObjHandle) Dict() (map[string]ObjHandle, []string, error)

Dict returns dict as map with ObjHandle values, or error if not a dict. Returns the items map, key order slice, and any error.

func (ObjHandle) Double

func (o ObjHandle) Double() (float64, error)

Double returns the float64 value, or error if not convertible.

func (ObjHandle) ForeignType

func (o ObjHandle) ForeignType() string

ForeignType returns the type name if foreign, empty string otherwise.

func (ObjHandle) ForeignValue

func (o ObjHandle) ForeignValue() any

ForeignValue returns the Go value if foreign, nil otherwise.

func (ObjHandle) Int

func (o ObjHandle) Int() (int64, error)

Int returns the integer value, or error if not convertible.

func (ObjHandle) IsForeign

func (o ObjHandle) IsForeign() bool

IsForeign returns true if this is a foreign object.

func (ObjHandle) List

func (o ObjHandle) List() ([]ObjHandle, error)

List returns list elements as ObjHandles, or error if not a list.

func (ObjHandle) Raw

func (o ObjHandle) Raw() FeatherObj

Raw returns the underlying FeatherObj handle.

func (ObjHandle) String

func (o ObjHandle) String() string

String returns the string representation of the handle.

type ObjType

type ObjType interface {
	// Name returns the type name (e.g., "int", "list").
	Name() string

	// UpdateString regenerates string representation from this internal rep.
	UpdateString() string

	// Dup creates a copy of this internal representation.
	Dup() ObjType
}

ObjType defines the core behavior for an internal representation.

type ParseResult

type ParseResult struct {
	// Status indicates whether parsing succeeded, found incomplete input, or failed.
	Status ParseStatus

	// Message contains an error message if Status is ParseError.
	Message string
}

ParseResult holds the result of parsing a script.

type ParseResultInternal

type ParseResultInternal struct {
	Status       InternalParseStatus
	Result       string // The interpreter's result string (e.g., "{INCOMPLETE 5 20}")
	ErrorMessage string // For ParseError, the error message from the result list
}

ParseResultInternal holds the result of parsing a script

type ParseStatus

type ParseStatus int

ParseStatus indicates the result of parsing a script.

const (
	// ParseOK indicates the script is syntactically complete and valid.
	ParseOK ParseStatus = ParseStatus(InternalParseOK)

	// ParseIncomplete indicates the script has unclosed braces, brackets, or quotes.
	ParseIncomplete ParseStatus = ParseStatus(InternalParseIncomplete)

	// ParseError indicates a syntax error in the script.
	ParseError ParseStatus = ParseStatus(InternalParseError)
)

type Procedure

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

Procedure represents a user-defined procedure

type Result

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

Result represents the result of a command execution.

Create results using OK, Error, or Errorf.

func Error

func Error(v any) Result

Error returns an error result with a message or *Obj.

Pass a string for simple error messages, or a *Obj for structured errors.

return feather.Error("something went wrong")
return feather.Error(errDict)  // structured error

func Errorf

func Errorf(format string, args ...any) Result

Errorf returns a formatted error result.

return feather.Errorf("expected %d args, got %d", want, got)

func OK

func OK(v any) Result

OK returns a successful result with a value.

The value is auto-converted to a TCL string representation. Pass a *Obj directly to preserve its internal type (int, list, dict, etc.).

return feather.OK("success")
return feather.OK(42)
return feather.OK([]string{"a", "b"})
return feather.OK(myObj)  // preserves *Obj type

type TypeDef

type TypeDef[T any] struct {
	// New is the constructor function, called when "TypeName new" is evaluated.
	// Required.
	New func() T

	// Methods maps method names to Go functions.
	// Each function's first parameter must be the receiver type T.
	// Additional parameters and return values are auto-converted.
	Methods map[string]any

	// String optionally provides a custom string representation.
	// If nil, a default "<TypeName:address>" format is used.
	String func(T) string

	// Destroy is called when the object is garbage collected or explicitly destroyed.
	// Use for cleanup (closing files, connections, etc.).
	Destroy func(T)
}

TypeDef defines a foreign type that can be exposed to TCL.

Foreign types allow Go structs to be used as TCL objects with methods. See RegisterType for usage.

Directories

Path Synopsis
cmd
feather-httpd command
feather-httpd is an example HTTP server configurable via the feather TCL interpreter.
feather-httpd is an example HTTP server configurable via the feather TCL interpreter.
feather-tester command
feather-tester is the interpreter used by the test harness.
feather-tester is the interpreter used by the test harness.