Compare commits
9 commits
db2c3fa5c4
...
699ff18dab
Author | SHA1 | Date | |
---|---|---|---|
lmika | 699ff18dab | ||
Leon Mika | e187ff0194 | ||
Leon Mika | 1b4b1a339c | ||
Leon Mika | 08c645a70f | ||
Leon Mika | 1169f82f7b | ||
Leon Mika | 484631782e | ||
Leon Mika | efaf31c869 | ||
Leon Mika | d501a3af25 | ||
Leon Mika | c689814ba2 |
20
.forgejo/workflows/test.yaml
Normal file
20
.forgejo/workflows/test.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- feature/*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.22.4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make test
|
346
ucl/builtins.go
346
ucl/builtins.go
|
@ -53,6 +53,120 @@ func addBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return intObject(n), nil
|
return intObject(n), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func subBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if len(args.args) == 0 {
|
||||||
|
return intObject(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
for i, a := range args.args {
|
||||||
|
var p int
|
||||||
|
switch t := a.(type) {
|
||||||
|
case intObject:
|
||||||
|
p = int(t)
|
||||||
|
case strObject:
|
||||||
|
v, err := strconv.Atoi(string(t))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("arg %v of 'sub' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
p = v
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("arg %v of 'sub' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
n = p
|
||||||
|
} else {
|
||||||
|
n -= p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intObject(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mupBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if len(args.args) == 0 {
|
||||||
|
return intObject(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 1
|
||||||
|
for i, a := range args.args {
|
||||||
|
switch t := a.(type) {
|
||||||
|
case intObject:
|
||||||
|
n *= int(t)
|
||||||
|
case strObject:
|
||||||
|
v, err := strconv.Atoi(string(t))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("arg %v of 'mup' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
n *= v
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("arg %v of 'mup' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intObject(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func divBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if len(args.args) == 0 {
|
||||||
|
return intObject(1), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 1
|
||||||
|
for i, a := range args.args {
|
||||||
|
var p int
|
||||||
|
switch t := a.(type) {
|
||||||
|
case intObject:
|
||||||
|
p = int(t)
|
||||||
|
case strObject:
|
||||||
|
v, err := strconv.Atoi(string(t))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("arg %v of 'div' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
p = v
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("arg %v of 'div' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
n = p
|
||||||
|
} else {
|
||||||
|
n /= p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return intObject(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func modBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if len(args.args) == 0 {
|
||||||
|
return intObject(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
for i, a := range args.args {
|
||||||
|
var p int
|
||||||
|
switch t := a.(type) {
|
||||||
|
case intObject:
|
||||||
|
p = int(t)
|
||||||
|
case strObject:
|
||||||
|
v, err := strconv.Atoi(string(t))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("arg %v of 'mod' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
p = v
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("arg %v of 'mod' not convertable to an int", i)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
n = p
|
||||||
|
} else {
|
||||||
|
n %= p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err := args.expectArgn(2); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -88,19 +202,223 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
l := args.args[0]
|
l := args.args[0]
|
||||||
r := args.args[1]
|
r := args.args[1]
|
||||||
|
|
||||||
switch lv := l.(type) {
|
return boolObject(objectsEqual(l, r)), nil
|
||||||
case strObject:
|
}
|
||||||
if rv, ok := r.(strObject); ok {
|
|
||||||
return boolObject(lv == rv), nil
|
func neBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
case intObject:
|
|
||||||
if rv, ok := r.(intObject); ok {
|
l := args.args[0]
|
||||||
return boolObject(lv == rv), nil
|
r := args.args[1]
|
||||||
|
|
||||||
|
return boolObject(!objectsEqual(l, r)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ltBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isLess, err := objectsLessThan(args.args[0], args.args[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return boolObject(isLess), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func leBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isLess, err := objectsLessThan(args.args[0], args.args[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return boolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gtBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isGreater, err := objectsLessThan(args.args[1], args.args[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return boolObject(isGreater), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func geBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isGreater, err := objectsLessThan(args.args[1], args.args[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return boolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func andBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range args.args {
|
||||||
|
if a == nil || !a.Truthy() {
|
||||||
|
return boolObject(false), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args.args[len(args.args)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func orBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range args.args {
|
||||||
|
if a != nil && a.Truthy() {
|
||||||
|
return a, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return boolObject(false), nil
|
return boolObject(false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolObject(!args.args[0].Truthy()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errObjectsNotEqual = errors.New("objects not equal")
|
||||||
|
|
||||||
|
func objectsEqual(l, r object) bool {
|
||||||
|
if l == nil || r == nil {
|
||||||
|
return l == nil && r == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch lv := l.(type) {
|
||||||
|
case strObject:
|
||||||
|
if rv, ok := r.(strObject); ok {
|
||||||
|
return lv == rv
|
||||||
|
}
|
||||||
|
case intObject:
|
||||||
|
if rv, ok := r.(intObject); ok {
|
||||||
|
return lv == rv
|
||||||
|
}
|
||||||
|
case boolObject:
|
||||||
|
if rv, ok := r.(boolObject); ok {
|
||||||
|
return lv == rv
|
||||||
|
}
|
||||||
|
case listable:
|
||||||
|
rv, ok := r.(listable)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lv.Len() != rv.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < lv.Len(); i++ {
|
||||||
|
if !objectsEqual(lv.Index(i), rv.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case hashable:
|
||||||
|
rv, ok := r.(hashable)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lv.Len() != rv.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err := lv.Each(func(k string, lkv object) error {
|
||||||
|
rkv := rv.Value(k)
|
||||||
|
if rkv == nil {
|
||||||
|
return errObjectsNotEqual
|
||||||
|
} else if !objectsEqual(lkv, rkv) {
|
||||||
|
return errObjectsNotEqual
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case OpaqueObject:
|
||||||
|
rv, ok := r.(OpaqueObject)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return lv.v == rv.v
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectsLessThan(l, r object) (bool, error) {
|
||||||
|
switch lv := l.(type) {
|
||||||
|
case strObject:
|
||||||
|
if rv, ok := r.(strObject); ok {
|
||||||
|
return lv < rv, nil
|
||||||
|
}
|
||||||
|
case intObject:
|
||||||
|
if rv, ok := r.(intObject); ok {
|
||||||
|
return lv < rv, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, errors.New("objects are not comparable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func strBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.args[0] == nil {
|
||||||
|
return strObject(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strObject(args.args[0].String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func intBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
|
if err := args.expectArgn(1); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.args[0] == nil {
|
||||||
|
return intObject(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := args.args[0].(type) {
|
||||||
|
case intObject:
|
||||||
|
return v, nil
|
||||||
|
case strObject:
|
||||||
|
i, err := strconv.Atoi(string(v))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("cannot convert to int")
|
||||||
|
}
|
||||||
|
return intObject(i), nil
|
||||||
|
case boolObject:
|
||||||
|
if v {
|
||||||
|
return intObject(1), nil
|
||||||
|
}
|
||||||
|
return intObject(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
var sb strings.Builder
|
||||||
|
|
||||||
|
@ -114,20 +432,6 @@ func concatBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
||||||
return strObject(sb.String()), nil
|
return strObject(sb.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//func catBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|
||||||
// if err := args.expectArgn(1); err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// filename, err := args.stringArg(0)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return &fileLinesStream{filename: filename}, 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 {
|
if err := args.expectArgn(1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
29
ucl/inst.go
29
ucl/inst.go
|
@ -61,7 +61,24 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addCmd("reduce", invokableFunc(reduceBuiltin))
|
rootEC.addCmd("reduce", invokableFunc(reduceBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
rootEC.addCmd("eq", invokableFunc(eqBuiltin))
|
||||||
|
rootEC.addCmd("ne", invokableFunc(neBuiltin))
|
||||||
|
rootEC.addCmd("gt", invokableFunc(gtBuiltin))
|
||||||
|
rootEC.addCmd("ge", invokableFunc(geBuiltin))
|
||||||
|
rootEC.addCmd("lt", invokableFunc(ltBuiltin))
|
||||||
|
rootEC.addCmd("le", invokableFunc(leBuiltin))
|
||||||
|
|
||||||
|
rootEC.addCmd("str", invokableFunc(strBuiltin))
|
||||||
|
rootEC.addCmd("int", invokableFunc(intBuiltin))
|
||||||
|
|
||||||
rootEC.addCmd("add", invokableFunc(addBuiltin))
|
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("cat", invokableFunc(concatBuiltin))
|
||||||
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
rootEC.addCmd("break", invokableFunc(breakBuiltin))
|
||||||
|
@ -72,10 +89,6 @@ func New(opts ...InstOption) *Inst {
|
||||||
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
rootEC.addMacro("foreach", macroFunc(foreachBuiltin))
|
||||||
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
rootEC.addMacro("proc", macroFunc(procBuiltin))
|
||||||
|
|
||||||
//rootEC.addCmd("testTimebomb", invokableStreamFunc(errorTestBuiltin))
|
|
||||||
|
|
||||||
rootEC.setOrDefineVar("hello", strObject("world"))
|
|
||||||
|
|
||||||
inst := &Inst{
|
inst := &Inst{
|
||||||
out: os.Stdout,
|
out: os.Stdout,
|
||||||
rootEC: rootEC,
|
rootEC: rootEC,
|
||||||
|
@ -88,6 +101,14 @@ func New(opts ...InstOption) *Inst {
|
||||||
return inst
|
return inst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inst *Inst) SetVar(name string, value any) {
|
||||||
|
obj, err := fromGoValue(value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inst.rootEC.setOrDefineVar(name, obj)
|
||||||
|
}
|
||||||
|
|
||||||
func (inst *Inst) Out() io.Writer {
|
func (inst *Inst) Out() io.Writer {
|
||||||
if inst.out == nil {
|
if inst.out == nil {
|
||||||
return os.Stdout
|
return os.Stdout
|
||||||
|
|
26
ucl/objs.go
26
ucl/objs.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/lmika/gopkgs/fp/slices"
|
"github.com/lmika/gopkgs/fp/slices"
|
||||||
)
|
)
|
||||||
|
@ -51,7 +52,22 @@ func (s listObject) Index(i int) object {
|
||||||
type hashObject map[string]object
|
type hashObject map[string]object
|
||||||
|
|
||||||
func (s hashObject) String() string {
|
func (s hashObject) String() string {
|
||||||
return fmt.Sprintf("%v", map[string]object(s))
|
if len(s) == 0 {
|
||||||
|
return "[:]"
|
||||||
|
}
|
||||||
|
|
||||||
|
sb := strings.Builder{}
|
||||||
|
sb.WriteString("[")
|
||||||
|
for k, v := range s {
|
||||||
|
if sb.Len() != 1 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
sb.WriteString(k)
|
||||||
|
sb.WriteString(":")
|
||||||
|
sb.WriteString(v.String())
|
||||||
|
}
|
||||||
|
sb.WriteString("]")
|
||||||
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s hashObject) Truthy() bool {
|
func (s hashObject) Truthy() bool {
|
||||||
|
@ -99,9 +115,9 @@ type boolObject bool
|
||||||
|
|
||||||
func (b boolObject) String() string {
|
func (b boolObject) String() string {
|
||||||
if b {
|
if b {
|
||||||
return "(true)"
|
return "true"
|
||||||
}
|
}
|
||||||
return "(false))"
|
return "false"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b boolObject) Truthy() bool {
|
func (b boolObject) Truthy() bool {
|
||||||
|
@ -118,6 +134,8 @@ func toGoValue(obj object) (interface{}, bool) {
|
||||||
return string(v), true
|
return string(v), true
|
||||||
case intObject:
|
case intObject:
|
||||||
return int(v), true
|
return int(v), true
|
||||||
|
case boolObject:
|
||||||
|
return bool(v), true
|
||||||
case listObject:
|
case listObject:
|
||||||
xs := make([]interface{}, 0, len(v))
|
xs := make([]interface{}, 0, len(v))
|
||||||
for _, va := range v {
|
for _, va := range v {
|
||||||
|
@ -159,6 +177,8 @@ func fromGoValue(v any) (object, error) {
|
||||||
return strObject(t), nil
|
return strObject(t), nil
|
||||||
case int:
|
case int:
|
||||||
return intObject(t), nil
|
return intObject(t), nil
|
||||||
|
case bool:
|
||||||
|
return boolObject(t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromGoReflectValue(reflect.ValueOf(v))
|
return fromGoReflectValue(reflect.ValueOf(v))
|
||||||
|
|
|
@ -229,16 +229,16 @@ func TestBuiltins_Break(t *testing.T) {
|
||||||
expr string
|
expr string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
//{desc: "break unconditionally returning nothing", expr: `
|
{desc: "break unconditionally returning nothing", expr: `
|
||||||
// foreach ["1" "2" "3"] { |v|
|
foreach ["1" "2" "3"] { |v|
|
||||||
// break
|
break
|
||||||
// echo $v
|
echo $v
|
||||||
// }`, want: "(nil)\n"},
|
}`, want: "(nil)\n"},
|
||||||
//{desc: "break conditionally returning nothing", expr: `
|
{desc: "break conditionally returning nothing", expr: `
|
||||||
// foreach ["1" "2" "3"] { |v|
|
foreach ["1" "2" "3"] { |v|
|
||||||
// echo $v
|
echo $v
|
||||||
// if (eq $v "2") { break }
|
if (eq $v "2") { break }
|
||||||
// }`, want: "1\n2\n(nil)\n"},
|
}`, want: "1\n2\n(nil)\n"},
|
||||||
{desc: "break inner loop only returning nothing", expr: `
|
{desc: "break inner loop only returning nothing", expr: `
|
||||||
foreach ["a" "b"] { |u|
|
foreach ["a" "b"] { |u|
|
||||||
foreach ["1" "2" "3"] { |v|
|
foreach ["1" "2" "3"] { |v|
|
||||||
|
@ -246,11 +246,11 @@ func TestBuiltins_Break(t *testing.T) {
|
||||||
if (eq $v "2") { break }
|
if (eq $v "2") { break }
|
||||||
}
|
}
|
||||||
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
}`, want: "a1\na2\nb1\nb2\n(nil)\n"},
|
||||||
//{desc: "break returning value", expr: `
|
{desc: "break returning value", expr: `
|
||||||
// echo (foreach ["1" "2" "3"] { |v|
|
echo (foreach ["1" "2" "3"] { |v|
|
||||||
// echo $v
|
echo $v
|
||||||
// if (eq $v "2") { break "hello" }
|
if (eq $v "2") { break "hello" }
|
||||||
// })`, want: "1\n2\nhello\n(nil)\n"},
|
})`, want: "1\n2\nhello\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -891,3 +891,342 @@ func TestBuiltins_Reduce(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_LtLeGtLe(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "str 1 - lt", expr: `lt "hello" "world"`, want: true},
|
||||||
|
{desc: "str 1 - le", expr: `le "hello" "world"`, want: true},
|
||||||
|
{desc: "str 1 - gt", expr: `gt "hello" "world"`, want: false},
|
||||||
|
{desc: "str 1 - ge", expr: `ge "hello" "world"`, want: false},
|
||||||
|
{desc: "str 2 - lt", expr: `lt "zzzzz" "world"`, want: false},
|
||||||
|
{desc: "str 2 - le", expr: `le "zzzzz" "world"`, want: false},
|
||||||
|
{desc: "str 2 - gt", expr: `gt "zzzzz" "world"`, want: true},
|
||||||
|
{desc: "str 2 - ge", expr: `ge "zzzzz" "world"`, want: true},
|
||||||
|
{desc: "str 3 - lt", expr: `lt "hello" "hello"`, want: false},
|
||||||
|
{desc: "str 3 - le", expr: `le "hello" "hello"`, want: true},
|
||||||
|
{desc: "str 3 - gt", expr: `gt "hello" "hello"`, want: false},
|
||||||
|
{desc: "str 3 - ge", expr: `ge "hello" "hello"`, want: true},
|
||||||
|
|
||||||
|
{desc: "int 1 - lt", expr: `lt 5 8`, want: true},
|
||||||
|
{desc: "int 1 - le", expr: `le 5 8`, want: true},
|
||||||
|
{desc: "int 1 - gt", expr: `gt 5 8`, want: false},
|
||||||
|
{desc: "int 1 - ge", expr: `ge 5 8`, want: false},
|
||||||
|
{desc: "int 2 - lt", expr: `lt 5 -8`, want: false},
|
||||||
|
{desc: "int 2 - le", expr: `le 5 -8`, want: false},
|
||||||
|
{desc: "int 2 - gt", expr: `gt 5 -8`, want: true},
|
||||||
|
{desc: "int 2 - ge", expr: `ge 5 -8`, want: true},
|
||||||
|
{desc: "int 3 - lt", expr: `lt 5 5`, want: false},
|
||||||
|
{desc: "int 3 - le", expr: `le 5 5`, want: true},
|
||||||
|
{desc: "int 3 - gt", expr: `gt 5 5`, want: false},
|
||||||
|
{desc: "int 3 - ge", expr: `ge 5 5`, want: true},
|
||||||
|
|
||||||
|
{desc: "not comparable 1", expr: `lt () ()`, wantErr: true},
|
||||||
|
{desc: "not comparable 2", expr: `lt $true $false`, wantErr: true},
|
||||||
|
{desc: "not comparable 3", expr: `lt [1 2 3] [2 3 4]`, wantErr: true},
|
||||||
|
{desc: "not comparable 4", expr: `lt ["1":2] ["2":3]`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
inst.SetVar("true", true)
|
||||||
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_EqNe(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{desc: "equal strs 1", expr: `eq "hello" "hello"`, want: true},
|
||||||
|
{desc: "equal strs 2", expr: `eq "bla" "bla"`, want: true},
|
||||||
|
{desc: "equal strs 3", expr: `eq "" ""`, want: true},
|
||||||
|
{desc: "equal ints 1", expr: `eq 123 123`, want: true},
|
||||||
|
{desc: "equal ints 2", expr: `eq -21 -21`, want: true},
|
||||||
|
{desc: "equal ints 3", expr: `eq 0 0`, want: true},
|
||||||
|
{desc: "equal lists 1", expr: `eq [1 2 3] [1 2 3]`, want: true},
|
||||||
|
{desc: "equal lists 2", expr: `eq ["foo" "bar"] ["foo" "bar"]`, want: true},
|
||||||
|
{desc: "equal lists 3", expr: `eq [] []`, want: true},
|
||||||
|
{desc: "equal hashes 1", expr: `eq ["this":1 "that":"thing"] ["that":"thing" "this":1]`, want: true},
|
||||||
|
{desc: "equal hashes 2", expr: `eq ["foo":"bar"] ["foo":"bar"]`, want: true},
|
||||||
|
{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: "not equal strs 1", expr: `eq "hello" "world"`, want: false},
|
||||||
|
{desc: "not equal strs 2", expr: `eq "bla" "BLA"`, want: false},
|
||||||
|
{desc: "not equal int 1", expr: `eq 131 313`, want: false},
|
||||||
|
{desc: "not equal int 2", expr: `eq -2 2`, want: false},
|
||||||
|
{desc: "not equal lists 1", expr: `eq [1 2 3] [1 2]`, want: false},
|
||||||
|
{desc: "not equal lists 2", expr: `eq ["123" "234"] [123 234]`, 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 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},
|
||||||
|
{desc: "not equal types 3", expr: `eq [] [:]`, want: false},
|
||||||
|
{desc: "not equal types 4", expr: `eq ["23"] "23"`, 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 7", expr: `eq () "yes"`, want: false},
|
||||||
|
{desc: "not equal types 8", expr: `eq () $world`, want: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
type testProxyObject struct {
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
inst.SetVar("hello", Opaque(testProxyObject{v: "hello"}))
|
||||||
|
inst.SetVar("world", Opaque(testProxyObject{v: "world"}))
|
||||||
|
inst.SetVar("true", true)
|
||||||
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
|
||||||
|
neRes, err := inst.Eval(ctx, strings.ReplaceAll(tt.expr, "eq", "ne"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, !tt.want, neRes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_Str(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{desc: "str", expr: `str "hello"`, want: "hello"},
|
||||||
|
{desc: "int", expr: `str 123`, want: "123"},
|
||||||
|
{desc: "bool 1", expr: `str (eq 1 1)`, want: "true"},
|
||||||
|
{desc: "bool 2", expr: `str (eq 1 0)`, want: "false"},
|
||||||
|
{desc: "list 1", expr: `str [1 2 3]`, want: "[1 2 3]"},
|
||||||
|
{desc: "list 2", expr: `str []`, want: "[]"},
|
||||||
|
{desc: "dict 1", expr: `str ["hello":"world"]`, want: `[hello:world]`},
|
||||||
|
{desc: "dict 2", expr: `str [:]`, want: "[:]"},
|
||||||
|
{desc: "nil", expr: `str ()`, want: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_Int(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "str 1", expr: `int "123"`, want: 123},
|
||||||
|
{desc: "str 2", expr: `int "31452"`, want: 31452},
|
||||||
|
{desc: "str 3", expr: `int "-21"`, want: -21},
|
||||||
|
{desc: "int 1", expr: `int 123`, want: 123},
|
||||||
|
{desc: "int 2", expr: `int -21`, want: -21},
|
||||||
|
{desc: "bool 1", expr: `int (eq 1 1)`, want: 1},
|
||||||
|
{desc: "bool 2", expr: `int (eq 1 0)`, want: 0},
|
||||||
|
{desc: "nil", expr: `int ()`, want: 0},
|
||||||
|
|
||||||
|
{desc: "list 1", expr: `int [1 2 3]`, wantErr: true},
|
||||||
|
{desc: "list 2", expr: `int []`, wantErr: true},
|
||||||
|
{desc: "dict 1", expr: `int ["hello":"world"]`, wantErr: true},
|
||||||
|
{desc: "dict 2", expr: `int [:]`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_AddSubMupDivMod(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "add 1", expr: `add 1 2`, want: 3},
|
||||||
|
{desc: "add 2", expr: `add "3" 5`, want: 8},
|
||||||
|
{desc: "add 3", expr: `add 1 "2" 8`, want: 11},
|
||||||
|
{desc: "add 4", expr: `add 1`, want: 1},
|
||||||
|
{desc: "add 5", expr: `add`, want: 0},
|
||||||
|
{desc: "sub 1", expr: `sub 9 3`, want: 6},
|
||||||
|
{desc: "sub 2", expr: `sub 2 "5"`, want: -3},
|
||||||
|
{desc: "sub 3", expr: `sub 8 1 8`, want: -1},
|
||||||
|
{desc: "sub 4", expr: `sub 4`, want: 4},
|
||||||
|
{desc: "sub 5", expr: `sub`, want: 0},
|
||||||
|
{desc: "mup 1", expr: `mup 2 4`, want: 8},
|
||||||
|
{desc: "mup 2", expr: `mup 3 "4" 5`, want: 60},
|
||||||
|
{desc: "mup 3", expr: `mup 7`, want: 7},
|
||||||
|
{desc: "mup 4", expr: `mup`, want: 1},
|
||||||
|
{desc: "div 1", expr: `div 8 4`, want: 2},
|
||||||
|
{desc: "div 2", expr: `div "7" 4`, want: 1},
|
||||||
|
{desc: "div 3", expr: `div 7`, want: 7},
|
||||||
|
{desc: "div 4", expr: `div`, want: 1},
|
||||||
|
{desc: "mod 1", expr: `mod 2 3`, want: 2},
|
||||||
|
{desc: "mod 2", expr: `mod "7" 4`, want: 3},
|
||||||
|
{desc: "mod 3", expr: `mod 8 4`, want: 0},
|
||||||
|
{desc: "mod 4", expr: `mod 3`, want: 3},
|
||||||
|
{desc: "mod 5", expr: `mod`, want: 0},
|
||||||
|
|
||||||
|
{desc: "add err", expr: `add [] [:]`, wantErr: true},
|
||||||
|
{desc: "sub err", expr: `sub [] [:]`, wantErr: true},
|
||||||
|
{desc: "mup err", expr: `mup [] [:]`, wantErr: true},
|
||||||
|
{desc: "div err", expr: `div [] [:]`, wantErr: true},
|
||||||
|
{desc: "mod err", expr: `mod [] [:]`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_AndOrNot(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want any
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{desc: "and 1", expr: `and $true $true`, want: true},
|
||||||
|
{desc: "and 2", expr: `and $false $true`, want: false},
|
||||||
|
{desc: "and 3", expr: `and $false $false`, want: false},
|
||||||
|
{desc: "or 1", expr: `or $true $true`, want: true},
|
||||||
|
{desc: "or 2", expr: `or $false $true`, want: true},
|
||||||
|
{desc: "or 3", expr: `or $false $false`, want: false},
|
||||||
|
{desc: "not 1", expr: `not $true`, want: false},
|
||||||
|
{desc: "not 2", expr: `not $false`, want: true},
|
||||||
|
{desc: "not 3", expr: `not $false $true`, want: true},
|
||||||
|
|
||||||
|
{desc: "short circuit and 1", expr: `and "hello" "world"`, want: "world"},
|
||||||
|
{desc: "short circuit and 2", expr: `and () "world"`, want: false},
|
||||||
|
{desc: "short circuit or 1", expr: `or "hello" "world"`, want: "hello"},
|
||||||
|
{desc: "short circuit or 2", expr: `or () "world"`, want: "world"},
|
||||||
|
|
||||||
|
{desc: "bad and 1", expr: `and "one"`, wantErr: true},
|
||||||
|
{desc: "bad and 2", expr: `and`, wantErr: true},
|
||||||
|
{desc: "bad or 1", expr: `or "one"`, wantErr: true},
|
||||||
|
{desc: "bad or 2", expr: `or`, wantErr: true},
|
||||||
|
{desc: "bad not 2", expr: `not`, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
inst.SetVar("true", true)
|
||||||
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuiltins_Cat(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
expr string
|
||||||
|
want any
|
||||||
|
}{
|
||||||
|
{desc: "cat 1", expr: `cat "hello, " "world"`, want: "hello, world"},
|
||||||
|
{desc: "cat 2", expr: `cat "hello, " "world " "and stuff"`, want: "hello, world and stuff"},
|
||||||
|
{desc: "cat 3", expr: `cat "int = " 123`, want: "int = 123"},
|
||||||
|
{desc: "cat 4", expr: `cat "bool = " $true`, want: "bool = true"},
|
||||||
|
{desc: "cat 5", expr: `cat "array = " []`, want: "array = []"},
|
||||||
|
{desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"},
|
||||||
|
{desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"},
|
||||||
|
{desc: "cat 8", expr: `cat`, want: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
outW := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
inst := New(WithOut(outW), WithTestBuiltin())
|
||||||
|
inst.SetVar("true", true)
|
||||||
|
inst.SetVar("false", false)
|
||||||
|
|
||||||
|
eqRes, err := inst.Eval(ctx, tt.expr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, eqRes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue