Files
zzg b9bd39689e
ci / test (push) Canceled after 0s
initial commit
2026-09-25 21:40:55 -07:00

296 lines
7.9 KiB
Go

package eval
import (
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"github.com/zg/interp/object"
)
// builtins are the functions available without being defined in the
// language itself.
var builtins map[string]*object.Builtin
// init rather than a literal, because several builtins call helpers that
// reference the map itself.
func init() {
builtins = map[string]*object.Builtin{}
register := func(name string, fn object.BuiltinFunction) {
builtins[name] = &object.Builtin{Name: name, Fn: fn}
}
register("len", func(args ...object.Object) object.Object {
if err := wantArgs("len", 1, args); err != nil {
return err
}
switch arg := args[0].(type) {
case *object.String:
// Count runes, not bytes: len("é") is 1.
return &object.Integer{Value: int64(len([]rune(arg.Value)))}
case *object.Array:
return &object.Integer{Value: int64(len(arg.Elements))}
case *object.Hash:
return &object.Integer{Value: int64(len(arg.Pairs))}
default:
return argError("len", "STRING, ARRAY or HASH", args[0])
}
})
register("first", func(args ...object.Object) object.Object {
if err := wantArgs("first", 1, args); err != nil {
return err
}
arr, ok := args[0].(*object.Array)
if !ok {
return argError("first", "ARRAY", args[0])
}
if len(arr.Elements) == 0 {
return NULL
}
return arr.Elements[0]
})
register("last", func(args ...object.Object) object.Object {
if err := wantArgs("last", 1, args); err != nil {
return err
}
arr, ok := args[0].(*object.Array)
if !ok {
return argError("last", "ARRAY", args[0])
}
if len(arr.Elements) == 0 {
return NULL
}
return arr.Elements[len(arr.Elements)-1]
})
register("rest", func(args ...object.Object) object.Object {
if err := wantArgs("rest", 1, args); err != nil {
return err
}
arr, ok := args[0].(*object.Array)
if !ok {
return argError("rest", "ARRAY", args[0])
}
if len(arr.Elements) == 0 {
return NULL
}
// Copy: returning arr.Elements[1:] would alias the original's
// backing array, so a later push could mutate both.
out := make([]object.Object, len(arr.Elements)-1)
copy(out, arr.Elements[1:])
return &object.Array{Elements: out}
})
register("push", func(args ...object.Object) object.Object {
if err := wantArgs("push", 2, args); err != nil {
return err
}
arr, ok := args[0].(*object.Array)
if !ok {
return argError("push", "ARRAY", args[0])
}
// Arrays are values here: push returns a new array rather than
// mutating, so aliasing cannot surprise the caller.
out := make([]object.Object, len(arr.Elements), len(arr.Elements)+1)
copy(out, arr.Elements)
return &object.Array{Elements: append(out, args[1])}
})
register("keys", func(args ...object.Object) object.Object {
if err := wantArgs("keys", 1, args); err != nil {
return err
}
hash, ok := args[0].(*object.Hash)
if !ok {
return argError("keys", "HASH", args[0])
}
out := make([]object.Object, 0, len(hash.Order))
for _, k := range hash.Order {
out = append(out, hash.Pairs[k].Key)
}
return &object.Array{Elements: out}
})
register("values", func(args ...object.Object) object.Object {
if err := wantArgs("values", 1, args); err != nil {
return err
}
hash, ok := args[0].(*object.Hash)
if !ok {
return argError("values", "HASH", args[0])
}
out := make([]object.Object, 0, len(hash.Order))
for _, k := range hash.Order {
out = append(out, hash.Pairs[k].Value)
}
return &object.Array{Elements: out}
})
register("puts", func(args ...object.Object) object.Object {
parts := make([]string, len(args))
for i, a := range args {
parts[i] = a.Inspect()
}
fmt.Fprintln(Stdout, strings.Join(parts, " "))
return NULL
})
register("str", func(args ...object.Object) object.Object {
if err := wantArgs("str", 1, args); err != nil {
return err
}
return &object.String{Value: args[0].Inspect()}
})
register("int", func(args ...object.Object) object.Object {
if err := wantArgs("int", 1, args); err != nil {
return err
}
switch arg := args[0].(type) {
case *object.Integer:
return arg
case *object.Float:
return &object.Integer{Value: int64(arg.Value)}
case *object.String:
n, err := strconv.ParseInt(strings.TrimSpace(arg.Value), 10, 64)
if err != nil {
return &object.Error{Message: fmt.Sprintf("int: cannot parse %q", arg.Value)}
}
return &object.Integer{Value: n}
default:
return argError("int", "INTEGER, FLOAT or STRING", args[0])
}
})
register("float", func(args ...object.Object) object.Object {
if err := wantArgs("float", 1, args); err != nil {
return err
}
switch arg := args[0].(type) {
case *object.Float:
return arg
case *object.Integer:
return &object.Float{Value: float64(arg.Value)}
case *object.String:
f, err := strconv.ParseFloat(strings.TrimSpace(arg.Value), 64)
if err != nil {
return &object.Error{Message: fmt.Sprintf("float: cannot parse %q", arg.Value)}
}
return &object.Float{Value: f}
default:
return argError("float", "INTEGER, FLOAT or STRING", args[0])
}
})
register("type", func(args ...object.Object) object.Object {
if err := wantArgs("type", 1, args); err != nil {
return err
}
return &object.String{Value: string(args[0].Type())}
})
register("split", func(args ...object.Object) object.Object {
if err := wantArgs("split", 2, args); err != nil {
return err
}
s, ok := args[0].(*object.String)
if !ok {
return argError("split", "STRING", args[0])
}
sep, ok := args[1].(*object.String)
if !ok {
return argError("split", "STRING", args[1])
}
var parts []string
if sep.Value == "" {
parts = strings.Fields(s.Value)
} else {
parts = strings.Split(s.Value, sep.Value)
}
out := make([]object.Object, len(parts))
for i, p := range parts {
out[i] = &object.String{Value: p}
}
return &object.Array{Elements: out}
})
register("join", func(args ...object.Object) object.Object {
if err := wantArgs("join", 2, args); err != nil {
return err
}
arr, ok := args[0].(*object.Array)
if !ok {
return argError("join", "ARRAY", args[0])
}
sep, ok := args[1].(*object.String)
if !ok {
return argError("join", "STRING", args[1])
}
parts := make([]string, len(arr.Elements))
for i, e := range arr.Elements {
parts[i] = e.Inspect()
}
return &object.String{Value: strings.Join(parts, sep.Value)}
})
register("sort", func(args ...object.Object) object.Object {
if err := wantArgs("sort", 1, args); err != nil {
return err
}
arr, ok := args[0].(*object.Array)
if !ok {
return argError("sort", "ARRAY", args[0])
}
out := make([]object.Object, len(arr.Elements))
copy(out, arr.Elements)
var sortErr object.Object
sort.SliceStable(out, func(i, j int) bool {
l, r := out[i], out[j]
switch {
case isNumeric(l) && isNumeric(r):
return toFloat(l) < toFloat(r)
case l.Type() == object.STRING && r.Type() == object.STRING:
return l.(*object.String).Value < r.(*object.String).Value
default:
if sortErr == nil {
sortErr = &object.Error{Message: fmt.Sprintf("sort: cannot compare %s with %s", l.Type(), r.Type())}
}
return false
}
})
if sortErr != nil {
return sortErr
}
return &object.Array{Elements: out}
})
}
// Stdout is where puts writes. It is a variable so tests can capture output.
var Stdout io.Writer = os.Stdout
func wantArgs(name string, n int, args []object.Object) object.Object {
if len(args) != n {
return &object.Error{Message: fmt.Sprintf("wrong number of arguments to %s: want %d, got %d", name, n, len(args))}
}
return nil
}
func argError(name, want string, got object.Object) object.Object {
return &object.Error{Message: fmt.Sprintf("argument to %s must be %s, got %s", name, want, got.Type())}
}
// Builtins returns the names of all builtin functions, for the REPL's help.
func Builtins() []string {
names := make([]string, 0, len(builtins))
for n := range builtins {
names = append(names, n)
}
sort.Strings(names)
return names
}