Compare commits
7 Commits
dce49fd2b7
...
8346e28340
Author | SHA1 | Date | |
---|---|---|---|
8346e28340 | |||
9c2eca40d7 | |||
c3198e4c79 | |||
99e0190b9c | |||
d4f63a3837 | |||
389d48b646 | |||
1f7b9131fc |
1
ast.go
1
ast.go
@ -128,6 +128,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
||||
}
|
||||
if err == nil {
|
||||
result, err = self.root.compute(ctx)
|
||||
ctx.setVar(ControlLastResult, result)
|
||||
}
|
||||
// } else {
|
||||
// err = errors.New("empty expression")
|
||||
|
@ -141,7 +141,7 @@ Numbers can be integers (GO int64) or float (GO float64). In mixed operations in
|
||||
|===
|
||||
|
||||
=== Fractions
|
||||
_Expr_ also suports fractions. Fraction literals are made with tow integers separated by a vertical bar `|`.
|
||||
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
|
||||
|
||||
.Examples
|
||||
// [source,go]
|
||||
@ -149,7 +149,7 @@ _Expr_ also suports fractions. Fraction literals are made with tow integers sepa
|
||||
`>>>` [blue]`1 | 2` +
|
||||
[green]`1|2` +
|
||||
`>>>` [blue]`4|6` +
|
||||
[green]`2|3` [gray]_Fractions are always reduced to theri lowest terms_ +
|
||||
[green]`2|3` [gray]_Fractions are always reduced to their lowest terms_ +
|
||||
`>>>` [blue]`1|2 + 2|3` +
|
||||
[green]`7|6` +
|
||||
`>>>` [blue]`1|2 * 2|3` +
|
||||
@ -245,7 +245,7 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
|
||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||
====
|
||||
|
||||
=== List
|
||||
=== Lists
|
||||
_Expr_ supports list of mixed-type values, also specified by normal expressions.
|
||||
|
||||
.List examples
|
||||
@ -258,7 +258,6 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
|
||||
[ [1,"one"], [2,"two"]] // List of lists
|
||||
----
|
||||
|
||||
|
||||
.List operators
|
||||
[cols="^2,^2,5,4"]
|
||||
|===
|
||||
@ -269,6 +268,34 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
|
||||
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|
||||
|===
|
||||
|
||||
The items of array can be accessed using the dot `.` operator.
|
||||
|
||||
.Item access syntax
|
||||
[source,bnf]
|
||||
----
|
||||
<item> ::= <list-expr>"."<index-expr>
|
||||
----
|
||||
|
||||
.Items of list
|
||||
[source,go]
|
||||
----
|
||||
`>>>` [blue]`[1,2,3].1`
|
||||
[green]`2`
|
||||
`>>>` [blue]`list=[1,2,3]; list.1`
|
||||
[green]`2`
|
||||
`>>>` [blue]`["one","two","three"].1`
|
||||
[green]`two`
|
||||
`>>>` [blue]`list=["one","two","three"]; list.(2-1)`
|
||||
[green]`two`
|
||||
`>>>` [blue]`list.(-1)`
|
||||
[green]`three`
|
||||
`>>>` [blue]`list.(-1)`
|
||||
[green]`three`
|
||||
`>>>` [blue]`list.(10)`
|
||||
----
|
||||
|
||||
|
||||
|
||||
== Dictionaries
|
||||
The _dictionary_ data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. Dictionary literals are sequences of pairs separated by comma `,`; sequences are enclosed between brace brackets.
|
||||
|
||||
|
49
func-base.go
49
func-base.go
@ -18,6 +18,42 @@ func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
return
|
||||
}
|
||||
|
||||
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsInteger(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsFloat(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsBool(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsString(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsFract(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsList(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = IsDict(args[0])
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) == 1 {
|
||||
switch v := args[0].(type) {
|
||||
@ -50,8 +86,17 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
|
||||
}
|
||||
|
||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
|
||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)
|
||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -20,7 +20,7 @@ func TestFuncs(t *testing.T) {
|
||||
/* 7 */ {`int(3.9)`, int64(3), nil},
|
||||
/* 8 */ {`int("432")`, int64(432), nil},
|
||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
|
||||
/* 12 */ {`two=func(){2}; two()`, int64(2), nil},
|
||||
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
||||
@ -54,9 +54,15 @@ func TestFuncs(t *testing.T) {
|
||||
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
|
||||
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
||||
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one","two","three"), nil},
|
||||
/* 45 */ {`["a", "b", "c"]`, newListA("a","b","c"), nil},
|
||||
/* 46 */ {`["a", "b", "c"]`, newList([]any{"a","b","c"}), nil},
|
||||
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
|
||||
/* 45 */ {`isInt(2+1)`, true, nil},
|
||||
/* 46 */ {`isInt(3.1)`, false, nil},
|
||||
/* 46 */ {`isFloat(3.1)`, true, nil},
|
||||
/* 47 */ {`isString("3.1")`, true, nil},
|
||||
/* 48 */ {`isString("3" + 1)`, true, nil},
|
||||
/* 49 */ {`isList(["3", 1])`, true, nil},
|
||||
/* 50 */ {`isDict({"a":"3", "b":1})`, true, nil},
|
||||
/* 51 */ {`isFract(3|1)`, true, nil},
|
||||
}
|
||||
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
35
list_test.go
35
list_test.go
@ -20,22 +20,25 @@ func TestListParser(t *testing.T) {
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`[]`, []any{}, nil},
|
||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
||||
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
||||
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
||||
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
||||
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
||||
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
||||
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 14 */ {`[1,2,3].1`, int64(2), nil},
|
||||
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
|
||||
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
|
||||
/* 1 */ {`[]`, []any{}, nil},
|
||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
||||
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
||||
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
||||
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
||||
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
||||
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
||||
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 14 */ {`[1,2,3].1`, int64(2), nil},
|
||||
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
|
||||
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
|
||||
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
|
||||
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
|
||||
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
|
||||
|
||||
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||
|
@ -22,10 +22,11 @@ func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err
|
||||
var indexValue any
|
||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
|
||||
if v < 0 && v >= -maxValue {
|
||||
v = maxValue + v
|
||||
}
|
||||
if v >= 0 && v < maxValue {
|
||||
index = v
|
||||
} else if index >= -maxValue {
|
||||
index = maxValue + v
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||
ctx := NewSimpleFuncStore()
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, "Iterator", input.source, input.wantResult, input.wantErr)
|
||||
logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
10
utils.go
10
utils.go
@ -24,6 +24,11 @@ func IsFloat(v any) (ok bool) {
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsBool(v any) (ok bool) {
|
||||
_, ok = v.(bool)
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsList(v any) (ok bool) {
|
||||
_, ok = v.(*ListType)
|
||||
return ok
|
||||
@ -34,6 +39,11 @@ func IsDict(v any) (ok bool) {
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsFract(v any) (ok bool) {
|
||||
_, ok = v.(*fraction)
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsNumber(v any) (ok bool) {
|
||||
return IsFloat(v) || IsInteger(v)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user