Renamed project to ucl

This commit is contained in:
Leon Mika 2024-04-27 10:11:22 +10:00
parent c7a4013641
commit 60147d8fdf
16 changed files with 158 additions and 108 deletions

View file

@ -2,7 +2,7 @@ clean:
-rm -r build -rm -r build
test: test:
go test ./cmdlang/... go test ./ucl/...
site: clean site: clean
mkdir build mkdir build

View file

@ -3,7 +3,7 @@ package main
import ( import (
"context" "context"
"github.com/chzyer/readline" "github.com/chzyer/readline"
"github.com/lmika/cmdlang-proto/cmdlang" "github.com/lmika/ucl/ucl"
"log" "log"
) )
@ -14,7 +14,7 @@ func main() {
} }
defer rl.Close() defer rl.Close()
inst := cmdlang.New() inst := ucl.New()
ctx := context.Background() ctx := context.Background()
for { for {
@ -23,7 +23,7 @@ func main() {
break break
} }
if err := inst.EvalAndDisplay(ctx, line); err != nil { if err := ucl.EvalAndDisplay(ctx, inst, line); err != nil {
log.Printf("%T: %v", err, err) log.Printf("%T: %v", err, err)
} }
} }

View file

@ -7,9 +7,8 @@ import (
"context" "context"
"errors" "errors"
"github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2"
"github.com/lmika/cmdlang-proto/cmdlang" "github.com/lmika/ucl/ucl"
"strings" "strings"
"syscall/js"
) )
func invokeUCLCallback(name string, args ...any) { func invokeUCLCallback(name string, args ...any) {
@ -21,7 +20,7 @@ func invokeUCLCallback(name string, args ...any) {
func initJS(ctx context.Context) { func initJS(ctx context.Context) {
ucl := make(map[string]any) ucl := make(map[string]any)
inst := cmdlang.New(cmdlang.WithOut(&uclOut{ inst := ucl.New(ucl.WithOut(&uclOut{
lineBuffer: new(bytes.Buffer), lineBuffer: new(bytes.Buffer),
writeLine: func(line string) { writeLine: func(line string) {
invokeUCLCallback("onOutLine", line) invokeUCLCallback("onOutLine", line)

View file

@ -1,7 +0,0 @@
package cmdlang
import "context"
func egLookup(ctx context.Context, args invocationArgs) (object, error) {
return nil, nil
}

2
go.mod
View file

@ -1,4 +1,4 @@
module github.com/lmika/cmdlang-proto module github.com/lmika/ucl
go 1.21.1 go 1.21.1

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
import ( import (
"io" "io"
@ -64,12 +64,13 @@ type astStatements struct {
} }
type astScript struct { type astScript struct {
Statements *astStatements `parser:"NL* @@ NL*"` Statements *astStatements `parser:"NL* (@@ NL*)?"`
} }
var scanner = lexer.MustStateful(lexer.Rules{ var scanner = lexer.MustStateful(lexer.Rules{
"Root": { "Root": {
{"Whitespace", `[ \t]+`, nil}, {"Whitespace", `[ \t]+`, nil},
{"Comment", `[#].*`, nil},
{"String", `"(\\"|[^"])*"`, nil}, {"String", `"(\\"|[^"])*"`, nil},
{"Int", `[-]?[0-9][0-9]*`, nil}, {"Int", `[-]?[0-9][0-9]*`, nil},
{"DOLLAR", `\$`, nil}, {"DOLLAR", `\$`, nil},
@ -86,7 +87,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
}, },
}) })
var parser = participle.MustBuild[astScript](participle.Lexer(scanner), var parser = participle.MustBuild[astScript](participle.Lexer(scanner),
participle.Elide("Whitespace")) participle.Elide("Whitespace", "Comment"))
func parse(r io.Reader) (*astScript, error) { func parse(r io.Reader) (*astScript, error) {
return parser.Parse("test", r) return parser.Parse("test", r)

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
import ( import (
"context" "context"
@ -40,8 +40,7 @@ func setBuiltin(ctx context.Context, args invocationArgs) (object, error) {
newVal := args.args[1] newVal := args.args[1]
// TODO: if the value is a stream, consume the stream and save it as a list args.ec.setOrDefineVar(name, newVal)
args.ec.setVar(name, newVal)
return newVal, nil return newVal, nil
} }
@ -383,9 +382,9 @@ func (b procObject) invoke(ctx context.Context, args invocationArgs) (object, er
for i, name := range b.block.Names { for i, name := range b.block.Names {
if i < len(args.args) { if i < len(args.args) {
newEc.setVar(name, args.args[i]) newEc.setOrDefineVar(name, args.args[i])
} else { } else {
newEc.setVar(name, nil) newEc.setOrDefineVar(name, nil)
} }
} }

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
type evalCtx struct { type evalCtx struct {
root *evalCtx root *evalCtx
@ -34,7 +34,24 @@ func (ec *evalCtx) addMacro(name string, inv macroable) {
ec.root.macros[name] = inv ec.root.macros[name] = inv
} }
func (ec *evalCtx) setVar(name string, val object) { func (ec *evalCtx) setVar(name string, val object) bool {
if ec == nil || ec.vars == nil {
return false
}
if _, ok := ec.vars[name]; ok {
ec.vars[name] = val
return true
}
return ec.parent.setVar(name, val)
}
func (ec *evalCtx) setOrDefineVar(name string, val object) {
if ec.setVar(name, val) {
return
}
if ec.vars == nil { if ec.vars == nil {
ec.vars = make(map[string]object) ec.vars = make(map[string]object)
} }

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
import ( import (
"context" "context"
@ -27,6 +27,10 @@ func (e evaluator) evalScript(ctx context.Context, ec *evalCtx, n *astScript) (l
} }
func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) { func (e evaluator) evalStatement(ctx context.Context, ec *evalCtx, n *astStatements) (object, error) {
if n == nil {
return nil, nil
}
res, err := e.evalPipeline(ctx, ec, n.First) res, err := e.evalPipeline(ctx, ec, n.First)
if err != nil { if err != nil {
return nil, err return nil, err

29
ucl/evaldisplay.go Normal file
View file

@ -0,0 +1,29 @@
package ucl
import (
"context"
"fmt"
)
func EvalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
res, err := inst.eval(ctx, expr)
if err != nil {
return err
}
return displayResult(ctx, inst, res)
}
func displayResult(ctx context.Context, inst *Inst, res object) (err error) {
switch v := res.(type) {
case nil:
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
return err
}
default:
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
return err
}
}
return nil
}

View file

@ -1,9 +1,8 @@
package cmdlang package ucl
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"strings" "strings"
@ -47,7 +46,7 @@ func New(opts ...InstOption) *Inst {
//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin)) //rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
rootEC.setVar("hello", strObject("world")) rootEC.setOrDefineVar("hello", strObject("world"))
inst := &Inst{ inst := &Inst{
out: os.Stdout, out: os.Stdout,
@ -93,26 +92,3 @@ func (inst *Inst) eval(ctx context.Context, expr string) (object, error) {
// TODO: this should be a separate forkAndIsolate() session // TODO: this should be a separate forkAndIsolate() session
return eval.evalScript(ctx, inst.rootEC, ast) return eval.evalScript(ctx, inst.rootEC, ast)
} }
func (inst *Inst) EvalAndDisplay(ctx context.Context, expr string) error {
res, err := inst.eval(ctx, expr)
if err != nil {
return err
}
return inst.display(ctx, res)
}
func (inst *Inst) display(ctx context.Context, res object) (err error) {
switch v := res.(type) {
case nil:
if _, err = fmt.Fprintln(inst.out, "(nil)"); err != nil {
return err
}
default:
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
return err
}
}
return nil
}

View file

@ -1,11 +1,11 @@
package cmdlang_test package ucl_test
import ( import (
"bytes" "bytes"
"context" "context"
"github.com/lmika/ucl/ucl"
"testing" "testing"
"github.com/lmika/cmdlang-proto/cmdlang"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -67,7 +67,7 @@ func TestInst_Eval(t *testing.T) {
ctx := context.Background() ctx := context.Background()
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := cmdlang.New(cmdlang.WithOut(outW), cmdlang.WithTestBuiltin()) inst := ucl.New(ucl.WithOut(outW), ucl.WithTestBuiltin())
res, err := inst.Eval(ctx, tt.expr) res, err := inst.Eval(ctx, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
import ( import (
"context" "context"
@ -235,7 +235,7 @@ func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushSco
} }
for i, n := range block.block.Names { for i, n := range block.block.Names {
if i < len(args) { if i < len(args) {
ec.setVar(n, args[i]) ec.setOrDefineVar(n, args[i])
} }
} }
@ -341,7 +341,7 @@ func (bo blockObject) invoke(ctx context.Context, args invocationArgs) (object,
ec := args.ec.fork() ec := args.ec.fork()
for i, n := range bo.block.Names { for i, n := range bo.block.Names {
if i < len(args.args) { if i < len(args.args) {
ec.setVar(n, args.args[i]) ec.setOrDefineVar(n, args.args[i])
} }
} }
@ -370,8 +370,7 @@ func (p proxyObject) String() string {
} }
func (p proxyObject) Truthy() bool { func (p proxyObject) Truthy() bool {
//TODO implement me return p.p != nil
panic("implement me")
} }
type listableProxyObject struct { type listableProxyObject struct {
@ -383,7 +382,7 @@ func (p listableProxyObject) String() string {
} }
func (p listableProxyObject) Truthy() bool { func (p listableProxyObject) Truthy() bool {
panic("implement me") return p.v.Len() > 0
} }
func (p listableProxyObject) Len() int { func (p listableProxyObject) Len() int {

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
import ( import (
"bytes" "bytes"
@ -53,8 +53,8 @@ func WithTestBuiltin() InstOption {
return strObject(sb.String()), nil return strObject(sb.String()), nil
})) }))
i.rootEC.setVar("a", strObject("alpha")) i.rootEC.setOrDefineVar("a", strObject("alpha"))
i.rootEC.setVar("bee", strObject("buzz")) i.rootEC.setOrDefineVar("bee", strObject("buzz"))
} }
} }
@ -84,6 +84,18 @@ func TestBuiltins_Echo(t *testing.T) {
; ;
echo "world" echo "world"
;
`, want: "Hello\nworld\n"},
{desc: "multi-line 4", expr: `
# This is a comment
#
;;;
# This is another comment
echo "Hello"
;
echo "world" # command after this
; ;
`, want: "Hello\nworld\n"}, `, want: "Hello\nworld\n"},
} }
@ -167,7 +179,7 @@ func TestBuiltins_If(t *testing.T) {
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := New(WithOut(outW), WithTestBuiltin()) inst := New(WithOut(outW), WithTestBuiltin())
err := inst.EvalAndDisplay(ctx, tt.expr) err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String()) assert.Equal(t, tt.want, outW.String())
@ -197,7 +209,7 @@ func TestBuiltins_ForEach(t *testing.T) {
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := New(WithOut(outW), WithTestBuiltin()) inst := New(WithOut(outW), WithTestBuiltin())
err := inst.EvalAndDisplay(ctx, tt.expr) err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String()) assert.Equal(t, tt.want, outW.String())
@ -257,18 +269,18 @@ func TestBuiltins_Procs(t *testing.T) {
call (makeGreeter "Quick") "call me" call (makeGreeter "Quick") "call me"
`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"}, `, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"},
//{desc: "modifying closed over variables", expr: ` {desc: "modifying closed over variables", expr: `
// proc makeSetter { proc makeSetter {
// set bla "X" set bla "X"
// proc appendToBla { |x| proc appendToBla { |x|
// set bla (cat $bla $x) set bla (cat $bla $x)
// } }
// } }
//
// set er (makeSetter) set er (makeSetter)
// call $er "xxx" echo (call $er "xxx")
// call $er "yyy" echo (call $er "yyy")
// `, want: "Xxxx\nXxxxyyy(nil)\n"}, `, want: "Xxxx\nXxxxyyy\n(nil)\n"},
} }
for _, tt := range tests { for _, tt := range tests {
@ -277,7 +289,7 @@ func TestBuiltins_Procs(t *testing.T) {
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := New(WithOut(outW), WithTestBuiltin()) inst := New(WithOut(outW), WithTestBuiltin())
err := inst.EvalAndDisplay(ctx, tt.expr) err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String()) assert.Equal(t, tt.want, outW.String())
@ -309,12 +321,12 @@ func TestBuiltins_Map(t *testing.T) {
{desc: "map list with block", expr: ` {desc: "map list with block", expr: `
map ["a" "b" "c"] { |x| toUpper $x } map ["a" "b" "c"] { |x| toUpper $x }
`, want: "[A B C]\n"}, `, want: "[A B C]\n"},
//{desc: "map list with stream", expr: ` {desc: "map list with stream", expr: `
// set makeUpper (proc { |x| $x | toUpper }) set makeUpper (proc { |x| toUpper $x })
//
// set l (["a" "b" "c"] | map $makeUpper) set l (["a" "b" "c"] | map $makeUpper)
// echo $l echo $l
// `, want: "[A B C]\n"}, `, want: "[A B C]\n(nil)\n"},
} }
for _, tt := range tests { for _, tt := range tests {
@ -323,7 +335,7 @@ func TestBuiltins_Map(t *testing.T) {
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := New(WithOut(outW), WithTestBuiltin()) inst := New(WithOut(outW), WithTestBuiltin())
err := inst.EvalAndDisplay(ctx, tt.expr) err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String()) assert.Equal(t, tt.want, outW.String())
@ -379,7 +391,7 @@ func TestBuiltins_Index(t *testing.T) {
Gamma: []int{22, 33}, Gamma: []int{22, 33},
}, nil }, nil
}) })
err := inst.EvalAndDisplay(ctx, tt.expr) err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String()) assert.Equal(t, tt.want, outW.String())
@ -438,7 +450,7 @@ func TestBuiltins_Len(t *testing.T) {
missing: "missing", missing: "missing",
}, nil }, nil
}) })
err := inst.EvalAndDisplay(ctx, tt.expr) err := EvalAndDisplay(ctx, inst, tt.expr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.want, outW.String()) assert.Equal(t, tt.want, outW.String())

View file

@ -1,4 +1,4 @@
package cmdlang package ucl
import ( import (
"context" "context"
@ -10,8 +10,8 @@ type CallArgs struct {
args invocationArgs args invocationArgs
} }
func (ca CallArgs) Bind(vars ...interface{}) error { func (ca *CallArgs) Bind(vars ...interface{}) error {
if len(ca.args.args) != len(vars) { if len(ca.args.args) < len(vars) {
return errors.New("wrong number of arguments") return errors.New("wrong number of arguments")
} }
@ -20,6 +20,7 @@ func (ca CallArgs) Bind(vars ...interface{}) error {
return err return err
} }
} }
ca.args = ca.args.shift(len(vars))
return nil return nil
} }

View file

@ -1,19 +1,19 @@
package cmdlang_test package ucl_test
import ( import (
"bytes" "bytes"
"context" "context"
"github.com/lmika/ucl/ucl"
"strings" "strings"
"testing" "testing"
"github.com/lmika/cmdlang-proto/cmdlang"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestInst_SetBuiltin(t *testing.T) { func TestInst_SetBuiltin(t *testing.T) {
t.Run("simple builtin accepting and returning strings", func(t *testing.T) { t.Run("simple builtin accepting and returning strings", func(t *testing.T) {
inst := cmdlang.New() inst := ucl.New()
inst.SetBuiltin("add2", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var x, y string var x, y string
if err := args.Bind(&x, &y); err != nil { if err := args.Bind(&x, &y); err != nil {
@ -28,9 +28,29 @@ func TestInst_SetBuiltin(t *testing.T) {
assert.Equal(t, "Hello, World", res) assert.Equal(t, "Hello, World", res)
}) })
t.Run("bind shift arguments", func(t *testing.T) {
inst := ucl.New()
inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var x, y string
if err := args.Bind(&x); err != nil {
return nil, err
}
if err := args.Bind(&y); err != nil {
return nil, err
}
return x + y, nil
})
res, err := inst.Eval(context.Background(), `add2 "Hello, " "World"`)
assert.NoError(t, err)
assert.Equal(t, "Hello, World", res)
})
t.Run("simple builtin with optional switches and strings", func(t *testing.T) { t.Run("simple builtin with optional switches and strings", func(t *testing.T) {
inst := cmdlang.New() inst := ucl.New()
inst.SetBuiltin("add2", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var x, y, sep string var x, y, sep string
if err := args.BindSwitch("sep", &sep); err != nil { if err := args.BindSwitch("sep", &sep); err != nil {
@ -75,8 +95,8 @@ func TestInst_SetBuiltin(t *testing.T) {
x, y string x, y string
} }
inst := cmdlang.New() inst := ucl.New()
inst.SetBuiltin("add2", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var x, y string var x, y string
if err := args.Bind(&x, &y); err != nil { if err := args.Bind(&x, &y); err != nil {
@ -107,8 +127,8 @@ func TestInst_SetBuiltin(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) { t.Run(tt.descr, func(t *testing.T) {
inst := cmdlang.New() inst := ucl.New()
inst.SetBuiltin("add2", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("add2", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var x, y string var x, y string
if err := args.Bind(&x, &y); err != nil { if err := args.Bind(&x, &y); err != nil {
@ -117,7 +137,7 @@ func TestInst_SetBuiltin(t *testing.T) {
return pair{x, y}, nil return pair{x, y}, nil
}) })
inst.SetBuiltin("join", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("join", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var x pair var x pair
if err := args.Bind(&x); err != nil { if err := args.Bind(&x); err != nil {
@ -148,9 +168,9 @@ func TestInst_SetBuiltin(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.descr, func(t *testing.T) { t.Run(tt.descr, func(t *testing.T) {
outW := bytes.NewBuffer(nil) outW := bytes.NewBuffer(nil)
inst := cmdlang.New(cmdlang.WithOut(outW)) inst := ucl.New(ucl.WithOut(outW))
inst.SetBuiltin("countTo3", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("countTo3", func(ctx context.Context, args ucl.CallArgs) (any, error) {
return []string{"1", "2", "3"}, nil return []string{"1", "2", "3"}, nil
}) })
@ -167,14 +187,14 @@ func TestCallArgs_Bind(t *testing.T) {
t.Run("bind to an interface", func(t *testing.T) { t.Run("bind to an interface", func(t *testing.T) {
ctx := context.Background() ctx := context.Background()
inst := cmdlang.New() inst := ucl.New()
inst.SetBuiltin("sa", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("sa", func(ctx context.Context, args ucl.CallArgs) (any, error) {
return doStringA{this: "a val"}, nil return doStringA{this: "a val"}, nil
}) })
inst.SetBuiltin("sb", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("sb", func(ctx context.Context, args ucl.CallArgs) (any, error) {
return doStringB{left: "foo", right: "bar"}, nil return doStringB{left: "foo", right: "bar"}, nil
}) })
inst.SetBuiltin("dostr", func(ctx context.Context, args cmdlang.CallArgs) (any, error) { inst.SetBuiltin("dostr", func(ctx context.Context, args ucl.CallArgs) (any, error) {
var ds doStringable var ds doStringable
if err := args.Bind(&ds); err != nil { if err := args.Bind(&ds); err != nil {