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.
`,
})
instRepl.SetCommand("new-fancy", newFancy, newFancyDoc)
instRepl.SetCommand("many-fancies", manyFancies)
repl.AddTypePrinter(instRepl, displayFancy)
repl.AddTypePrinter(instRepl, displayFancies)
for {
line, err := rl.Readline()

View file

@ -3,7 +3,10 @@ package repl
import (
"context"
"fmt"
"io"
"os"
"reflect"
"strings"
"ucl.lmika.dev/ucl"
)
@ -15,31 +18,118 @@ func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
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) {
if len(args) == 0 {
_, 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
}
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 NoResults:
return nil
case nil:
if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil {
return err
}
case ucl.Listable:
for i := 0; i < v.Len(); i++ {
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
return err
if concise {
fmt.Fprintf(w, "[")
for i := 0; i < v.Len(); i++ {
if i > 0 {
fmt.Fprintf(w, " ")
}
if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
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:
if _, err = fmt.Fprintln(os.Stdout, v.String()); err != nil {
if _, err = fmt.Fprint(w, v.String()); err != nil {
return err
}
if !concise {
fmt.Fprintln(w)
}
default:
if _, err = fmt.Fprintln(os.Stdout, v); err != nil {
if _, err = fmt.Fprint(w, v); err != nil {
return err
}
if !concise {
fmt.Fprintln(w)
}
}
return nil
}

View file

@ -1,6 +1,9 @@
package repl
import (
"io"
"reflect"
"slices"
"ucl.lmika.dev/ucl"
)
@ -11,16 +14,19 @@ type CommandOpt interface {
type REPL struct {
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 {
inst := ucl.New(opts...)
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{
Brief: "displays help about a 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
}

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"
"errors"
"fmt"
"strconv"
"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 _, err := fmt.Fprintln(args.inst.Out()); err != nil {
return nil, err
@ -28,7 +42,7 @@ func echoBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return intObject(0), nil
}
@ -52,7 +66,7 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return intObject(0), nil
}
@ -82,7 +96,7 @@ func subBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return intObject(1), nil
}
@ -106,7 +120,7 @@ func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return intObject(1), nil
}
@ -136,7 +150,7 @@ func divBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return intObject(0), nil
}
@ -166,7 +180,7 @@ func modBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -182,7 +196,7 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -193,7 +207,7 @@ func toUpperBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -204,7 +218,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -215,7 +229,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -227,7 +241,7 @@ func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
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
}
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 {
return nil, err
}
@ -251,7 +265,7 @@ func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
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
}
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 {
return nil, err
}
@ -276,7 +290,7 @@ func andBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -289,7 +303,7 @@ func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -299,7 +313,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
var errObjectsNotEqual = errors.New("objects not equal")
func objectsEqual(l, r object) bool {
func objectsEqual(l, r Object) bool {
if 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 {
return lv == rv
}
case listable:
rv, ok := r.(listable)
case Listable:
rv, ok := r.(Listable)
if !ok {
return false
}
@ -341,7 +355,7 @@ func objectsEqual(l, r object) bool {
if lv.Len() != rv.Len() {
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)
if rkv == nil {
return errObjectsNotEqual
@ -357,7 +371,7 @@ func objectsEqual(l, r object) bool {
return false
}
func objectsLessThan(l, r object) (bool, error) {
func objectsLessThan(l, r Object) (bool, error) {
switch lv := l.(type) {
case strObject:
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")
}
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 {
return nil, err
}
@ -383,7 +397,7 @@ func strBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -411,7 +425,7 @@ func intBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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
for _, a := range args.args {
@ -424,7 +438,7 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -437,7 +451,7 @@ func callBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -445,7 +459,7 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
switch v := args.args[0].(type) {
case strObject:
return intObject(len(string(v))), nil
case listable:
case Listable:
return intObject(v.Len()), nil
case hashable:
return intObject(v.Len()), nil
@ -454,9 +468,9 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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) {
case listable:
case Listable:
intIdx, ok := elem.(intObject)
if !ok {
return nil, nil
@ -475,7 +489,7 @@ func indexLookup(ctx context.Context, obj, elem object) (object, error) {
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 {
return nil, err
}
@ -492,7 +506,28 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
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 {
return nil, err
}
@ -503,12 +538,12 @@ func mapBuiltin(ctx context.Context, args invocationArgs) (object, error) {
}
switch t := args.args[0].(type) {
case listable:
case Listable:
l := t.Len()
newList := listObject{}
for i := 0; i < l; 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 {
return nil, err
}
@ -525,7 +560,7 @@ func firstBuiltin(ctx context.Context, args invocationArgs) (object, error) {
}
switch t := args.args[0].(type) {
case listable:
case Listable:
if t.Len() == 0 {
return nil, nil
}
@ -561,7 +596,7 @@ func (s seqObject) Len() int {
return l
}
func (s seqObject) Index(i int) object {
func (s seqObject) Index(i int) Object {
l := s.Len()
if i < 0 || i > l {
return nil
@ -572,7 +607,7 @@ func (s seqObject) Index(i int) object {
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
if inc, ok := args.kwargs["inc"]; ok {
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 {
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) {
var (
items object
items Object
blockIdx int
err error
)
@ -664,16 +699,16 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
}
var (
last object
last Object
breakErr errBreak
)
switch t := items.(type) {
case listable:
case Listable:
l := t.Len()
for i := 0; i < l; 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 errors.As(err, &breakErr) {
if !breakErr.isCont {
@ -685,8 +720,8 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
}
}
case hashable:
err := t.Each(func(k string, v object) error {
last, err = args.evalBlock(ctx, blockIdx, []object{strObject(k), v}, true)
err := t.Each(func(k string, v Object) error {
last, err = args.evalBlock(ctx, blockIdx, []Object{strObject(k), v}, true)
return err
})
if errors.As(err, &breakErr) {
@ -701,25 +736,25 @@ func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
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 {
return nil, errBreak{}
}
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}
}
func returnBuiltin(ctx context.Context, args invocationArgs) (object, error) {
func returnBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
if len(args.args) < 1 {
return nil, errReturn{}
}
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 {
return nil, errors.New("need at least one arguments")
}
@ -763,7 +798,7 @@ func (b procObject) Truthy() bool {
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()
for i, name := range b.block.Names {

View file

@ -11,6 +11,7 @@ import (
type Inst struct {
out io.Writer
missingBuiltinHandler MissingBuiltinHandler
echoPrinter EchoPrinter
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 {
Name string
Builtins map[string]BuiltinHandler