Compare commits

...

20 Commits

Author SHA1 Message Date
camoroso 4aa0113c6a new string test file 2024-05-10 07:33:59 +02:00
camoroso d035fa0d5e operator-dot.go: fixed string index 2024-05-10 07:32:45 +02:00
camoroso cf73b5c98d Doc: add link to dev-expr download page 2024-05-10 06:39:48 +02:00
camoroso 8346e28340 Doc: fixed some typo and added some list details 2024-05-10 04:49:03 +02:00
camoroso 9c2eca40d7 utils.go: added IsBool() and IsFract() 2024-05-10 04:48:13 +02:00
camoroso c3198e4c79 parser_test.go: did not show the correct section name 2024-05-10 04:47:34 +02:00
camoroso 99e0190b9c operator-dot.go: fixed the index range checking 2024-05-10 04:45:51 +02:00
camoroso d4f63a3837 funcs_test.go: fixed and extended 2024-05-10 04:44:08 +02:00
camoroso 389d48b646 func-base.go: added functions to check data-type of a value 2024-05-10 04:43:14 +02:00
camoroso 1f7b9131fc The 'last' variable now also receives the value of the final expression 2024-05-10 04:41:26 +02:00
camoroso dce49fd2b7 Doc: fractions and dictionaries 2024-05-09 07:20:22 +02:00
camoroso 8ee0bb5701 Some data-type check functions (e.g. IsInteger()) exported 2024-05-08 07:53:01 +02:00
camoroso b2b0bb04c5 formatter.go: number base options added 2024-05-08 07:51:38 +02:00
camoroso f3abf5e77c Doc: dev-expr description updated 2024-05-08 07:51:01 +02:00
camoroso 34b7799177 parser_test.go: test added (mixed number types) 2024-05-07 07:23:38 +02:00
camoroso 8c3c54913a Doc: dev-expr introduction 2024-05-07 07:22:32 +02:00
camoroso 5910345c08 operator-{sum,prod}.go: Fixed sum and prod operation with a fraction and a float 2024-05-06 17:32:39 +02:00
camoroso 8b4dad1381 utils.go: new function isNumOrFract(x); it returns true if x is a float, an integer, ora a fraction 2024-05-06 17:31:12 +02:00
camoroso 6ef468408c operator-sum.go: Fixed sum of fraction and float 2024-05-06 17:16:30 +02:00
camoroso 3a30d890c6 operator-assign.go: improvements that allow to define function aliases (TODO: maybe *funcDefFunctor else-if section can be removed 2024-05-06 16:01:50 +02:00
26 changed files with 1852 additions and 94 deletions
+1
View File
@@ -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")
+161 -6
View File
@@ -22,15 +22,93 @@ Expressions calculator
toc::[]
#TODO: Work in progress#
#TODO: Work in progress (last update on 2024/05/10, 06:52 a.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology
#TODO#
image::expression-diagram.png[]
=== `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, beyond in additon to the automatic test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
It cat work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
Here are some examples of execution.
.Run `dev-expr` in REPL mode and ask for help
[source,shell]
----
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
[user]$ tools/expr -- Expressions calculator v1.7.0,2024/05/08 (celestino.amoroso@portale-stac.it)
Type help to get the list of command.
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help
--- REPL commands:
base -- Set the integer output base: 2, 8, 10, or 16
exit -- Exit the program
help -- Show command list
ml -- Enable/Disable multi-line output
mods -- List builtin modules
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
--- Command line options:
-b <builtin> Import builtin modules.
<builtin> can be a list of module names or a glob-pattern.
Use the special value 'all' or the pattern '*' to import all modules.
-e <expression> Evaluate <expression> instead of standard-input
-i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu
-m, --modules List all builtin modules
-p Print prefix form
-t Print tree form <2>
>>>
----
<1> Only available for single fraction values
<2> Work in progress
.REPL examples
[source,shell]
----
[user]$ tools/expr -- Expressions calculator v1.6.1,2024/05/06 (celestino.amoroso@portale-stac.it)
Type help to get the list of command.
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> 2+3
5
>>> 2+3*(4-1.5)
9.5
>>> 0xFD + 0b1 + 0o1 <1>
255
>>> 1|2 + 2|3 <2>
7|6
>>> ml <3>
>>> 1|2 + 2|3
7
-
6
>>> 1+2 but 5|2+0.5 <4>
3
>>> 1+2; 5|2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
<2> Fractions: numerator | denominator.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
@@ -63,7 +141,44 @@ Numbers can be integers (GO int64) or float (GO float64). In mixed operations in
|===
=== String
=== Fractions
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
.Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` +
[green]`1|2` +
`>>>` [blue]`4|6` +
[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` +
[green]`1|3` +
`>>>` [blue]`1|2 / 1|3` +
[green]`3|2` +
`>>>` [blue]`1|2 ./ 1|3` [gray]_Force decimal division_ +
[green]`1.5` +
`>>>` [blue]`-1|2` +
[green]`-1|2` +
`>>>` [blue]`1|-2` [gray]_Wrong sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
`>>>` [blue]`1|(-2)` +
[green]`-1|2`
// ----
Fractions can be used together with integers and floats in expressions.
`>>>` [blue]`1|2 + 5` +
[green]`11|2` +
`>>>` [blue]`4 - 1|2` +
[green]`7|2` +
`>>>` [blue]`1.0 + 1|2` +
[green]`1.5` +
=== Strings
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
Some arithmetic operators can also be used with strings.
@@ -131,7 +246,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
@@ -144,7 +259,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"]
|===
@@ -155,6 +269,47 @@ _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.
.List examples
[source,go]
----
{1:"one", 2:"two"}
{"one":1, "two": 2}
{"sum":1+2+3, "prod":1*2*3}
----
WARNING: Support for dictionaries is still ongoing.
== Variables
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
@@ -182,7 +337,7 @@ a=1; b=2; c=3; a+b+c // returns 6
----
=== [blue]`but` operator
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
+1497
View File
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+4
View File
@@ -9,6 +9,10 @@ type FmtOpt uint16
const (
TTY FmtOpt = 1 << iota
MultiLine
Base2
Base8
Base10
Base16
)
type Formatter interface {
+47 -2
View File
@@ -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() {
+1 -1
View File
@@ -35,7 +35,7 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isString(paramValue) /*|| isList(paramValue)*/) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
+3 -3
View File
@@ -10,7 +10,7 @@ import (
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
if !(isNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
funcName, paramPos+1, subPos+1, level, paramValue)
}
@@ -45,7 +45,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
count++
if !sumAsFloat {
if isFloat(v) {
if IsFloat(v) {
sumAsFloat = true
if sumAsFract {
floatSum = fractSum.toFloat()
@@ -120,7 +120,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
count++
if !mulAsFloat {
if isFloat(v) {
if IsFloat(v) {
mulAsFloat = true
if mulAsFract {
floatProd = fractProd.toFloat()
+10 -4
View File
@@ -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", ".")
+19 -16
View File
@@ -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},
+11 -5
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// All rights reserightChilded.
// operator-assign.go
package expr
@@ -27,11 +27,17 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
return
}
if v, err = self.children[1].compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, 0
rightChild := self.children[1]
if funcDef, ok := functor.(*funcDefFunctor); ok {
if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, -1
funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists {
minArgs = info.MinArgs()
maxArgs = info.MaxArgs()
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
l := len(funcDef.params)
minArgs = l
maxArgs = l
+1 -1
View File
@@ -26,7 +26,7 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
}
count := 0
if isString(childValue) {
if IsString(childValue) {
module, _ := childValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module)
} else {
+4 -3
View File
@@ -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)
}
@@ -56,7 +57,7 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
case string:
var index int
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
v = unboxedValue[index]
v = string(unboxedValue[index])
}
case map[any]any:
var ok bool
+1 -1
View File
@@ -25,7 +25,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
return
}
if isInteger(leftValue) {
if IsInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 {
f := int64(1)
for k := int64(1); k <= i; k++ {
+2 -2
View File
@@ -24,7 +24,7 @@ func evalInclude(ctx ExprContext, self *term) (v any, err error) {
}
count := 0
if isList(childValue) {
if IsList(childValue) {
list, _ := childValue.([]any)
for i, filePathSpec := range list {
if filePath, ok := filePathSpec.(string); ok {
@@ -39,7 +39,7 @@ func evalInclude(ctx ExprContext, self *term) (v any, err error) {
break
}
}
} else if isString(childValue) {
} else if IsString(childValue) {
filePath, _ := childValue.(string)
v, err = EvalFile(ctx, filePath)
} else {
+2 -2
View File
@@ -33,7 +33,7 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
return
}
if isList(rightValue) {
if IsList(rightValue) {
list, _ := rightValue.(*ListType)
newList := append(ListType{leftValue}, *list...)
v = &newList
@@ -50,7 +50,7 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
return
}
if isList(leftValue) {
if IsList(leftValue) {
list, _ := leftValue.(*ListType)
newList := append(*list, rightValue)
v = &newList
+2 -2
View File
@@ -23,10 +23,10 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
return
}
if isList(childValue) {
if IsList(childValue) {
list, _ := childValue.([]any)
v = int64(len(list))
} else if isString(childValue) {
} else if IsString(childValue) {
s, _ := childValue.(string)
v = int64(len(s))
} else if it, ok := childValue.(Iterator); ok {
+1 -1
View File
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else if isInteger(childValue) && self.children[0].symbol() == SymIdentifier {
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
+11 -11
View File
@@ -30,20 +30,20 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
return
}
if isString(leftValue) && isInteger(rightValue) {
if IsString(leftValue) && IsInteger(rightValue) {
s, _ := leftValue.(string)
n, _ := rightValue.(int64)
v = strings.Repeat(s, int(n))
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) * numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt * rightInt
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -71,14 +71,16 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
} else {
v = numAsFloat(leftValue) / d
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
@@ -87,8 +89,6 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
v = leftInt / rightInt
}
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -114,7 +114,7 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
@@ -148,7 +148,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
return
}
if isInteger(leftValue) && isInteger(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
err = errors.New("division by zero")
+9 -9
View File
@@ -25,15 +25,15 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li == ri
} else {
v = numAsFloat(leftValue) == numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls == rs
@@ -111,15 +111,15 @@ func evalLess(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li < ri
} else {
v = numAsFloat(leftValue) < numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls < rs
@@ -148,15 +148,15 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li <= ri
} else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls <= rs
+2 -2
View File
@@ -35,14 +35,14 @@ func evalSign(ctx ExprContext, self *term) (v any, err error) {
return
}
if isFloat(rightValue) {
if IsFloat(rightValue) {
if self.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64)
v = -f
} else {
v = rightValue
}
} else if isInteger(rightValue) {
} else if IsInteger(rightValue) {
if self.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64)
v = -i
+14 -10
View File
@@ -28,17 +28,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
return
}
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
} else if IsNumber(leftValue) && IsNumber(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt + rightInt
}
} else if isList(leftValue) || isList(rightValue) {
} else if IsList(leftValue) || IsList(rightValue) {
var leftList, rightList *ListType
var ok bool
if leftList, ok = leftValue.(*ListType); !ok {
@@ -56,7 +56,11 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
}
v = &sumList
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = sumAnyFract(leftValue, rightValue)
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
v, err = sumAnyFract(leftValue, rightValue)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -82,15 +86,17 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt - rightInt
}
} else if isList(leftValue) && isList(rightValue) {
} else if IsList(leftValue) && IsList(rightValue) {
leftList, _ := leftValue.(*ListType)
rightList, _ := rightValue.(*ListType)
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
@@ -100,8 +106,6 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
}
}
v = &diffList
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
+6 -5
View File
@@ -35,10 +35,10 @@ func TestGeneralParser(t *testing.T) {
/* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* *17 */ {`"uno" + "due"`, `unodue`, nil},
/* *18 */ {`"uno" + 2`, `uno2`, nil},
/* *19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* *20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil},
@@ -157,6 +157,7 @@ func TestGeneralParser(t *testing.T) {
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 137 */ {`builtin "os.file"`, int64(1), nil},
/* 138 */ {`v=10; v++; v`, int64(11), nil},
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
}
// t.Setenv("EXPR_PATH", ".")
@@ -176,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())
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// strings_test.go
package expr
import (
"testing"
)
func TestStringsParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil},
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc".1`, `b`, nil},
/* 5 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
}
+22 -8
View File
@@ -9,37 +9,51 @@ import (
"reflect"
)
func isString(v any) (ok bool) {
func IsString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
func isInteger(v any) (ok bool) {
func IsInteger(v any) (ok bool) {
_, ok = v.(int64)
return ok
}
func isFloat(v any) (ok bool) {
func IsFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func isList(v any) (ok bool) {
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
}
func isDict(v any) (ok bool) {
func IsDict(v any) (ok bool) {
_, ok = v.(map[any]any)
return ok
}
func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v)
func IsFract(v any) (ok bool) {
_, ok = v.(*fraction)
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}
func isNumOrFract(v any) (ok bool) {
return IsFloat(v) || IsInteger(v) || isFraction(v)
}
func isNumberString(v any) (ok bool) {
return isString(v) || isNumber(v)
return IsString(v) || IsNumber(v)
}
func isFunctor(v any) (ok bool) {