Added a log builtin

This commit is contained in:
Leon Mika 2025-01-15 22:22:17 +11:00
parent 23f0f16d26
commit 485e839ae3
5 changed files with 167 additions and 22 deletions

View file

@ -20,6 +20,8 @@ func main() {
instRepl := repl.New( instRepl := repl.New(
ucl.WithModule(builtins.OS()), ucl.WithModule(builtins.OS()),
ucl.WithModule(builtins.FS(nil)), ucl.WithModule(builtins.FS(nil)),
ucl.WithModule(builtins.Log(nil)),
ucl.WithModule(builtins.Strs()),
) )
ctx := context.Background() ctx := context.Background()

View file

@ -8,6 +8,7 @@ import (
"github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2"
"strings" "strings"
"syscall/js" "syscall/js"
"ucl.lmika.dev/repl"
"ucl.lmika.dev/ucl" "ucl.lmika.dev/ucl"
) )
@ -20,9 +21,12 @@ func invokeUCLCallback(name string, args ...any) {
func initJS(ctx context.Context) { func initJS(ctx context.Context) {
uclObj := make(map[string]any) uclObj := make(map[string]any)
inst := ucl.New(ucl.WithOut(ucl.LineHandler(func(line string) { replInst := repl.New(
invokeUCLCallback("onOutLine", line) ucl.WithModule(builtins.Log(nil)),
}))) ucl.WithModule(builtins.Strs()),
ucl.WithOut(ucl.LineHandler(func(line string) {
invokeUCLCallback("onOutLine", line)
})))
uclObj["eval"] = js.FuncOf(func(this js.Value, args []js.Value) any { uclObj["eval"] = js.FuncOf(func(this js.Value, args []js.Value) any {
if len(args) != 2 { if len(args) != 2 {
@ -36,7 +40,7 @@ func initJS(ctx context.Context) {
} }
wantContinue := args[1].Bool() wantContinue := args[1].Bool()
if err := ucl.EvalAndDisplay(ctx, inst, cmdLine); err != nil { if err := replInst.EvalAndDisplay(ctx, cmdLine); err != nil {
var p participle.Error var p participle.Error
if errors.As(err, &p) && wantContinue { if errors.As(err, &p) && wantContinue {
invokeUCLCallback("onContinue") invokeUCLCallback("onContinue")
@ -50,21 +54,3 @@ func initJS(ctx context.Context) {
}) })
js.Global().Set("ucl", uclObj) js.Global().Set("ucl", uclObj)
} }
//
//type uclOut struct {
// lineBuffer *bytes.Buffer
// writeLine func(line string)
//}
//
//func (uo *uclOut) Write(p []byte) (n int, err error) {
// for _, b := range p {
// if b == '\n' {
// uo.writeLine(uo.lineBuffer.String())
// uo.lineBuffer.Reset()
// } else {
// uo.lineBuffer.WriteByte(b)
// }
// }
// return len(p), nil
//}

44
ucl/builtins/log.go Normal file
View file

@ -0,0 +1,44 @@
package builtins
import (
"context"
"log"
"strings"
"ucl.lmika.dev/ucl"
)
type LogMessageHandler func(s string) error
func Log(handler LogMessageHandler) ucl.Module {
if handler == nil {
handler = func(s string) error {
log.Println(s)
return nil
}
}
lh := logHandler{handler: handler}
return ucl.Module{
Name: "log",
Builtins: map[string]ucl.BuiltinHandler{
"puts": lh.logMessage,
},
}
}
type logHandler struct {
handler LogMessageHandler
}
func (lh logHandler) logMessage(ctx context.Context, args ucl.CallArgs) (any, error) {
var sb strings.Builder
var s ucl.Object
for args.NArgs() > 0 {
if err := args.Bind(&s); err == nil {
sb.WriteString(s.String())
}
}
return nil, lh.handler(sb.String())
}

47
ucl/builtins/log_test.go Normal file
View file

@ -0,0 +1,47 @@
package builtins_test
import (
"bytes"
"context"
"github.com/stretchr/testify/assert"
"testing"
"ucl.lmika.dev/ucl"
"ucl.lmika.dev/ucl/builtins"
)
func TestLog_Puts(t *testing.T) {
tests := []struct {
desc string
eval string
want any
wantErr bool
wantLog string
}{
{desc: "log 1", eval: `log:puts "bad stuff here"`, wantLog: "bad stuff here\n"},
{desc: "log 2", eval: `log:puts "bad " 123 " here " [1 2 3]`, wantLog: "bad 123 here [1 2 3]\n"},
{desc: "log 3", eval: `log:puts`, wantLog: "\n"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
var logMessage bytes.Buffer
inst := ucl.New(
ucl.WithModule(builtins.Log(func(s string) error {
logMessage.WriteString(s)
logMessage.WriteByte('\n')
return nil
})),
)
res, err := inst.Eval(context.Background(), tt.eval)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, res)
}
assert.Equal(t, tt.wantLog, logMessage.String())
})
}
}

View file

@ -94,20 +94,44 @@ func (u userBuiltin) invoke(ctx context.Context, args invocationArgs) (object, e
func bindArg(v interface{}, arg object) error { func bindArg(v interface{}, arg object) error {
switch t := v.(type) { switch t := v.(type) {
case *Object:
*t = arg
return nil
case *interface{}: case *interface{}:
*t, _ = toGoValue(arg) *t, _ = toGoValue(arg)
case *Invokable:
i, ok := arg.(invokable)
if !ok {
return errors.New("exepected invokable")
}
*t = Invokable{
inv: i,
eval: ca.args.eval,
inst: ca.args.inst,
ec: ca.args.ec,
}
return nil
case *Listable:
i, ok := arg.(Listable)
if !ok {
return errors.New("exepected listable")
}
*t = i
return nil
case *string: case *string:
if arg != nil { if arg != nil {
*t = arg.String() *t = arg.String()
} else { } else {
*t = "" *t = ""
} }
return nil
case *int: case *int:
if iArg, ok := arg.(intObject); ok { if iArg, ok := arg.(intObject); ok {
*t = int(iArg) *t = int(iArg)
} else { } else {
return errors.New("invalid arg") return errors.New("invalid arg")
} }
return nil
} }
switch t := arg.(type) { switch t := arg.(type) {
@ -198,3 +222,45 @@ func (m missingHandlerInvokable) invoke(ctx context.Context, args invocationArgs
return fromGoValue(v) return fromGoValue(v)
} }
type Invokable struct {
inv invokable
eval evaluator
inst *Inst
ec *evalCtx
}
func (i Invokable) IsNil() bool {
return i.inv == nil
}
func (i Invokable) Invoke(ctx context.Context, args ...any) (any, error) {
if i.inv == nil {
return nil, nil
}
var err error
invArgs := invocationArgs{
eval: i.eval,
ec: i.ec,
inst: i.inst,
}
invArgs.args, err = slices.MapWithError(args, func(a any) (Object, error) {
return fromGoValue(a)
})
if err != nil {
return nil, err
}
res, err := i.inv.invoke(ctx, invArgs)
if err != nil {
return nil, err
}
goRes, ok := toGoValue(res)
if !ok {
return nil, errors.New("cannot convert result to Go Value")
}
return goRes, err
}