Added custom printers

This commit is contained in:
Leon Mika 2024-12-11 22:30:14 +11:00
parent 62724e3f37
commit 7a92eeddac
7 changed files with 287 additions and 63 deletions

57
cmd/cmsh/fancy.go Normal file
View file

@ -0,0 +1,57 @@
package main
import (
"context"
"fmt"
"io"
"ucl.lmika.dev/repl"
"ucl.lmika.dev/ucl"
)
type FancyType struct {
Foo string
Bar string
}
var newFancyDoc = repl.Doc{
Brief: "returns something fancy",
Detailed: `
This will create and return something fancy.
`,
}
func newFancy(ctx context.Context, args ucl.CallArgs) (any, error) {
return FancyType{Foo: "is foo", Bar: "is bar"}, nil
}
func manyFancies(ctx context.Context, args ucl.CallArgs) (any, error) {
return []FancyType{
{Foo: "foo 1", Bar: "bar 1"},
{Foo: "foo 2", Bar: "bar 2"},
{Foo: "foo 3", Bar: "bar 3"},
}, nil
}
func displayFancy(w io.Writer, f FancyType, concise bool) error {
if concise {
_, err := fmt.Fprintf(w, "%s:%s", f.Foo, f.Bar)
return err
}
fmt.Fprintf(w, "Foo.. %s\n", f.Foo)
fmt.Fprintf(w, "Bar.. %s\n", f.Bar)
return nil
}
func displayFancies(w io.Writer, fs []FancyType, concise bool) error {
if concise {
_, err := fmt.Fprintf(w, "%d fancies", len(fs))
return err
}
fmt.Fprintln(w, "FOO\tBAR")
for _, f := range fs {
fmt.Fprintf(w, "%v\t%v\n", f.Foo, f.Bar)
}
return nil
}

View file

@ -33,6 +33,11 @@ func main() {
It then terminates. It then terminates.
`, `,
}) })
instRepl.SetCommand("new-fancy", newFancy, newFancyDoc)
instRepl.SetCommand("many-fancies", manyFancies)
repl.AddTypePrinter(instRepl, displayFancy)
repl.AddTypePrinter(instRepl, displayFancies)
for { for {
line, err := rl.Readline() line, err := rl.Readline()

View file

@ -3,7 +3,10 @@ package repl
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"os" "os"
"reflect"
"strings"
"ucl.lmika.dev/ucl" "ucl.lmika.dev/ucl"
) )
@ -15,31 +18,118 @@ func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
return err return err
} }
return displayResult(ctx, r.inst, res) return r.displayResult(ctx, os.Stdout, res, false)
} }
func displayResult(ctx context.Context, inst *ucl.Inst, res any) (err error) { func (r *REPL) echoPrinter(ctx context.Context, w io.Writer, args []any) (err error) {
switch v := res.(type) { if len(args) == 0 {
case NoResults: _, err := fmt.Fprintln(w)
return err
}
var line strings.Builder
for _, arg := range args {
if err := r.displayResult(ctx, &line, arg, len(args) > 1); err != nil {
return err
}
}
res := line.String()
if strings.HasSuffix(res, "\n") {
res = res[:len(res)-1]
}
_, err = fmt.Fprintln(w, res)
return nil return nil
}
func (r *REPL) displayResult(ctx context.Context, w io.Writer, res any, concise bool) (err error) {
// Check type printers
tp, ok := r.typePrinters[reflect.TypeOf(res)]
if ok {
return tp(w, res, concise)
}
switch v := res.(type) {
case nil: case nil:
if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil { if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil {
return err return err
} }
case ucl.Listable: case ucl.Listable:
if concise {
fmt.Fprintf(w, "[")
for i := 0; i < v.Len(); i++ { for i := 0; i < v.Len(); i++ {
if err = displayResult(ctx, inst, v.Index(i)); err != nil { if i > 0 {
fmt.Fprintf(w, " ")
}
if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
return err return err
} }
} }
fmt.Fprintf(w, "]")
} else {
for i := 0; i < v.Len(); i++ {
if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
return err
}
fmt.Fprintf(w, "\n")
}
}
case []interface{}:
if concise {
fmt.Fprintf(w, "[")
for i := 0; i < len(v); i++ {
if i > 0 {
fmt.Fprintf(w, " ")
}
if err = r.displayResult(ctx, w, v[i], true); err != nil {
return err
}
}
fmt.Fprintf(w, "]")
} else {
// In the off-chance that this is actually a slice of printables
vt := reflect.SliceOf(reflect.TypeOf(v[0]))
if tp, ok := r.typePrinters[vt]; ok {
canDisplay := true
typeSlice := reflect.MakeSlice(vt, len(v), len(v))
for i := 0; i < len(v); i++ {
vv := reflect.ValueOf(v[i])
if vv.CanConvert(vt.Elem()) {
typeSlice.Index(i).Set(vv)
} else {
canDisplay = false
break
}
}
if canDisplay {
return tp(w, typeSlice.Interface(), concise)
}
}
for i := 0; i < len(v); i++ {
if err = r.displayResult(ctx, w, v[i], true); err != nil {
return err
}
fmt.Fprintf(w, "\n")
}
}
case ucl.Object: case ucl.Object:
if _, err = fmt.Fprintln(os.Stdout, v.String()); err != nil { if _, err = fmt.Fprint(w, v.String()); err != nil {
return err return err
} }
if !concise {
fmt.Fprintln(w)
}
default: default:
if _, err = fmt.Fprintln(os.Stdout, v); err != nil { if _, err = fmt.Fprint(w, v); err != nil {
return err return err
} }
if !concise {
fmt.Fprintln(w)
}
} }
return nil return nil
} }

View file

@ -1,6 +1,9 @@
package repl package repl
import ( import (
"io"
"reflect"
"slices"
"ucl.lmika.dev/ucl" "ucl.lmika.dev/ucl"
) )
@ -12,15 +15,18 @@ type REPL struct {
inst *ucl.Inst inst *ucl.Inst
commandDocs map[string]Doc commandDocs map[string]Doc
typePrinters map[reflect.Type]func(w io.Writer, v any, brief bool) error
} }
func New(opts ...ucl.InstOption) *REPL { func New(opts ...ucl.InstOption) *REPL {
inst := ucl.New(opts...)
r := &REPL{ r := &REPL{
inst: inst,
commandDocs: make(map[string]Doc), commandDocs: make(map[string]Doc),
typePrinters: make(map[reflect.Type]func(w io.Writer, v any, brief bool) error),
} }
instOpts := append(slices.Clone(opts), ucl.WithCustomEchoPrinter(r.echoPrinter))
r.inst = ucl.New(instOpts...)
r.SetCommand("help", r.helpBuiltin, Doc{ r.SetCommand("help", r.helpBuiltin, Doc{
Brief: "displays help about a command", Brief: "displays help about a command",
Usage: "[command]", Usage: "[command]",
@ -36,6 +42,8 @@ func New(opts ...ucl.InstOption) *REPL {
`, `,
}) })
AddTypePrinter(r, func(w io.Writer, t NoResults, concise bool) error { return nil })
return r return r
} }

20
repl/typeprinter.go Normal file
View file

@ -0,0 +1,20 @@
package repl
import (
"fmt"
"io"
"reflect"
)
func AddTypePrinter[T any](r *REPL, p func(w io.Writer, t T, concise bool) error) {
var t T
tt := reflect.TypeOf(t)
r.typePrinters[tt] = func(w io.Writer, v any, concise bool) error {
vt, ok := v.(T)
if !ok {
return fmt.Errorf("cannot convert %v to T", v)
}
return p(w, vt, concise)
}
}

View file

@ -4,10 +4,24 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strconv"
"strings" "strings"
) )
func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) { func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
echoPrinter := args.inst.echoPrinter
if echoPrinter != nil {
convertedArgs := make([]interface{}, len(args.args))
for i, arg := range args.args {
if convArg, ok := toGoValue(arg); ok {
convertedArgs[i] = convArg
} else {
convertedArgs[i] = arg
}
}
return nil, echoPrinter(ctx, args.inst.out, convertedArgs)
}
if len(args.args) == 0 { if len(args.args) == 0 {
if _, err := fmt.Fprintln(args.inst.Out()); err != nil { if _, err := fmt.Fprintln(args.inst.Out()); err != nil {
return nil, err return nil, err
@ -28,7 +42,7 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return nil, nil return nil, nil
} }
func addBuiltin(ctx context.Context, args invocationArgs) (object, error) { func addBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 { if len(args.args) == 0 {
return intObject(0), nil return intObject(0), nil
} }
@ -52,7 +66,7 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(n), nil return intObject(n), nil
} }
func subBuiltin(ctx context.Context, args invocationArgs) (object, error) { func subBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 { if len(args.args) == 0 {
return intObject(0), nil return intObject(0), nil
} }
@ -82,7 +96,7 @@ func subBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(n), nil return intObject(n), nil
} }
func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) { func mupBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 { if len(args.args) == 0 {
return intObject(1), nil return intObject(1), nil
} }
@ -106,7 +120,7 @@ func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(n), nil return intObject(n), nil
} }
func divBuiltin(ctx context.Context, args invocationArgs) (object, error) { func divBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 { if len(args.args) == 0 {
return intObject(1), nil return intObject(1), nil
} }
@ -136,7 +150,7 @@ func divBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(n), nil return intObject(n), nil
} }
func modBuiltin(ctx context.Context, args invocationArgs) (object, error) { func modBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) == 0 { if len(args.args) == 0 {
return intObject(0), nil return intObject(0), nil
} }
@ -166,7 +180,7 @@ func modBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(n), nil return intObject(n), nil
} }
func setBuiltin(ctx context.Context, args invocationArgs) (object, error) { func setBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -182,7 +196,7 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return newVal, nil return newVal, nil
} }
func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) { func toUpperBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -193,7 +207,7 @@ func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return strObject(strings.ToUpper(sarg)), nil return strObject(strings.ToUpper(sarg)), nil
} }
func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) { func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -204,7 +218,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(objectsEqual(l, r)), nil return boolObject(objectsEqual(l, r)), nil
} }
func neBuiltin(ctx context.Context, args invocationArgs) (object, error) { func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -215,7 +229,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(!objectsEqual(l, r)), nil return boolObject(!objectsEqual(l, r)), nil
} }
func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) { func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -227,7 +241,7 @@ func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(isLess), nil return boolObject(isLess), nil
} }
func leBuiltin(ctx context.Context, args invocationArgs) (object, error) { func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -239,7 +253,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil return boolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil
} }
func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) { func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -251,7 +265,7 @@ func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(isGreater), nil return boolObject(isGreater), nil
} }
func geBuiltin(ctx context.Context, args invocationArgs) (object, error) { func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -263,7 +277,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil
} }
func andBuiltin(ctx context.Context, args invocationArgs) (object, error) { func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -276,7 +290,7 @@ func andBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return args.args[len(args.args)-1], nil return args.args[len(args.args)-1], nil
} }
func orBuiltin(ctx context.Context, args invocationArgs) (object, error) { func orBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -289,7 +303,7 @@ func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return boolObject(false), nil return boolObject(false), nil
} }
func notBuiltin(ctx context.Context, args invocationArgs) (object, error) { func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -299,7 +313,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
var errObjectsNotEqual = errors.New("objects not equal") var errObjectsNotEqual = errors.New("objects not equal")
func objectsEqual(l, r object) bool { func objectsEqual(l, r Object) bool {
if l == nil || r == nil { if l == nil || r == nil {
return l == nil && r == nil return l == nil && r == nil
} }
@ -317,8 +331,8 @@ func objectsEqual(l, r object) bool {
if rv, ok := r.(boolObject); ok { if rv, ok := r.(boolObject); ok {
return lv == rv return lv == rv
} }
case listable: case Listable:
rv, ok := r.(listable) rv, ok := r.(Listable)
if !ok { if !ok {
return false return false
} }
@ -341,7 +355,7 @@ func objectsEqual(l, r object) bool {
if lv.Len() != rv.Len() { if lv.Len() != rv.Len() {
return false return false
} }
if err := lv.Each(func(k string, lkv object) error { if err := lv.Each(func(k string, lkv Object) error {
rkv := rv.Value(k) rkv := rv.Value(k)
if rkv == nil { if rkv == nil {
return errObjectsNotEqual return errObjectsNotEqual
@ -357,7 +371,7 @@ func objectsEqual(l, r object) bool {
return false return false
} }
func objectsLessThan(l, r object) (bool, error) { func objectsLessThan(l, r Object) (bool, error) {
switch lv := l.(type) { switch lv := l.(type) {
case strObject: case strObject:
if rv, ok := r.(strObject); ok { if rv, ok := r.(strObject); ok {
@ -371,7 +385,7 @@ func objectsLessThan(l, r object) (bool, error) {
return false, errors.New("objects are not comparable") return false, errors.New("objects are not comparable")
} }
func strBuiltin(ctx context.Context, args invocationArgs) (object, error) { func strBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -383,7 +397,7 @@ func strBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return strObject(args.args[0].String()), nil return strObject(args.args[0].String()), nil
} }
func intBuiltin(ctx context.Context, args invocationArgs) (object, error) { func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -411,7 +425,7 @@ func intBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return nil, errors.New("cannot convert to int") return nil, errors.New("cannot convert to int")
} }
func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) { func concatBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
var sb strings.Builder var sb strings.Builder
for _, a := range args.args { for _, a := range args.args {
@ -424,7 +438,7 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return strObject(sb.String()), nil return strObject(sb.String()), nil
} }
func callBuiltin(ctx context.Context, args invocationArgs) (object, error) { func callBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -437,7 +451,7 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return inv.invoke(ctx, args.shift(1)) return inv.invoke(ctx, args.shift(1))
} }
func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) { func lenBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -445,7 +459,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
switch v := args.args[0].(type) { switch v := args.args[0].(type) {
case strObject: case strObject:
return intObject(len(string(v))), nil return intObject(len(string(v))), nil
case listable: case Listable:
return intObject(v.Len()), nil return intObject(v.Len()), nil
case hashable: case hashable:
return intObject(v.Len()), nil return intObject(v.Len()), nil
@ -454,9 +468,9 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(0), nil return intObject(0), nil
} }
func indexLookup(ctx context.Context, obj, elem object) (object, error) { func indexLookup(ctx context.Context, obj, elem Object) (Object, error) {
switch v := obj.(type) { switch v := obj.(type) {
case listable: case Listable:
intIdx, ok := elem.(intObject) intIdx, ok := elem.(intObject)
if !ok { if !ok {
return nil, nil return nil, nil
@ -475,7 +489,7 @@ func indexLookup(ctx context.Context, obj, elem object) (object, error) {
return nil, nil return nil, nil
} }
func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) { func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
} }
@ -492,7 +506,28 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return val, nil return val, nil
} }
func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) { func keysBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(1); err != nil {
return nil, err
}
val := args.args[0]
switch v := val.(type) {
case hashable:
keys := make(listObject, 0, v.Len())
if err := v.Each(func(k string, _ Object) error {
keys = append(keys, strObject(k))
return nil
}); err != nil {
return nil, err
}
return keys, nil
}
return nil, nil
}
func mapBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if err := args.expectArgn(2); err != nil { if err := args.expectArgn(2); err != nil {
return nil, err return nil, err
} }
@ -503,12 +538,12 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
} }
switch t := args.args[0].(type) { switch t := args.args[0].(type) {
case listable: case Listable:
l := t.Len() l := t.Len()
newList := listObject{} newList := listObject{}
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
v := t.Index(i) v := t.Index(i)
m, err := inv.invoke(ctx, args.fork([]object{v})) m, err := inv.invoke(ctx, args.fork([]Object{v}))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -525,7 +560,7 @@ func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
} }
switch t := args.args[0].(type) { switch t := args.args[0].(type) {
case listable: case Listable:
if t.Len() == 0 { if t.Len() == 0 {
return nil, nil return nil, nil
} }
@ -561,7 +596,7 @@ func (s seqObject) Len() int {
return l return l
} }
func (s seqObject) Index(i int) object { func (s seqObject) Index(i int) Object {
l := s.Len() l := s.Len()
if i < 0 || i > l { if i < 0 || i > l {
return nil return nil
@ -572,7 +607,7 @@ func (s seqObject) Index(i int) object {
return intObject(s.from + i) return intObject(s.from + i)
} }
func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) { func seqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
inclusive := false inclusive := false
if inc, ok := args.kwargs["inc"]; ok { if inc, ok := args.kwargs["inc"]; ok {
inclusive = (inc.Len() == 0) || inc.Truthy() inclusive = (inc.Len() == 0) || inc.Truthy()
@ -601,7 +636,7 @@ func seqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
} }
} }
func ifBuiltin(ctx context.Context, args macroArgs) (object, error) { func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) {
if args.nargs() < 2 { if args.nargs() < 2 {
return nil, errors.New("need at least 2 arguments") return nil, errors.New("need at least 2 arguments")
} }
@ -641,7 +676,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (object, error) {
func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) { func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
var ( var (
items object items Object
blockIdx int blockIdx int
err error err error
) )
@ -664,16 +699,16 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
} }
var ( var (
last object last Object
breakErr errBreak breakErr errBreak
) )
switch t := items.(type) { switch t := items.(type) {
case listable: case Listable:
l := t.Len() l := t.Len()
for i := 0; i < l; i++ { for i := 0; i < l; i++ {
v := t.Index(i) v := t.Index(i)
last, err = args.evalBlock(ctx, blockIdx, []object{v}, true) // TO INCLUDE: the index last, err = args.evalBlock(ctx, blockIdx, []Object{v}, true) // TO INCLUDE: the index
if err != nil { if err != nil {
if errors.As(err, &breakErr) { if errors.As(err, &breakErr) {
if !breakErr.isCont { if !breakErr.isCont {
@ -685,8 +720,8 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
} }
} }
case hashable: case hashable:
err := t.Each(func(k string, v object) error { err := t.Each(func(k string, v Object) error {
last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true) last, err = args.evalBlock(ctx, blockIdx, []Object{strObject(k), v}, true)
return err return err
}) })
if errors.As(err, &breakErr) { if errors.As(err, &breakErr) {
@ -701,25 +736,25 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
return last, nil return last, nil
} }
func breakBuiltin(ctx context.Context, args invocationArgs) (object, error) { func breakBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 { if len(args.args) < 1 {
return nil, errBreak{} return nil, errBreak{}
} }
return nil, errBreak{ret: args.args[0]} return nil, errBreak{ret: args.args[0]}
} }
func continueBuiltin(ctx context.Context, args invocationArgs) (object, error) { func continueBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
return nil, errBreak{isCont: true} return nil, errBreak{isCont: true}
} }
func returnBuiltin(ctx context.Context, args invocationArgs) (object, error) { func returnBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 { if len(args.args) < 1 {
return nil, errReturn{} return nil, errReturn{}
} }
return nil, errReturn{ret: args.args[0]} return nil, errReturn{ret: args.args[0]}
} }
func procBuiltin(ctx context.Context, args macroArgs) (object, error) { func procBuiltin(ctx context.Context, args macroArgs) (Object, error) {
if args.nargs() < 1 { if args.nargs() < 1 {
return nil, errors.New("need at least one arguments") return nil, errors.New("need at least one arguments")
} }
@ -763,7 +798,7 @@ func (b procObject) Truthy() bool {
return true return true
} }
func (b procObject) invoke(ctx context.Context, args invocationArgs) (object, error) { func (b procObject) invoke(ctx context.Context, args invocationArgs) (Object, error) {
newEc := b.ec.fork() newEc := b.ec.fork()
for i, name := range b.block.Names { for i, name := range b.block.Names {

View file

@ -11,6 +11,7 @@ import (
type Inst struct { type Inst struct {
out io.Writer out io.Writer
missingBuiltinHandler MissingBuiltinHandler missingBuiltinHandler MissingBuiltinHandler
echoPrinter EchoPrinter
rootEC *evalCtx rootEC *evalCtx
} }
@ -37,6 +38,14 @@ func WithModule(module Module) InstOption {
} }
} }
type EchoPrinter func(ctx context.Context, w io.Writer, args []any) error
func WithCustomEchoPrinter(echoPrinter EchoPrinter) InstOption {
return func(i *Inst) {
i.echoPrinter = echoPrinter
}
}
type Module struct { type Module struct {
Name string Name string
Builtins map[string]BuiltinHandler Builtins map[string]BuiltinHandler