This commit is contained in:
parent
0a7021845e
commit
0ddffcc489
3
go.mod
3
go.mod
|
@ -1,6 +1,6 @@
|
|||
module ucl.lmika.dev
|
||||
|
||||
go 1.21.1
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/alecthomas/participle/v2 v2.1.1
|
||||
|
@ -13,7 +13,6 @@ require (
|
|||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -75,7 +75,7 @@ type astListOrHash struct {
|
|||
|
||||
type astBlock struct {
|
||||
Names []string `parser:"LC NL? (PIPE @Ident+ PIPE NL?)?"`
|
||||
Statements []*astStatements `parser:"@@ NL? RC"`
|
||||
Statements []*astStatements `parser:"@@* NL? RC"`
|
||||
}
|
||||
|
||||
type astMaybeSub struct {
|
||||
|
@ -139,7 +139,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
|||
{"RC", `\}`, nil},
|
||||
{"NL", `[;\n][; \n\t]*`, nil},
|
||||
{"PIPE", `\|`, nil},
|
||||
{"Ident", `[-]*[a-zA-Z_][\w-]*`, nil},
|
||||
{"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil},
|
||||
},
|
||||
"String": {
|
||||
{"Escaped", `\\.`, nil},
|
||||
|
|
|
@ -196,6 +196,25 @@ func setBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
return newVal, nil
|
||||
}
|
||||
|
||||
func mustSetBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if err := args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name, err := args.stringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newVal := args.args[1]
|
||||
if newVal == nil {
|
||||
return nil, fmt.Errorf("attempt to set '%v' to a nil value", args.args[0])
|
||||
}
|
||||
|
||||
args.ec.setOrDefineVar(name, newVal)
|
||||
return newVal, nil
|
||||
}
|
||||
|
||||
func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if err := args.expectArgn(2); err != nil {
|
||||
return nil, err
|
||||
|
@ -878,7 +897,7 @@ func ifBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
|||
return nil, errors.New("malformed if-elif-else")
|
||||
}
|
||||
|
||||
func foreachBuiltin(ctx context.Context, args macroArgs) (object, error) {
|
||||
func foreachBuiltin(ctx context.Context, args macroArgs) (Object, error) {
|
||||
var (
|
||||
items Object
|
||||
blockIdx int
|
||||
|
|
|
@ -68,6 +68,22 @@ func (h csvHandlers) eachRecord(ctx context.Context, args ucl.CallArgs) (any, er
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
type stringSlice []string
|
||||
|
||||
func (ss stringSlice) String() string {
|
||||
return strings.Join(ss, ",")
|
||||
}
|
||||
func (ss stringSlice) Truthy() bool {
|
||||
return len(ss) > 0
|
||||
}
|
||||
|
||||
func (ss stringSlice) Len() int {
|
||||
return len(ss)
|
||||
}
|
||||
func (ss stringSlice) Index(i int) ucl.Object {
|
||||
return ucl.StringObject(ss[i])
|
||||
}
|
||||
|
||||
type headerIndexObject map[string]int
|
||||
|
||||
func (hio headerIndexObject) String() string {
|
||||
|
|
12
ucl/env.go
12
ucl/env.go
|
@ -5,7 +5,7 @@ type evalCtx struct {
|
|||
parent *evalCtx
|
||||
commands map[string]invokable
|
||||
macros map[string]macroable
|
||||
vars map[string]object
|
||||
vars map[string]Object
|
||||
}
|
||||
|
||||
func (ec *evalCtx) forkAndIsolate() *evalCtx {
|
||||
|
@ -34,7 +34,7 @@ func (ec *evalCtx) addMacro(name string, inv macroable) {
|
|||
ec.root.macros[name] = inv
|
||||
}
|
||||
|
||||
func (ec *evalCtx) setVar(name string, val object) bool {
|
||||
func (ec *evalCtx) setVar(name string, val Object) bool {
|
||||
if ec == nil || ec.vars == nil {
|
||||
return false
|
||||
}
|
||||
|
@ -47,20 +47,20 @@ func (ec *evalCtx) setVar(name string, val object) bool {
|
|||
return ec.parent.setVar(name, val)
|
||||
}
|
||||
|
||||
func (ec *evalCtx) setOrDefineVar(name string, val object) {
|
||||
func (ec *evalCtx) setOrDefineVar(name string, val Object) {
|
||||
if ec.setVar(name, val) {
|
||||
return
|
||||
}
|
||||
|
||||
if ec.vars == nil {
|
||||
ec.vars = make(map[string]object)
|
||||
ec.vars = make(map[string]Object)
|
||||
}
|
||||
ec.vars[name] = val
|
||||
}
|
||||
|
||||
func (ec *evalCtx) getVar(name string) (object, bool) {
|
||||
func (ec *evalCtx) getVar(name string) (Object, bool) {
|
||||
if ec.vars == nil {
|
||||
return nil, false
|
||||
return ec.parent.getVar(name)
|
||||
}
|
||||
|
||||
if v, ok := ec.vars[name]; ok {
|
||||
|
|
16
ucl/eval.go
16
ucl/eval.go
|
@ -108,9 +108,9 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe Object,
|
|||
|
||||
func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe Object, ast *astCmd, cmd invokable) (Object, error) {
|
||||
var (
|
||||
pargs listObject
|
||||
kwargs map[string]*listObject
|
||||
argsPtr *listObject
|
||||
pargs ListObject
|
||||
kwargs map[string]*ListObject
|
||||
argsPtr *ListObject
|
||||
)
|
||||
|
||||
argsPtr = &pargs
|
||||
|
@ -121,10 +121,10 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe O
|
|||
if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' {
|
||||
// Arg switch
|
||||
if kwargs == nil {
|
||||
kwargs = make(map[string]*listObject)
|
||||
kwargs = make(map[string]*ListObject)
|
||||
}
|
||||
|
||||
argsPtr = &listObject{}
|
||||
argsPtr = &ListObject{}
|
||||
kwargs[ident.String()[1:]] = argsPtr
|
||||
} else {
|
||||
ae, err := e.evalDot(ctx, ec, arg)
|
||||
|
@ -203,7 +203,7 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec
|
|||
|
||||
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (Object, error) {
|
||||
if loh.EmptyList {
|
||||
return listObject{}, nil
|
||||
return &ListObject{}, nil
|
||||
} else if loh.EmptyHash {
|
||||
return hashObject{}, nil
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
|||
return h, nil
|
||||
}
|
||||
|
||||
l := listObject{}
|
||||
l := ListObject{}
|
||||
for _, el := range loh.Elements {
|
||||
if el.Right != nil {
|
||||
return nil, errors.New("miss-match of lists and hash")
|
||||
|
@ -241,7 +241,7 @@ func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astList
|
|||
}
|
||||
l = append(l, v)
|
||||
}
|
||||
return l, nil
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (Object, error) {
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
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
|
||||
}
|
||||
case listable:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err = displayResult(ctx, inst, v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
if _, err = fmt.Fprintln(inst.out, v.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
10
ucl/inst.go
10
ucl/inst.go
|
@ -57,14 +57,19 @@ func New(opts ...InstOption) *Inst {
|
|||
|
||||
rootEC.addCmd("echo", invokableFunc(echoBuiltin))
|
||||
rootEC.addCmd("set", invokableFunc(setBuiltin))
|
||||
rootEC.addCmd("set!", invokableFunc(mustSetBuiltin))
|
||||
rootEC.addCmd("len", invokableFunc(lenBuiltin))
|
||||
rootEC.addCmd("index", invokableFunc(indexBuiltin))
|
||||
rootEC.addCmd("call", invokableFunc(callBuiltin))
|
||||
rootEC.addCmd("seq", invokableFunc(seqBuiltin))
|
||||
|
||||
rootEC.addCmd("map", invokableFunc(mapBuiltin))
|
||||
rootEC.addCmd("filter", invokableFunc(filterBuiltin))
|
||||
rootEC.addCmd("reduce", invokableFunc(reduceBuiltin))
|
||||
rootEC.addCmd("head", invokableFunc(firstBuiltin))
|
||||
|
||||
rootEC.addCmd("keys", invokableFunc(keysBuiltin))
|
||||
|
||||
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||
rootEC.addCmd("ne", invokableFunc(neBuiltin))
|
||||
rootEC.addCmd("gt", invokableFunc(gtBuiltin))
|
||||
|
@ -75,11 +80,16 @@ func New(opts ...InstOption) *Inst {
|
|||
rootEC.addCmd("str", invokableFunc(strBuiltin))
|
||||
rootEC.addCmd("int", invokableFunc(intBuiltin))
|
||||
|
||||
rootEC.addCmd("add", invokableFunc(addBuiltin))
|
||||
rootEC.addCmd("sub", invokableFunc(subBuiltin))
|
||||
rootEC.addCmd("mup", invokableFunc(mupBuiltin))
|
||||
rootEC.addCmd("div", invokableFunc(divBuiltin))
|
||||
rootEC.addCmd("mod", invokableFunc(modBuiltin))
|
||||
|
||||
rootEC.addCmd("and", invokableFunc(andBuiltin))
|
||||
rootEC.addCmd("or", invokableFunc(orBuiltin))
|
||||
rootEC.addCmd("not", invokableFunc(notBuiltin))
|
||||
|
||||
rootEC.addCmd("cat", invokableFunc(concatBuiltin))
|
||||
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
||||
rootEC.addCmd("continue", invokableFunc(continueBuiltin))
|
||||
|
|
|
@ -3,7 +3,7 @@ package ucl_test
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/lmika/ucl/ucl"
|
||||
"ucl.lmika.dev/ucl"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
|
|
@ -3,6 +3,7 @@ package ucl
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"strings"
|
||||
|
@ -237,7 +238,6 @@ func TestBuiltins_If(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func TestBuiltins_ForEach(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
|
@ -1258,8 +1258,8 @@ func TestBuiltins_EqNe(t *testing.T) {
|
|||
{desc: "equal bools 1", expr: `eq $true $true`, want: true},
|
||||
{desc: "equal bools 2", expr: `eq $false $false`, want: true},
|
||||
{desc: "equal nil 1", expr: `eq () ()`, want: true},
|
||||
{desc: "equal opaque 1", expr: `eq $hello $hello`, want: true},
|
||||
{desc: "equal opaque 2", expr: `eq $world $world`, want: true},
|
||||
{desc: "equal undef 1", expr: `eq $undef $missing`, want: true},
|
||||
{desc: "equal undef 2", expr: `eq $missing $undef`, want: true},
|
||||
|
||||
{desc: "not equal strs 1", expr: `eq "hello" "world"`, want: false},
|
||||
{desc: "not equal strs 2", expr: `eq "bla" "BLA"`, want: false},
|
||||
|
@ -1270,8 +1270,6 @@ func TestBuiltins_EqNe(t *testing.T) {
|
|||
{desc: "not equal hashes 1", expr: `eq ["this":1 "that":"thing"] ["that":"thing"]`, want: false},
|
||||
{desc: "not equal hashes 2", expr: `eq ["this":1 "that":"thing"] ["this":1 "that":"thing" "other":"thing"]`, want: false},
|
||||
{desc: "not equal hashes 3", expr: `eq ["this":1 "that":"thing"] ["this":"1" "that":"other"]`, want: false},
|
||||
{desc: "not equal opaque 1", expr: `eq $hello $world`, want: false},
|
||||
{desc: "not equal opaque 2", expr: `eq $hello "hello"`, want: false},
|
||||
|
||||
{desc: "not equal types 1", expr: `eq "123" 123`, want: false},
|
||||
{desc: "not equal types 2", expr: `eq 0 ""`, want: false},
|
||||
|
@ -1280,7 +1278,7 @@ func TestBuiltins_EqNe(t *testing.T) {
|
|||
{desc: "not equal types 5", expr: `eq $true ()`, want: false},
|
||||
{desc: "not equal types 6", expr: `eq () $false`, want: false},
|
||||
{desc: "not equal types 7", expr: `eq () "yes"`, want: false},
|
||||
{desc: "not equal types 8", expr: `eq () $world`, want: false},
|
||||
{desc: "not equal types 8", expr: `eq () $undef`, want: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -1293,7 +1291,6 @@ func TestBuiltins_EqNe(t *testing.T) {
|
|||
outW := bytes.NewBuffer(nil)
|
||||
|
||||
inst := New(WithOut(outW), WithTestBuiltin())
|
||||
// Removed code I don't have the rights to
|
||||
inst.SetVar("true", true)
|
||||
inst.SetVar("false", false)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ func (ca *CallArgs) Bind(vars ...interface{}) error {
|
|||
}
|
||||
|
||||
for i, v := range vars {
|
||||
if err := bindArg(v, ca.args.args[i]); err != nil {
|
||||
if err := ca.bindArg(v, ca.args.args[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ucl_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
|
Loading…
Reference in a new issue