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