Compare commits

...

43 Commits

Author SHA1 Message Date
camoroso 8144122d2c fixed some defects in the iter-list implementation 2024-06-05 08:06:39 +02:00
camoroso 188ea354ee it-range.go renamed as it-range.go.unused 2024-06-05 08:05:42 +02:00
camoroso 2fc6bbfe10 removed old simple vars and funcs context implementations 2024-06-05 05:54:12 +02:00
camoroso 847d85605e removed unused commented code 2024-06-05 05:53:02 +02:00
camoroso 9c29392389 changes to adapt errors using typeName() functions 2024-06-05 05:52:27 +02:00
camoroso a7b6e6f8d2 t_list_test.go: some tests added 2024-06-05 05:50:37 +02:00
camoroso a16ac70e4a t_dict_test.go: some tests added 2024-06-05 05:49:07 +02:00
camoroso ab2e3f0528 use of typeName() in error messages 2024-06-05 05:48:02 +02:00
camoroso 974835a8ef utils.go/typeName() and formatter.go/getTipeName() have been merged in formatter.go/typeName() 2024-06-05 05:42:37 +02:00
camoroso 457a656073 tests on collection's item assignments and some other changes 2024-06-05 05:09:13 +02:00
camoroso 9e63e1402e t_parser_test.go: Expr's type names 2024-06-05 05:06:43 +02:00
camoroso e4ded4f746 operator-assign.go: some errors message changed to report the Expr's type names, not the Go ones 2024-06-05 05:05:40 +02:00
camoroso 4e3af837e6 list-type.go: constructor newListA(), called without arguments, now creates an empty list, not a nil object 2024-06-05 05:03:37 +02:00
camoroso ca12722c93 utils.go: new function typeName() 2024-06-05 05:01:34 +02:00
camoroso d96123ab02 The assign operator '=' can now set items in ListType and DictType 2024-06-04 11:07:35 +02:00
camoroso f2d6d63017 fixed an error message (test nr 97) 2024-06-04 11:04:59 +02:00
camoroso 905b2af7fa setItem() function 2024-06-04 11:04:00 +02:00
camoroso 9307473d08 Call() implementation can invoke varaibles holding functors 2024-06-04 11:03:24 +02:00
camoroso 10a596a4cd parser.go: commented out some useless code 2024-06-04 11:02:26 +02:00
camoroso 609fb21505 global-context.go: variables holding function values can be invoked as function now 2024-06-04 11:01:04 +02:00
camoroso 7650a4a441 DictType moved from operand-dict.go to the dict-type.go file 2024-06-04 10:59:25 +02:00
camoroso f51d6023ae Doc: start of function documentation 2024-06-03 06:27:14 +02:00
camoroso 99454227d5 little changes to test source files 2024-06-02 12:33:32 +02:00
camoroso 75358e5d35 func-fmt.go: print() and println() 2024-06-02 12:32:08 +02:00
camoroso 51b272dda8 func-string.go: ad least one extra prefix and suffix for startsWithStr() and endsWitchStr() 2024-06-02 12:30:17 +02:00
camoroso 7f282e5460 func-string.go: the second parameter joinStr() flags changed as pfRepeat 2024-06-02 11:56:17 +02:00
camoroso cff21b40f7 typeFilepath changed 2024-06-02 11:54:43 +02:00
camoroso 4f432da2b9 typeFilepath and typeDirpath renamed as paramFilepath and paramDirpath respectively 2024-06-02 11:32:47 +02:00
camoroso e4b4b4fb79 function.go: if the right most parameter is repeteable (pfRepeat), then it does not increment the min-arg count 2024-06-02 11:30:24 +02:00
camoroso c04678c426 simple-store.go: duplicated name removed from funcs list 2024-06-02 11:12:53 +02:00
camoroso 9bba40f155 Expr's functions now support parameters with default value 2024-06-01 19:56:40 +02:00
camoroso f66cd1fdb1 New test file for specific code section or data type 2024-06-01 16:31:50 +02:00
camoroso f41ea96d17 Expr functions now act as closures 2024-05-30 07:13:26 +02:00
camoroso d84e690ef3 deep list inclusion and item membership implemented 2024-05-29 13:03:58 +02:00
camoroso 4b25a07699 commented code removed 2024-05-28 07:28:33 +02:00
camoroso 3736214c5a A lot of changes. Main ones are:
- fraction type renamed as FractionType and moved from operator-fraction.go to fraction-type.go
- ListType moved from operator-list.go to list-type.go
- all test file were renamed adding the "t_" prefix
- defined a test template in file t_temple_test.go
- new test file t_relational_test.go where relational tests are collected
- lists can now compared as set using operators <, <=, >, and >= (IMPORTANT: here = menas same content, not same list)
2024-05-28 07:26:05 +02:00
camoroso 78cbb7b36f test index/5 moved to list/26 because reflection's deep-equal function returns false even though computed and wanted lists are equal 2024-05-26 06:30:42 +02:00
camoroso 2c87d6bf9e Eprx now supports range of indeces to extract parts of strings or lists 2024-05-26 06:19:08 +02:00
camoroso 691c213d17 operator-dot.go: the '.' (dot) operator can now only be used to call member functions of iterators 2024-05-25 03:35:17 +02:00
camoroso fa136cb70b parser.go: square brackets are also allowed after a variable 2024-05-25 03:32:13 +02:00
camoroso 76dd01afcd strings_test.go: test nr 5,6 fixed 2024-05-25 03:30:26 +02:00
camoroso 4283fab816 list_test.go: test nr 15,16,17 fixed 2024-05-25 03:28:01 +02:00
camoroso 03d4c192c2 new syntax to get items from collection: collection[index]. Supported collections are string, list and dict 2024-05-24 22:51:01 +02:00
70 changed files with 2047 additions and 1718 deletions
+3 -2
View File
@@ -127,8 +127,9 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
} }
} }
if err == nil { if err == nil {
result, err = self.root.compute(ctx) if result, err = self.root.compute(ctx); err == nil {
ctx.UnsafeSetVar(ControlLastResult, result) ctx.UnsafeSetVar(ControlLastResult, result)
}
} }
// } else { // } else {
// err = errors.New("empty expression") // err = errors.New("empty expression")
+15 -15
View File
@@ -8,48 +8,48 @@ import (
"fmt" "fmt"
) )
func errTooFewParams(minArgs, maxArgs, argCount int) (err error) { func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 { if maxArgs < 0 {
err = fmt.Errorf("too few params -- expected %d or more, got %d", minArgs, argCount) err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
} else { } else {
err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount) err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
} }
return return
} }
func errTooMuchParams(maxArgs, argCount int) (err error) { func errTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount) err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
return return
} }
// --- General errors // --- General errors
func errCantConvert(funcName string, value any, kind string) error { func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind) return fmt.Errorf("%s(): can't convert %s to %s", funcName, typeName(value), kind)
} }
func errExpectedGot(funcName string, kind string, value any) error { func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value) return fmt.Errorf("%s() expected %s, got %s (%v)", funcName, kind, typeName(value), value)
} }
func errDivisionByZero(funcName string) error { func errFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s() division by zero", funcName) return fmt.Errorf("%s(): division by zero", funcName)
}
func errDivisionByZero() error {
return fmt.Errorf("division by zero")
} }
// --- Parameter errors // --- Parameter errors
// func errOneParam(funcName string) error {
// return fmt.Errorf("%s() requires exactly one param", funcName)
// }
func errMissingRequiredParameter(funcName, paramName string) error { func errMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName) return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
} }
func errInvalidParameterValue(funcName, paramName string, paramValue any) error { func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName) return fmt.Errorf("%s() invalid value %s (%v) for parameter %q", funcName, typeName(paramValue), paramValue, paramName)
} }
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error { func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue) return fmt.Errorf("%s() the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, typeName(paramValue), paramValue)
} }
+2 -11
View File
@@ -16,15 +16,6 @@ const (
paramEnd = "end" paramEnd = "end"
paramValue = "value" paramValue = "value"
paramEllipsis = "..." paramEllipsis = "..."
typeFilepath = "filepath" paramFilepath = "filepath"
typeDirpath = "dirpath" paramDirpath = "dirpath"
) )
// const (
// typeInteger = "int"
// typeFloat = "float"
// typeString = "string"
// typeFraction = "fract"
// typeList = "list"
// typeDict = "dict"
// )
+1
View File
@@ -13,5 +13,6 @@ const (
typeInt = "integer" typeInt = "integer"
typeItem = "item" typeItem = "item"
typeNumber = "number" typeNumber = "number"
typePair = "pair"
typeString = "string" typeString = "string"
) )
+2
View File
@@ -9,6 +9,7 @@ type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error) Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc) SetFunc(info ExprFunc)
GetFunc() ExprFunc GetFunc() ExprFunc
GetParams() []ExprFuncParam
} }
// ---- Function Param Info // ---- Function Param Info
@@ -34,6 +35,7 @@ type ExprFunc interface {
// ----Expression Context // ----Expression Context
type ExprContext interface { type ExprContext interface {
Clone() ExprContext Clone() ExprContext
Merge(ctx ExprContext)
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any) SetVar(varName string, value any)
UnsafeSetVar(varName string, value any) UnsafeSetVar(varName string, value any)
+130
View File
@@ -0,0 +1,130 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-type.go
package expr
import (
"fmt"
"reflect"
"strings"
)
type DictType map[any]any
func newDict(dictAny map[any]*term) (dict *DictType) {
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("{\n")
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(strings.Repeat("\t", indent+1))
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(MultiLine))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine != 0 {
dict.toMultiLine(&sb, 0)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", key))
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else if t, ok := value.(*term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return "dict"
}
func (dict *DictType) hasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
}
return
}
func (dict *DictType) merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
func (dict *DictType) setItem(key any, value any) (err error) {
(*dict)[key]=value
return
}
+5 -2
View File
@@ -22,7 +22,7 @@ Expressions calculator
toc::[] toc::[]
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)# #TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)#
== Expr == Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions. _Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
@@ -615,7 +615,10 @@ The table below shows all supported operators by decreasing priorities.
|=== |===
== Functions == Functions
Functions in _Expr_ are very similar to functions in many programming languages. Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context. In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
+14 -4
View File
@@ -585,7 +585,7 @@ pre.rouge .ss {
<div class="sectionbody"> <div class="sectionbody">
<!-- toc disabled --> <!-- toc disabled -->
<div class="paragraph"> <div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)</mark></p> <p><mark>TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)</mark></p>
</div> </div>
</div> </div>
</div> </div>
@@ -1609,7 +1609,7 @@ These operators have a high priority, in particular higher than the operator <co
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>""</code> <em>any</em> &#8594; <em>any</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"."</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td> <td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td>
@@ -1834,7 +1834,17 @@ These operators have a high priority, in particular higher than the operator <co
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2> <h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2>
<div class="sectionbody"> <div class="sectionbody">
<div class="paragraph"> <div class="paragraph">
<p>Functions in <em>Expr</em> are very similar to functions in many programming languages.</p> <p>Functions in <em>Expr</em> are very similar to functions available in many programming languages. Actually, <em>Expr</em> supports two types of function, <em>expr-functions</em> and <em>go-functions</em>.</p>
</div>
<div class="ulist">
<ul>
<li>
<p><em>expr-functions</em> are defined using <em>Expr</em>'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.</p>
</li>
<li>
<p><em>go-functions</em> are regular Golang functions callable from <em>Expr</em> expressions. They are defined in Golang source files called <em>modules</em> and compiled within the <em>Expr</em> package. To make Golang functions available in <em>Expr</em> contextes, it is required to <em>import</em> the module in which they are defined.</p>
</li>
</ul>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p>In <em>Expr</em> functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator <code class="blue">@</code> it is possibile to export local definition to the calling context.</p> <p>In <em>Expr</em> functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator <code class="blue">@</code> it is possibile to export local definition to the calling context.</p>
@@ -1874,7 +1884,7 @@ These operators have a high priority, in particular higher than the operator <co
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2024-05-20 09:40:23 +0200 Last updated 2024-06-03 06:26:03 +0200
</div> </div>
</div> </div>
</body> </body>
-82
View File
@@ -1,82 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr_test.go
package expr
import (
"strings"
"testing"
)
func TestExpr(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
};
it=$(ds,3);
it++;
it++
`, int64(1), nil},
}
succeeded := 0
failed := 0
for i, input := range inputs {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
ImportOsFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "Expr", input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
}
+4 -2
View File
@@ -51,8 +51,10 @@ type Typer interface {
TypeName() string TypeName() string
} }
func getTypeName(v any) (name string) { func typeName(v any) (name string) {
if typer, ok := v.(Typer); ok { if v == nil {
name = "nil"
} else if typer, ok := v.(Typer); ok {
name = typer.TypeName() name = typer.TypeName()
} else if IsInteger(v) { } else if IsInteger(v) {
name = "integer" name = "integer"
+359
View File
@@ -0,0 +1,359 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// fraction-type.go
package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
type FractionType struct {
num, den int64
}
func newFraction(num, den int64) *FractionType {
num, den = simplifyIntegers(num, den)
return &FractionType{num, den}
}
func float64ToFraction(f float64) (fract *FractionType, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
func makeGeneratingFraction(s string) (f *FractionType, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2]
}
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *FractionType) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *FractionType) String() string {
return f.ToString(0)
}
func (f *FractionType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
} else {
var s, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
} else {
if len(s) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(s)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
}
return sb.String()
}
func (f *FractionType) TypeName() string {
return "fraction"
}
// -------- fraction utility functions
// greatest common divider
func gcd(a, b int64) (g int64) {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
a, b = b, a
}
r := a % b
for r > 0 {
a, b = b, r
r = a % b
}
g = b
return
}
// lower common multiple
func lcm(a, b int64) (l int64) {
g := gcd(a, b)
l = a * b / g
return
}
// Sum two fractions
func sumFract(f1, f2 *FractionType) (sum *FractionType) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
// Multiply two fractions
func mulFract(f1, f2 *FractionType) (prod *FractionType) {
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
return
}
func anyToFract(v any) (f *FractionType, err error) {
var ok bool
if f, ok = v.(*FractionType); !ok {
if n, ok := v.(int64); ok {
f = intToFraction(n)
}
}
if f == nil {
err = errExpectedGot("fract", typeFraction, v)
}
return
}
func anyPairToFract(v1, v2 any) (f1, f2 *FractionType, err error) {
if f1, err = anyToFract(v1); err != nil {
return
}
if f2, err = anyToFract(v2); err != nil {
return
}
return
}
func sumAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
// err if af1 or af2 is not convertible to fraction
func cmpAnyFract(af1, af2 any) (result int, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
result = cmpFract(f1, f2)
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
func cmpFract(f1, f2 *FractionType) (result int) {
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num < 0 {
result = -1
} else if f.num > 0 {
result = 1
} else {
result = 0
}
return
}
func subAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func mulAnyFract(af1, af2 any) (prod any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func divAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &FractionType{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *FractionType) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &FractionType{f.num, f.den}
}
return v
}
func simplifyIntegers(num, den int64) (a, b int64) {
if num == 0 {
return 0, 1
}
if den == 0 {
panic("fraction with denominator == 0")
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
a = num / g
b = den / g
return
}
func intToFraction(n int64) *FractionType {
return &FractionType{n, 1}
}
func isFraction(v any) (ok bool) {
_, ok = v.(*FractionType)
return ok
}
+4 -4
View File
@@ -58,7 +58,7 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
switch v := args[0].(type) { switch v := args[0].(type) {
case int64: case int64:
result = (v != 0) result = (v != 0)
case *fraction: case *FractionType:
result = v.num != 0 result = v.num != 0
case float64: case float64:
result = v != 0.0 result = v != 0.0
@@ -112,7 +112,7 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if f, err = strconv.ParseFloat(v, 64); err == nil { if f, err = strconv.ParseFloat(v, 64); err == nil {
result = f result = f
} }
case *fraction: case *FractionType:
result = v.toFloat() result = v.toFloat()
default: default:
err = errCantConvert(name, v, "float") err = errCantConvert(name, v, "float")
@@ -129,7 +129,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
if den, ok = args[1].(int64); !ok { if den, ok = args[1].(int64); !ok {
err = errExpectedGot(name, "integer", args[1]) err = errExpectedGot(name, "integer", args[1])
} else if den == 0 { } else if den == 0 {
err = errDivisionByZero(name) err = errFuncDivisionByZero(name)
} }
} }
if err == nil { if err == nil {
@@ -145,7 +145,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
} }
case string: case string:
result, err = makeGeneratingFraction(v) result, err = makeGeneratingFraction(v)
case *fraction: case *FractionType:
result = v result = v
default: default:
err = errCantConvert(name, v, "float") err = errCantConvert(name, v, "float")
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-fmt.go
package expr
import "fmt"
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Print(args...); err == nil {
result = int64(n)
}
return
}
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Println(args...); err == nil {
result = int64(n)
}
return
}
func ImportFmtFuncs(ctx ExprContext) {
ctx.RegisterFunc("print", newGolangFunctor(printFunc), typeInt, []ExprFuncParam{
newFuncParamFlag(paramItem, pfRepeat),
})
ctx.RegisterFunc("println", newGolangFunctor(printLnFunc), typeInt, []ExprFuncParam{
newFuncParamFlag(paramItem, pfRepeat),
})
}
func init() {
registerImport("fmt", ImportFmtFuncs, "String and console formatting functions")
}
+2 -2
View File
@@ -138,10 +138,10 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
func ImportImportFuncs(ctx ExprContext) { func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{ ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat), newFuncParamFlag(paramFilepath, pfRepeat),
}) })
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{ ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat), newFuncParamFlag(paramFilepath, pfRepeat),
}) })
} }
+8 -8
View File
@@ -21,7 +21,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
var sumAsFloat, sumAsFract bool var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0 var floatSum float64 = 0.0
var intSum int64 = 0 var intSum int64 = 0
var fractSum *fraction var fractSum *FractionType
var v any var v any
level++ level++
@@ -61,9 +61,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
if sumAsFloat { if sumAsFloat {
floatSum += numAsFloat(v) floatSum += numAsFloat(v)
} else if sumAsFract { } else if sumAsFract {
var item *fraction var item *FractionType
var ok bool var ok bool
if item, ok = v.(*fraction); !ok { if item, ok = v.(*FractionType); !ok {
iv, _ := v.(int64) iv, _ := v.(int64)
item = newFraction(iv, 1) item = newFraction(iv, 1)
} }
@@ -95,7 +95,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
var mulAsFloat, mulAsFract bool var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0 var floatProd float64 = 1.0
var intProd int64 = 1 var intProd int64 = 1
var fractProd *fraction var fractProd *FractionType
var v any var v any
level++ level++
@@ -136,9 +136,9 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
if mulAsFloat { if mulAsFloat {
floatProd *= numAsFloat(v) floatProd *= numAsFloat(v)
} else if mulAsFract { } else if mulAsFract {
var item *fraction var item *FractionType
var ok bool var ok bool
if item, ok = v.(*fraction); !ok { if item, ok = v.(*FractionType); !ok {
iv, _ := v.(int64) iv, _ := v.(int64)
item = newFraction(iv, 1) item = newFraction(iv, 1)
} }
@@ -168,11 +168,11 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
func ImportMathFuncs(ctx ExprContext) { func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0), newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(0)),
}) })
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1), newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(1)),
}) })
} }
+84 -55
View File
@@ -20,6 +20,14 @@ type osWriter struct {
writer *bufio.Writer writer *bufio.Writer
} }
func (h *osWriter) TypeName() string {
return "osWriter"
}
func (h *osWriter) String() string {
return "writer"
}
func (h *osWriter) getFile() *os.File { func (h *osWriter) getFile() *os.File {
return h.fh return h.fh
} }
@@ -29,66 +37,73 @@ type osReader struct {
reader *bufio.Reader reader *bufio.Reader
} }
func (h *osReader) TypeName() string {
return "osReader"
}
func (h *osReader) String() string {
return "reader"
}
func (h *osReader) getFile() *os.File { func (h *osReader) getFile() *os.File {
return h.fh return h.fh
} }
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func errMissingFilePath(funcName string) error {
var filePath string return fmt.Errorf("%s(): missing or invalid file path", funcName)
if len(args) > 0 { }
filePath, _ = args[0].(string)
}
if len(filePath) > 0 { func errInvalidFileHandle(funcName string, v any) error {
if v != nil {
return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v)
} else {
return fmt.Errorf("%s(): invalid file handle", funcName)
}
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
var fh *os.File var fh *os.File
if fh, err = os.Create(filePath); err == nil { if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = fmt.Errorf("%s(): missing the file path", name) err = errMissingFilePath("createFile")
} }
return return
} }
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File var fh *os.File
if fh, err = os.Open(filePath); err == nil { if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)} result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
} }
} else { } else {
err = fmt.Errorf("%s(): missing the file path", name) err = errMissingFilePath("openFile")
} }
return return
} }
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil { if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = fmt.Errorf("%s(): missing the file path", name) err = errMissingFilePath("openFile")
} }
return return
} }
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any
var ok bool
if len(args) > 0 { if handle, ok = args[0].(osHandle); !ok {
handle, _ = args[0].(osHandle) invalidFileHandle = args[0]
} }
if handle != nil { if handle != nil {
@@ -96,12 +111,14 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
if w, ok := handle.(*osWriter); ok { if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush() err = w.writer.Flush()
} }
if err == nil { if err == nil {
err = fh.Close() err = fh.Close()
} }
} }
} else { }
err = fmt.Errorf("%s(): invalid file handle", name) if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("closeFileFunc", handle)
} }
result = err == nil result = err == nil
return return
@@ -109,63 +126,75 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any
var ok bool
if len(args) > 0 { if handle, ok = args[0].(osHandle); !ok {
handle, _ = args[0].(osHandle) invalidFileHandle = args[0]
} }
if handle != nil { if handle != nil {
if fh := handle.getFile(); fh != nil { if w, ok := handle.(*osWriter); ok {
if w, ok := handle.(*osWriter); ok { result, err = fmt.Fprint(w.writer, args[1:]...)
result, err = fmt.Fprint(w.writer, args[1:]...) } else {
} invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle)
}
return return
} }
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any
var ok bool
result = nil result = nil
if len(args) > 0 { if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
handle, _ = args[0].(osHandle) invalidFileHandle = args[0]
} }
if handle != nil { if handle != nil {
if fh := handle.getFile(); fh != nil { if r, ok := handle.(*osReader); ok {
if r, ok := handle.(*osReader); ok { var limit byte = '\n'
var limit byte = '\n' var v string
var v string if s, ok := args[1].(string); ok && len(s) > 0 {
if len(args) > 1 { limit = s[0]
if s, ok := args[1].(string); ok && len(s) > 0 { }
limit = s[0]
} if v, err = r.reader.ReadString(limit); err == nil {
} if len(v) > 0 && v[len(v)-1] == limit {
if v, err = r.reader.ReadString(limit); err == nil { result = v[0 : len(v)-1]
if len(v) > 0 && v[len(v)-1] == limit { } else {
result = v[0 : len(v)-1] result = v
} else {
result = v
}
}
if err == io.EOF {
err = nil
} }
} }
if err == io.EOF {
err = nil
}
} else {
invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("readFileFunc", invalidFileHandle)
}
return return
} }
func ImportOsFuncs(ctx ExprContext) { func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath), newFuncParam(paramFilepath),
}) })
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath), newFuncParam(paramFilepath),
}) })
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath), newFuncParam(paramFilepath),
}) })
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{ ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
newFuncParam(typeHandle), newFuncParam(typeHandle),
@@ -173,7 +202,7 @@ func ImportOsFuncs(ctx ExprContext) {
}) })
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
newFuncParam(typeHandle), newFuncParam(typeHandle),
newFuncParamFlagDef("limitCh", pfOptional, "\\n"), newFuncParamFlagDef("limitCh", pfOptional, "\n"),
}) })
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
newFuncParam(typeHandle), newFuncParam(typeHandle),
+30 -29
View File
@@ -65,19 +65,19 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) return nil, errWrongParamType(name, paramSource, typeString, args[0])
} }
if len(args) > 1 {
if start, err = toInt(args[1], name+"()"); err != nil { if start, err = toInt(args[1], name+"()"); err != nil {
return return
}
if len(args) > 2 {
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
}
if start < 0 {
start = len(source) + start
}
} }
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
if start < 0 {
start = len(source) + start
}
if count < 0 { if count < 0 {
count = len(source) - start count = len(source) - start
} }
@@ -152,18 +152,17 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, errWrongParamType(name, paramSource, typeString, args[0])
} }
if len(args) >= 2 {
if sep, ok = args[1].(string); !ok { if sep, ok = args[1].(string); !ok {
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1]) return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
}
if len(args) >= 3 {
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
}
}
} }
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
}
if count > 0 { if count > 0 {
parts = strings.SplitN(source, sep, count) parts = strings.SplitN(source, sep, count)
} else if count < 0 { } else if count < 0 {
@@ -185,19 +184,19 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
func ImportStringFuncs(ctx ExprContext) { func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSeparator), newFuncParam(paramSeparator),
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""), newFuncParamFlag(paramItem, pfRepeat),
}) })
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource), newFuncParam(paramSource),
newFuncParamFlagDef(paramStart, pfOptional, 0), newFuncParamFlagDef(paramStart, pfOptional, int64(0)),
newFuncParamFlagDef(paramCount, pfOptional, -1), newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
}) })
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{ ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
newFuncParam(paramSource), newFuncParam(paramSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""), newFuncParamFlagDef(paramSeparator, pfOptional, ""),
newFuncParamFlagDef(paramCount, pfOptional, -1), newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
}) })
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
@@ -206,12 +205,14 @@ func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource), newFuncParam(paramSource),
newFuncParamFlag(paramPrefix, pfRepeat), newFuncParam(paramPrefix),
newFuncParamFlag("other "+paramPrefix, pfRepeat),
}) })
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource), newFuncParam(paramSource),
newFuncParamFlag(paramSuffix, pfRepeat), newFuncParam(paramSuffix),
newFuncParamFlag("other "+paramSuffix, pfRepeat),
}) })
} }
-116
View File
@@ -1,116 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncs(t *testing.T) {
inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil},
/* 2 */ {`v=nil; isNil(v)`, true, nil},
/* 3 */ {`v=5; isNil(v)`, false, nil},
/* 4 */ {`int(true)`, int64(1), nil},
/* 5 */ {`int(false)`, int64(0), nil},
/* 6 */ {`int(3.1)`, int64(3), nil},
/* 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(`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},
/* 14 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 15 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 16 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 17 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 18 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 19 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 20 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 21 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 22 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 23 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 24 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 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 */ {`isInt(2+1)`, true, nil},
/* 46 */ {`isInt(3.1)`, false, nil},
/* 47 */ {`isFloat(3.1)`, true, nil},
/* 48 */ {`isString("3.1")`, true, nil},
/* 49 */ {`isString("3" + 1)`, true, nil},
/* 50 */ {`isList(["3", 1])`, true, nil},
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 52 */ {`isFract(1|3)`, true, nil},
/* 53 */ {`isFract(3|1)`, false, nil},
/* 54 */ {`isRational(3|1)`, true, nil},
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
/* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
/* 59 */ {`fract(1.21)`, newFraction(121, 100), nil},
/* 60 */ {`dec(2)`, float64(2), nil},
/* 61 */ {`dec(2.0)`, float64(2), nil},
/* 62 */ {`dec("2.0")`, float64(2), nil},
/* 63 */ {`dec(true)`, float64(1), nil},
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
/* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "Func", inputs, 69)
parserTest(t, "Func", inputs)
}
+28 -11
View File
@@ -26,6 +26,14 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
return s return s
} }
func (functor *baseFunctor) GetParams() (params []ExprFuncParam) {
if functor.info != nil {
return functor.info.Params()
} else {
return []ExprFuncParam{}
}
}
func (functor *baseFunctor) SetFunc(info ExprFunc) { func (functor *baseFunctor) SetFunc(info ExprFunc) {
functor.info = info functor.info = info
} }
@@ -34,7 +42,7 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info return functor.info
} }
// ---- Linking with the functions of Go // ---- Linking with Go functions
type golangFunctor struct { type golangFunctor struct {
baseFunctor baseFunctor
f FuncTemplate f FuncTemplate
@@ -48,31 +56,39 @@ func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (
return functor.f(ctx, name, args) return functor.f(ctx, name, args)
} }
// ---- Linking with the functions of Expr // ---- Linking with Expr functions
type exprFunctor struct { type exprFunctor struct {
baseFunctor baseFunctor
params []string params []ExprFuncParam
expr Expr expr Expr
defCtx ExprContext
} }
func newExprFunctor(e Expr, params []string) *exprFunctor { // func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params} // return &exprFunctor{expr: e, params: params, defCtx: ctx}
// }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params, defCtx: ctx}
} }
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
if functor.defCtx != nil {
ctx.Merge(functor.defCtx)
}
for i, p := range functor.params { for i, p := range functor.params {
if i < len(args) { if i < len(args) {
arg := args[i] arg := args[i]
if funcArg, ok := arg.(Functor); ok { if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1) // ctx.RegisterFunc(p, functor, 0, -1)
ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{ paramSpecs := funcArg.GetParams()
newFuncParam(paramValue), ctx.RegisterFunc(p.Name(), funcArg, typeAny, paramSpecs)
})
} else { } else {
ctx.UnsafeSetVar(p, arg) ctx.UnsafeSetVar(p.Name(), arg)
} }
} else { } else {
ctx.UnsafeSetVar(p, nil) ctx.UnsafeSetVar(p.Name(), nil)
} }
} }
result, err = functor.expr.eval(ctx, false) result, err = functor.expr.eval(ctx, false)
@@ -152,6 +168,7 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name()) return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
} }
if p.IsRepeat() { if p.IsRepeat() {
minArgs--
maxArgs = -1 maxArgs = -1
} }
} }
@@ -205,7 +222,7 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
if info.maxArgs < 0 { if info.maxArgs < 0 {
sb.WriteString(" ...") sb.WriteString(" ...")
} }
sb.WriteString("): ") sb.WriteString("):")
if len(info.returnType) > 0 { if len(info.returnType) > 0 {
sb.WriteString(info.returnType) sb.WriteString(info.returnType)
} else { } else {
+18 -4
View File
@@ -40,11 +40,25 @@ func GetVar(ctx ExprContext, name string) (value any, exists bool) {
return return
} }
func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) {
var v any
if len(name) > 0 {
if v, exists = ctx.GetVar(name); exists && isFunctor(v) {
f, _ := v.(Functor)
item = f.GetFunc()
} else {
item, exists = ctx.GetFuncInfo(name)
}
}
return
}
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) { func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
if item, exists = ctx.GetFuncInfo(name); exists { if len(name) > 0 {
ownerCtx = ctx if item, exists = GetLocalFuncInfo(ctx, name); exists {
} else if item, exists = globalCtx.GetFuncInfo(name); exists { ownerCtx = ctx
ownerCtx = globalCtx } else if item, exists = globalCtx.GetFuncInfo(name); exists {
ownerCtx = globalCtx
}
} }
return return
} }
View File
+12 -3
View File
@@ -106,10 +106,19 @@ func (it *ListIterator) CallOperation(name string, args []any) (v any, err error
func (it *ListIterator) Current() (item any, err error) { func (it *ListIterator) Current() (item any, err error) {
a := *(it.a) a := *(it.a)
if it.index >= 0 && it.index <= it.stop { if it.start <= it.stop {
item = a[it.index] if it.stop < len(a) && it.index >= it.start && it.index <= it.stop {
item = a[it.index]
} else {
err = io.EOF
}
} else { } else {
err = io.EOF if it.start < len(a) && it.index >= it.stop && it.index <= it.start {
item = a[it.index]
} else {
err = io.EOF
}
} }
return return
} }
+1 -1
View File
@@ -34,7 +34,7 @@ type ExtIterator interface {
} }
func errNoOperation(name string) error { func errNoOperation(name string) error {
return fmt.Errorf("no %q function defined in the data-source", name) return fmt.Errorf("no %s() function defined in the data-source", name)
} }
func errInvalidDataSource() error { func errInvalidDataSource() error {
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-type.go
package expr
import (
"fmt"
"reflect"
"strings"
)
type ListType []any
func newListA(listAny ...any) (list *ListType) {
if listAny == nil {
listAny = []any{}
}
return newList(listAny)
}
func newList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
ls[i] = item
}
list = &ls
}
return
}
func (ls *ListType) ToString(opt FmtOpt) (s string) {
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
if opt&MultiLine != 0 {
sb.WriteString("\n ")
}
for i, item := range []any(*ls) {
if i > 0 {
if opt&MultiLine != 0 {
sb.WriteString(",\n ")
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if opt&MultiLine != 0 {
sb.WriteByte('\n')
}
}
sb.WriteByte(']')
s = sb.String()
if opt&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
return
}
func (ls *ListType) String() string {
return ls.ToString(0)
}
func (ls *ListType) TypeName() string {
return "list"
}
func (list *ListType) indexDeepCmp(target any) (index int) {
index = -1
for i, item := range *list {
if reflect.DeepEqual(item, target) {
index = i
break
}
}
return
}
func (ls *ListType) contains(t *ListType) (answer bool) {
if len(*ls) >= len(*t) {
answer = true
for _, item := range *t {
if answer = ls.indexDeepSameCmp(item) >= 0; !answer {
break
}
}
}
return
}
func (list *ListType) indexDeepSameCmp(target any) (index int) {
var eq bool
var err error
index = -1
for i, item := range *list {
if eq, err = deepSame(item, target, sameContent); err != nil {
break
} else if eq {
index = i
break
}
}
return
}
func sameContent(a, b any) (same bool, err error) {
la, _ := a.(*ListType)
lb, _ := b.(*ListType)
if len(*la) == len(*lb) {
same = true
for _, item := range *la {
if pos := lb.indexDeepSameCmp(item); pos < 0 {
same = false
break
}
}
}
return
}
func deepSame(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
if isNumOrFract(a) && isNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
eq = li == ri
} else {
eq = numAsFloat(a) == numAsFloat(b)
}
} else {
var cmp int
if cmp, err = cmpAnyFract(a, b); err == nil {
eq = cmp == 0
}
}
} else if deepCmp != nil && IsList(a) && IsList(b) {
eq, err = deepCmp(a, b)
} else {
eq = reflect.DeepEqual(a, b)
}
return
}
func (list *ListType) setItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*list)) {
(*list)[index] = value
} else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*list)-1)
}
return
}
-25
View File
@@ -30,31 +30,6 @@ func registerImport(name string, importFunc func(ExprContext), description strin
moduleRegister[name] = newModule(importFunc, description) moduleRegister[name] = newModule(importFunc, description)
} }
// func ImportInContext(ctx ExprContext, name string) (exists bool) {
// var mod *module
// if mod, exists = moduleRegister[name]; exists {
// mod.importFunc(ctx)
// mod.imported = true
// }
// return
// }
// func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
// var matched bool
// for name, mod := range moduleRegister {
// if matched, err = filepath.Match(pattern, name); err == nil {
// if matched {
// count++
// mod.importFunc(ctx)
// mod.imported = true
// }
// } else {
// break
// }
// }
// return
// }
func IterateModules(op func(name, description string, imported bool) bool) { func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil { if op != nil {
for name, mod := range moduleRegister { for name, mod := range moduleRegister {
-118
View File
@@ -4,124 +4,6 @@
// operand-dict.go // operand-dict.go
package expr package expr
import (
"fmt"
"reflect"
"strings"
)
type DictType map[any]any
func newDict(dictAny map[any]*term) (dict *DictType) {
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("{\n")
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(strings.Repeat("\t", indent+1))
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(MultiLine))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine != 0 {
dict.toMultiLine(&sb, 0)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", key))
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else if t, ok := value.(*term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return "dict"
}
func (dict *DictType) hasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
}
return
}
func (dict *DictType) merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
// -------- dict term // -------- dict term
func newDictTerm(args map[any]*term) *term { func newDictTerm(args map[any]*term) *term {
+29 -42
View File
@@ -22,16 +22,24 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) { func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists { if info, exists, owner := GetFuncInfo(ctx, name); exists {
if info.MinArgs() > len(params) { passedCount := len(*varParams)
err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params)) if info.MinArgs() > passedCount {
err = errTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
} }
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) { for i, p := range info.Params() {
err = errTooMuchParams(info.MaxArgs(), len(params)) if i >= passedCount {
if !p.IsOptional() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = errTooMuchParams(name, info.MaxArgs(), len(*varParams))
} }
if err == nil && owner != ctx { if err == nil && owner != ctx {
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
ctx.RegisterFuncInfo(info) ctx.RegisterFuncInfo(info)
} }
} else { } else {
@@ -43,8 +51,7 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) { func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx) ctx := cloneContext(parentCtx)
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
// fmt.Printf("Call %s(), context: %p\n", name, ctx) params := make([]any, len(self.children), len(self.children)+5)
params := make([]any, len(self.children))
for i, tree := range self.children { for i, tree := range self.children {
var param any var param any
if param, err = tree.compute(ctx); err != nil { if param, err = tree.compute(ctx); err != nil {
@@ -52,8 +59,9 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
} }
params[i] = param params[i] = param
} }
if err == nil { if err == nil {
if err = checkFunctionCall(ctx, name, params); err == nil { if err = checkFunctionCall(ctx, name, &params); err == nil {
if v, err = ctx.Call(name, params); err == nil { if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx) exportObjects(parentCtx, ctx)
} }
@@ -75,44 +83,23 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
} }
// -------- eval func def // -------- eval func def
// TODO
// type funcDefFunctor struct {
// params []string
// expr Expr
// }
// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
// for i, p := range funcDef.params {
// if i < len(args) {
// arg := args[i]
// if functor, ok := arg.(Functor); ok {
// // ctx.RegisterFunc(p, functor, 0, -1)
// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
// newFuncParam(paramValue),
// })
// } else {
// ctx.UnsafeSetVar(p, arg)
// }
// } else {
// ctx.UnsafeSetVar(p, nil)
// }
// }
// result, err = funcDef.expr.eval(ctx, false)
// return
// }
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) { func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value() bodySpec := self.value()
if expr, ok := bodySpec.(*ast); ok { if expr, ok := bodySpec.(*ast); ok {
paramList := make([]string, 0, len(self.children)) paramList := make([]ExprFuncParam, 0, len(self.children))
for _, param := range self.children { for _, param := range self.children {
paramList = append(paramList, param.source()) var defValue any
flags := paramFlags(0)
if len(param.children) > 0 {
flags |= pfOptional
if defValue, err = param.children[0].compute(ctx); err != nil {
return
}
}
info := newFuncParamFlagDef(param.source(), flags, defValue)
paramList = append(paramList, info)
} }
v = newExprFunctor(expr, paramList) v = newExprFunctor(expr, paramList, ctx)
// v = &funcDefFunctor{
// params: paramList,
// expr: expr,
// }
} else { } else {
err = errors.New("invalid function definition: the body specification must be an expression") err = errors.New("invalid function definition: the body specification must be an expression")
} }
-17
View File
@@ -148,23 +148,6 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
return return
} }
// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
// items := make(ListType, len(terms))
// for i, tree := range terms {
// var param any
// if i == 0 && firstChildValue != nil {
// param = firstChildValue
// } else if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// list = &items
// }
// return
// }
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) { func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
items := make([]any, 0, len(terms)) items := make([]any, 0, len(terms))
for i, tree := range terms { for i, tree := range terms {
+3 -80
View File
@@ -4,91 +4,14 @@
// operand-list.go // operand-list.go
package expr package expr
import (
"fmt"
"reflect"
"strings"
)
type ListType []any
func newListA(listAny ...any) (list *ListType) {
return newList(listAny)
}
func newList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
ls[i] = item
}
list = &ls
}
return
}
func (ls *ListType) ToString(opt FmtOpt) (s string) {
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
if opt&MultiLine != 0 {
sb.WriteString("\n ")
}
for i, item := range []any(*ls) {
if i > 0 {
if opt&MultiLine != 0 {
sb.WriteString(",\n ")
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if opt&MultiLine != 0 {
sb.WriteByte('\n')
}
}
sb.WriteByte(']')
s = sb.String()
if opt&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
return
}
func (ls *ListType) String() string {
return ls.ToString(0)
}
func (ls *ListType) TypeName() string {
return "list"
}
func (list *ListType) indexDeepCmp(target any) (index int) {
index = -1
for i, item := range *list {
if reflect.DeepEqual(item, target) {
index = i
break
}
}
return
}
// -------- list term // -------- list term
func newListTermA(args ...*term) *term { func newListTermA(args ...*term) *term {
return newListTerm(args) return newListTerm(0, 0, args)
} }
func newListTerm(args []*term) *term { func newListTerm(row, col int, args []*term) *term {
return &term{ return &term{
tk: *NewValueToken(0, 0, SymList, "[]", args), tk: *NewValueToken(row, col, SymList, "[]", args),
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
+1 -3
View File
@@ -9,9 +9,7 @@ import "fmt"
// -------- variable term // -------- variable term
func newVarTerm(tk *Token) *term { func newVarTerm(tk *Token) *term {
t := &term{ t := &term{
tk: *tk, tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
+55 -13
View File
@@ -16,14 +16,59 @@ func newAssignTerm(tk *Token) (inst *term) {
} }
} }
func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, value any) (err error) {
var collectionValue, keyListValue, keyValue any
var keyList *ListType
var ok bool
if collectionValue, err = collectionTerm.compute(ctx); err != nil {
return
}
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
return
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, typeName(keyListValue))
return
}
if keyValue = (*keyList)[0]; keyValue == nil {
err = keyListTerm.Errorf("index/key is nil")
return
}
switch collection := collectionValue.(type) {
case *ListType:
if index, ok := keyValue.(int64); ok {
err = collection.setItem(index, value)
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, typeName(keyValue))
}
case *DictType:
err = collection.setItem(keyValue, value)
default:
err = collectionTerm.Errorf("collection expected")
}
return
}
func assignValue(ctx ExprContext, leftTerm *term, v any) (err error) {
if leftTerm.symbol() == SymIndex {
err = assignCollectionItem(ctx, leftTerm.children[0], leftTerm.children[1], v)
} else {
ctx.UnsafeSetVar(leftTerm.source(), v)
}
return
}
func evalAssign(ctx ExprContext, self *term) (v any, err error) { func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if err = self.checkOperands(); err != nil { if err = self.checkOperands(); err != nil {
return return
} }
leftTerm := self.children[0] leftTerm := self.children[0]
if leftTerm.tk.Sym != SymVariable { leftSym := leftTerm.symbol()
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source) if leftSym != SymVariable && leftSym != SymIndex {
err = leftTerm.tk.Errorf("left operand of %q must be a variable or a collection's item", self.tk.source)
return return
} }
@@ -31,25 +76,22 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if v, err = rightChild.compute(ctx); err == nil { if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok { if functor, ok := v.(Functor); ok {
funcName := rightChild.source() if info := functor.GetFunc(); info != nil {
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
// ctx.RegisterFuncInfo(info)
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params()) ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok { } else if funcDef, ok := functor.(*exprFunctor); ok {
paramSpecs := ForAll(funcDef.params, newFuncParam) paramSpecs := ForAll(funcDef.params, func(p ExprFuncParam) ExprFuncParam { return p })
// paramCount := len(funcDef.params)
// paramSpecs := make([]ExprFuncParam, paramCount)
// for i := range paramSpecs {
// paramSpecs[i] = newFuncParam(funcDef.params[i])
// }
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs) ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs)
} else { } else {
err = self.Errorf("unknown function %s()", funcName) err = self.Errorf("unknown function %s()", rightChild.source())
} }
} else { } else {
ctx.UnsafeSetVar(leftTerm.source(), v) err = assignValue(ctx, leftTerm, v)
} }
} }
if err != nil {
v = nil
}
return return
} }
+6 -41
View File
@@ -4,8 +4,6 @@
// operator-dot.go // operator-dot.go
package expr package expr
import "fmt"
// -------- dot term // -------- dot term
func newDotTerm(tk *Token) (inst *term) { func newDotTerm(tk *Token) (inst *term) {
return &term{ return &term{
@@ -17,24 +15,6 @@ func newDotTerm(tk *Token) (inst *term) {
} }
} }
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
var v int
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 {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
}
return
}
func evalDot(ctx ExprContext, self *term) (v any, err error) { func evalDot(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
@@ -48,27 +28,8 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
indexTerm := self.children[1] indexTerm := self.children[1]
switch unboxedValue := leftValue.(type) { switch unboxedValue := leftValue.(type) {
case *ListType:
var index int
array := ([]any)(*unboxedValue)
if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil {
v = array[index]
}
case string:
var index int
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
v = string(unboxedValue[index])
}
case *DictType:
var ok bool
var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok {
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
case ExtIterator: case ExtIterator:
if indexTerm.symbol() == SymVariable { if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
opName := indexTerm.source() opName := indexTerm.source()
if unboxedValue.HasOperation(opName) { if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, []any{}) v, err = unboxedValue.CallOperation(opName, []any{})
@@ -76,9 +37,13 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
err = indexTerm.Errorf("this iterator do not support the %q command", opName) err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false v = false
} }
} else {
err = indexTerm.tk.ErrorExpectedGot("identifier")
} }
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) if rightValue, err = self.children[1].compute(ctx); err == nil {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
} }
return return
} }
+1 -355
View File
@@ -9,205 +9,8 @@ package expr
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"strconv"
"strings"
) )
type fraction struct {
num, den int64
}
func newFraction(num, den int64) *fraction {
/* if den < 0 {
den = -den
num = -num
}*/
num, den = simplifyIntegers(num, den)
return &fraction{num, den}
}
func float64ToFraction(f float64) (fract *fraction, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
// fmt.Printf("S: '%s'\n",s)
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
/*
func _float64ToFraction(f float64) (num, den int64, err error) {
const expMask = 1<<11 - 1
bits := math.Float64bits(f)
mantissa := bits & (1<<52 - 1)
exp := int((bits >> 52) & expMask)
switch exp {
case expMask: // non-finite
err = errors.New("infite")
return
case 0: // denormal
exp -= 1022
default: // normal
mantissa |= 1 << 52
exp -= 1023
}
shift := 52 - exp
// Optimization (?): partially pre-normalise.
for mantissa&1 == 0 && shift > 0 {
mantissa >>= 1
shift--
}
if f < 0 {
num = -int64(mantissa)
} else {
num = int64(mantissa)
}
den = int64(1)
if shift > 0 {
den = den << shift
} else {
num = num << (-shift)
}
return
}
*/
func makeGeneratingFraction(s string) (f *fraction, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2]
}
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *fraction) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *fraction) String() string {
return f.ToString(0)
}
func (f *fraction) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
} else {
var s, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
} else {
if len(s) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(s)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
}
return sb.String()
}
func (f *fraction) TypeName() string {
return "fraction"
}
// -------- fraction term // -------- fraction term
func newFractionTerm(tk *Token) *term { func newFractionTerm(tk *Token) *term {
return &term{ return &term{
@@ -252,168 +55,11 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
if den == 1 { if den == 1 {
v = num v = num
} else { } else {
v = &fraction{num, den} v = &FractionType{num, den}
} }
return return
} }
func gcd(a, b int64) (g int64) {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
a, b = b, a
}
r := a % b
for r > 0 {
a, b = b, r
r = a % b
}
g = b
return
}
func lcm(a, b int64) (l int64) {
g := gcd(a, b)
l = a * b / g
return
}
func sumFract(f1, f2 *fraction) (sum *fraction) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
func mulFract(f1, f2 *fraction) (prod *fraction) {
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
return
}
func anyToFract(v any) (f *fraction, err error) {
var ok bool
if f, ok = v.(*fraction); !ok {
if n, ok := v.(int64); ok {
f = intToFraction(n)
}
}
if f == nil {
err = errExpectedGot("fract", typeFraction, v)
}
return
}
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
if f1, err = anyToFract(v1); err != nil {
return
}
if f2, err = anyToFract(v2); err != nil {
return
}
return
}
func sumAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func subAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f2.num = -f2.num
f := sumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func mulAnyFract(af1, af2 any) (prod any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &fraction{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func divAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *fraction
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &fraction{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *fraction) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &fraction{f.num, f.den}
}
return v
}
func simplifyIntegers(num, den int64) (a, b int64) {
if num == 0 {
return 0, 1
}
if den == 0 {
panic("fraction with denominator == 0")
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
a = num / g
b = den / g
return
}
func intToFraction(n int64) *fraction {
return &fraction{n, 1}
}
func isFraction(v any) (ok bool) {
_, ok = v.(*fraction)
return ok
}
// init // init
func init() { func init() {
registerTermConstructor(SymVertBar, newFractionTerm) registerTermConstructor(SymVertBar, newFractionTerm)
+1 -1
View File
@@ -30,7 +30,7 @@ func evalIn(ctx ExprContext, self *term) (v any, err error) {
if IsList(rightValue) { if IsList(rightValue) {
list, _ := rightValue.(*ListType) list, _ := rightValue.(*ListType)
v = list.indexDeepCmp(leftValue) >= 0 v = list.indexDeepSameCmp(leftValue) >= 0
} else if IsDict(rightValue) { } else if IsDict(rightValue) {
dict, _ := rightValue.(*DictType) dict, _ := rightValue.(*DictType)
v = dict.hasKey(leftValue) v = dict.hasKey(leftValue)
+123
View File
@@ -0,0 +1,123 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-index.go
package expr
// -------- index term
func newIndexTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalIndex,
}
}
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
index = (*indexList)[0]
return
}
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int
if v, err = toInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue {
index = v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
return
}
func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a
endIndex = v.b
if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex
}
if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex + 1
}
if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
} else if endIndex < 0 || endIndex > maxValue {
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
} else if startIndex > endIndex {
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
}
return
}
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var indexList *ListType
var ok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
indexTerm := self.children[1]
if indexList, ok = rightValue.(*ListType); !ok {
err = self.Errorf("invalid index expression")
return
} else if len(*indexList) != 1 {
err = indexTerm.Errorf("one index only is allowed")
return
}
if IsInteger((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *ListType:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
v = (*unboxedValue)[index]
}
case string:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
v = string(unboxedValue[index])
}
case *DictType:
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
} else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *ListType:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
sublist := ListType((*unboxedValue)[start:end])
v = &sublist
}
case string:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
v = unboxedValue[start:end]
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
// init
func init() {
registerTermConstructor(SymIndex, newIndexTerm)
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-range.go
package expr
import "fmt"
// -------- range term
type intPair struct {
a, b int
}
func (p *intPair) TypeName() string {
return typePair
}
func (p *intPair) ToString(opt FmtOpt) string {
return fmt.Sprintf("(%d, %d)", p.a, p.b)
}
func isIntPair(v any) bool {
_, ok := v.(*intPair)
return ok
}
func newRangeTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalRange,
}
}
func evalRange(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
// if err = self.checkOperands(); err != nil {
// return
// }
if len(self.children) == 0 {
leftValue = int64(0)
rightValue = int64(-1)
} else if len(self.children) == 1 {
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
rightValue = int64(-1)
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
err = self.errIncompatibleTypes(leftValue, rightValue)
return
}
startIndex, _ := leftValue.(int64)
endIndex, _ := rightValue.(int64)
v = &intPair{int(startIndex), int(endIndex)}
return
}
// init
func init() {
registerTermConstructor(SymColon, newRangeTerm)
}
+94 -92
View File
@@ -4,13 +4,13 @@
// operator-rel.go // operator-rel.go
package expr package expr
import "reflect"
//-------- equal term //-------- equal term
func newEqualTerm(tk *Token) (inst *term) { func newEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, priority: priRelational,
@@ -18,6 +18,33 @@ func newEqualTerm(tk *Token) (inst *term) {
} }
} }
type deepFuncTemplate func(a, b any) (eq bool, err error)
func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
if isNumOrFract(a) && isNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
eq = li == ri
} else {
eq = numAsFloat(a) == numAsFloat(b)
}
} else {
var cmp int
if cmp, err = cmpAnyFract(a, b); err == nil {
eq = cmp == 0
}
}
} else if deepCmp != nil && IsList(a) && IsList(b) {
eq, err = deepCmp(a, b)
} else {
eq = reflect.DeepEqual(a, b)
}
return
}
func evalEqual(ctx ExprContext, self *term) (v any, err error) { func evalEqual(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
@@ -25,21 +52,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsNumber(leftValue) && IsNumber(rightValue) { v, err = equals(leftValue, rightValue, nil)
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) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls == rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return return
} }
@@ -47,9 +60,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
func newNotEqualTerm(tk *Token) (inst *term) { func newNotEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, priority: priRelational,
@@ -65,38 +76,11 @@ func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
// func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
// 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) {
// ls, _ := leftValue.(string)
// rs, _ := rightValue.(string)
// v = ls != rs
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
//-------- less term //-------- less term
func newLessTerm(tk *Token) (inst *term) { func newLessTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, priority: priRelational,
@@ -104,28 +88,44 @@ func newLessTerm(tk *Token) (inst *term) {
} }
} }
func lessThan(self *term, a, b any) (isLess bool, err error) {
if isNumOrFract(a) && isNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
isLess = li < ri
} else {
isLess = numAsFloat(a) < numAsFloat(b)
}
} else {
var cmp int
if cmp, err = cmpAnyFract(a, b); err == nil {
isLess = cmp < 0
}
}
} else if IsString(a) && IsString(b) {
ls, _ := a.(string)
rs, _ := b.(string)
isLess = ls < rs
// Inclusion test
} else if IsList(a) && IsList(b) {
aList, _ := a.(*ListType)
bList, _ := b.(*ListType)
isLess = len(*aList) < len(*bList) && bList.contains(aList)
} else {
err = self.errIncompatibleTypes(a, b)
}
return
}
func evalLess(ctx ExprContext, self *term) (v any, err error) { func evalLess(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return return
} }
v, err = lessThan(self, leftValue, 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) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls < rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return return
} }
@@ -141,6 +141,19 @@ func newLessEqualTerm(tk *Token) (inst *term) {
} }
} }
func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
if isLessEq, err = lessThan(self, a, b); err == nil {
if !isLessEq {
if IsList(a) && IsList(b) {
isLessEq, err = sameContent(a, b)
} else {
isLessEq, err = equals(a, b, nil)
}
}
}
return
}
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) { func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
@@ -148,21 +161,8 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if IsNumber(leftValue) && IsNumber(rightValue) { v, err = lessThanOrEqual(self, leftValue, 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) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls <= rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return return
} }
@@ -170,9 +170,7 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
func newGreaterTerm(tk *Token) (inst *term) { func newGreaterTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, priority: priRelational,
@@ -181,10 +179,13 @@ func newGreaterTerm(tk *Token) (inst *term) {
} }
func evalGreater(ctx ExprContext, self *term) (v any, err error) { func evalGreater(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalLessEqual(ctx, self); err == nil { var leftValue, rightValue any
b, _ := toBool(v)
v = !b if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
} }
v, err = lessThan(self, rightValue, leftValue)
return return
} }
@@ -192,9 +193,7 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) {
func newGreaterEqualTerm(tk *Token) (inst *term) { func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, priority: priRelational,
@@ -203,10 +202,13 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
} }
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) { func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalLess(ctx, self); err == nil { var leftValue, rightValue any
b, _ := toBool(v)
v = !b if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
} }
v, err = lessThanOrEqual(self, rightValue, leftValue)
return return
} }
-17
View File
@@ -38,23 +38,6 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
v = leftInt + rightInt v = leftInt + rightInt
} }
// } else if IsList(leftValue) || IsList(rightValue) {
// var leftList, rightList *ListType
// var ok bool
// if leftList, ok = leftValue.(*ListType); !ok {
// leftList = &ListType{leftValue}
// }
// if rightList, ok = rightValue.(*ListType); !ok {
// rightList = &ListType{rightValue}
// }
// sumList := make(ListType, 0, len(*leftList)+len(*rightList))
// for _, item := range *leftList {
// sumList = append(sumList, item)
// }
// for _, item := range *rightList {
// sumList = append(sumList, item)
// }
// v = &sumList
} else if IsList(leftValue) && IsList(rightValue) { } else if IsList(leftValue) && IsList(rightValue) {
var leftList, rightList *ListType var leftList, rightList *ListType
leftList, _ = leftValue.(*ListType) leftList, _ = leftValue.(*ListType)
+99 -106
View File
@@ -6,7 +6,6 @@ package expr
import ( import (
"errors" "errors"
"fmt"
) )
//-------- parser //-------- parser
@@ -23,34 +22,28 @@ func NewParser(ctx ExprContext) (p *parser) {
} }
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) { func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
// name, _ := tk.Value.(string)
// funcObj := self.ctx.GetFuncInfo(name)
// if funcObj == nil {
// err = fmt.Errorf("unknown function %s()", name)
// return
// }
// maxArgs := funcObj.MaxArgs()
// if maxArgs < 0 {
// maxArgs = funcObj.MinArgs() + 10
// }
// args := make([]*term, 0, maxArgs)
args := make([]*term, 0, 10) args := make([]*term, 0, 10)
itemExpected := false
lastSym := SymUnknown lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil { if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
if subTree.root != nil {
args = append(args, subTree.root)
}
} else {
break break
} }
prev := scanner.Previous()
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
err = prev.ErrorExpectedGot("function-param-value")
break
}
itemExpected = prev.Sym == SymComma
lastSym = scanner.Previous().Sym lastSym = scanner.Previous().Sym
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound { if lastSym != SymClosedRound {
err = errors.New("unterminate arguments list") err = errors.New("unterminated arguments list")
} else { } else {
tree = newFuncCallTerm(tk, args) tree = newFuncCallTerm(tk, args)
} }
@@ -58,62 +51,12 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
return return
} }
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// // Example: "add = func(x,y) {x+y}
// var body *ast
// args := make([]*term, 0)
// lastSym := SymUnknown
// itemExpected := false
// tk := scanner.Previous()
// for lastSym != SymClosedRound && lastSym != SymEos {
// var subTree *ast
// if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
// if subTree.root != nil {
// if subTree.root.symbol() == SymIdentifier {
// args = append(args, subTree.root)
// } else {
// err = tk.ErrorExpectedGotString("param-name", subTree.root.String())
// }
// } else if itemExpected {
// prev := scanner.Previous()
// err = prev.ErrorExpectedGot("function-param")
// break
// }
// } else {
// break
// }
// lastSym = scanner.Previous().Sym
// itemExpected = lastSym == SymComma
// }
// if err == nil && lastSym != SymClosedRound {
// err = tk.ErrorExpectedGot(")")
// }
// if err == nil {
// tk = scanner.Next()
// if tk.Sym == SymOpenBrace {
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
// } else {
// err = tk.ErrorExpectedGot("{")
// }
// }
// if err == nil {
// // TODO Check arguments
// if scanner.Previous().Sym != SymClosedBrace {
// err = scanner.Previous().ErrorExpectedGot("}")
// } else {
// tk = scanner.makeValueToken(SymExpression, "", body)
// tree = newFuncDefTerm(tk, args)
// }
// }
// return
// }
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) { func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y} // Example: "add = func(x,y) {x+y}
var body *ast var body *ast
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
defaultParamsStarted := false
itemExpected := false itemExpected := false
tk := scanner.Previous() tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
@@ -122,9 +65,20 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
param := newTerm(tk) param := newTerm(tk)
args = append(args, param) args = append(args, param)
tk = scanner.Next() tk = scanner.Next()
if tk.Sym == SymEqual {
var paramExpr *ast
defaultParamsStarted = true
if paramExpr, err = self.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
break
}
param.forceChild(paramExpr.root)
} else if defaultParamsStarted {
err = tk.Errorf("can't mix default and non-default parameters")
break
}
} else if itemExpected { } else if itemExpected {
prev := scanner.Previous() prev := scanner.Previous()
err = prev.ErrorExpectedGot("function-param") err = prev.ErrorExpectedGot("function-param-spec")
break break
} }
lastSym = scanner.Previous().Sym lastSym = scanner.Previous().Sym
@@ -143,7 +97,6 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
} }
} }
if err == nil { if err == nil {
// TODO Check arguments
if scanner.Previous().Sym != SymClosedBrace { if scanner.Previous().Sym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}") err = scanner.Previous().ErrorExpectedGot("}")
} else { } else {
@@ -154,15 +107,34 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
return return
} }
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) { func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
r, c := scanner.lastPos()
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
itemExpected := false itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos { for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast var subTree *ast
zeroRequired := scanner.current.Sym == SymColon
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil { if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil { root := subTree.root
args = append(args, subTree.root) if root != nil {
if !parsingIndeces && root.symbol() == SymColon {
err = root.Errorf("unexpected range expression")
break
}
args = append(args, root)
if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
if len(root.children) == 1 {
root.children = append(root.children, root.children[0])
} else if len(root.children) > 1 {
err = root.Errorf("invalid range specification")
break
}
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
zeroTerm := newTerm(zeroTk)
zeroTerm.setParent(root)
root.children[0] = zeroTerm
}
} else if itemExpected { } else if itemExpected {
prev := scanner.Previous() prev := scanner.Previous()
err = prev.ErrorExpectedGot("list-item") err = prev.ErrorExpectedGot("list-item")
@@ -175,11 +147,10 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
itemExpected = lastSym == SymComma itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedSquare { if lastSym != SymClosedSquare {
err = scanner.Previous().ErrorExpectedGot("]") err = scanner.Previous().ErrorExpectedGot("]")
} else { } else {
subtree = newListTerm(args) subtree = newListTerm(r, c, args)
} }
} }
return return
@@ -207,7 +178,6 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
itemExpected = lastSym == SymComma itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound { if lastSym != SymClosedRound {
err = scanner.Previous().ErrorExpectedGot(")") err = scanner.Previous().ErrorExpectedGot(")")
} else { } else {
@@ -234,7 +204,6 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
key = tk.Value key = tk.Value
} }
} else { } else {
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
err = tk.ErrorExpectedGot("dictionary-key or }") err = tk.ErrorExpectedGot("dictionary-key or }")
} }
return return
@@ -272,7 +241,6 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
itemExpected = lastSym == SymComma itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedBrace { if lastSym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}") err = scanner.Previous().ErrorExpectedGot("}")
} else { } else {
@@ -294,14 +262,14 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
err = tk.Errorf("case list in default clause") err = tk.Errorf("case list in default clause")
return return
} }
if filterList, err = self.parseList(scanner, allowVarRef); err != nil { if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
return return
} }
tk = scanner.Next() tk = scanner.Next()
startRow = tk.row startRow = tk.row
startCol = tk.col startCol = tk.col
} else if !defaultCase { } else if !defaultCase {
filterList = newListTerm(make([]*term, 0)) filterList = newListTerm(startRow, startCol, make([]*term, 0))
} }
if tk.Sym == SymOpenBrace { if tk.Sym == SymOpenBrace {
@@ -352,13 +320,21 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
return self.parseGeneral(scanner, true, false, termSymbols...) return self.parseGeneral(scanner, true, false, termSymbols...)
} }
func couldBeACollection(t *term) bool {
var sym = SymUnknown
if t != nil {
sym = t.symbol()
}
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
}
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) { func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil var selectorTerm *term = nil
var currentTerm *term = nil var currentTerm *term = nil
var tk *Token var tk *Token
tree = NewAst() tree = NewAst()
firstToken := true firstToken := true
lastSym := SymUnknown // lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() { for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
if tk.Sym == SymComment { if tk.Sym == SymComment {
continue continue
@@ -368,6 +344,8 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
if allowForest { if allowForest {
tree.ToForest() tree.ToForest()
firstToken = true firstToken = true
currentTerm = nil
selectorTerm = nil
continue continue
} else { } else {
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source) err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
@@ -401,20 +379,33 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} }
case SymOpenSquare: case SymOpenSquare:
var listTerm *term var listTerm *term
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil { parsingIndeces := couldBeACollection(currentTerm)
err = tree.addTerm(listTerm) if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
if parsingIndeces {
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
indexTerm := newTerm(indexTk)
if err = tree.addTerm(indexTerm); err == nil {
err = tree.addTerm(listTerm)
}
} else {
err = tree.addTerm(listTerm)
}
currentTerm = listTerm currentTerm = listTerm
} }
case SymOpenBrace: case SymOpenBrace:
var mapTerm *term if currentTerm != nil && currentTerm.symbol() == SymColon {
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil { err = currentTerm.Errorf(`selector-case outside of a selector context`)
err = tree.addTerm(mapTerm) } else {
currentTerm = mapTerm var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
} }
case SymEqual: case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil { // if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
} // }
case SymFuncDef: case SymFuncDef:
var funcDefTerm *term var funcDefTerm *term
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil { if funcDefTerm, err = self.parseFuncDef(scanner); err == nil {
@@ -439,14 +430,16 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} }
case SymColon, SymDoubleColon: case SymColon, SymDoubleColon:
var caseTerm *term var caseTerm *term
if selectorTerm == nil { if selectorTerm != nil {
err = tk.Errorf("selector-case outside of a selector context") if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil { addSelectorCase(selectorTerm, caseTerm)
addSelectorCase(selectorTerm, caseTerm) currentTerm = caseTerm
currentTerm = caseTerm if tk.Sym == SymDoubleColon {
if tk.Sym == SymDoubleColon { selectorTerm = nil
selectorTerm = nil }
} }
} else {
currentTerm, err = tree.addToken2(tk)
} }
default: default:
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
@@ -456,7 +449,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
selectorTerm = nil selectorTerm = nil
} }
lastSym = tk.Sym // lastSym = tk.Sym
} }
if err == nil { if err == nil {
err = tk.Error() err = tk.Error()
@@ -464,9 +457,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
return return
} }
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) { // func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
if lastSym != wantedSym { // if lastSym != wantedSym {
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source) // err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
} // }
return // return
} // }
+8
View File
@@ -74,6 +74,14 @@ func (self *scanner) unreadChar() (err error) {
return return
} }
func (self *scanner) lastPos() (r, c int) {
if self.prev != nil {
r = self.prev.row
c = self.prev.col
}
return
}
func (self *scanner) Previous() *Token { func (self *scanner) Previous() *Token {
return self.prev return self.prev
} }
-244
View File
@@ -1,244 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import (
"fmt"
"slices"
"strings"
)
type SimpleFuncStore struct {
SimpleVarStore
funcStore map[string]*funcInfo
}
type paramFlags uint16
const (
pfOptional paramFlags = 1 << iota
pfRepeat
)
type funcParamInfo struct {
name string
flags paramFlags
defaultValue any
}
func newFuncParam(name string) *funcParamInfo {
return &funcParamInfo{name: name}
}
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags}
}
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return "any"
}
func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
return param.defaultValue
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
returnType string
}
func (info *funcInfo) Params() []ExprFuncParam {
return info.params
}
func (info *funcInfo) ReturnType() string {
return info.returnType
}
func (info *funcInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.Name())
if p.IsOptional() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
}
}
}
}
if info.maxArgs < 0 {
sb.WriteString(" ...")
}
sb.WriteString(") -> ")
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(typeAny)
}
return sb.String()
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
func NewSimpleFuncStore() *SimpleFuncStore {
ctx := &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
//ImportBuiltinsFuncs(ctx)
return ctx
}
func (ctx *SimpleFuncStore) Clone() ExprContext {
svs := ctx.SimpleVarStore
return &SimpleFuncStore{
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}\n")
}
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
// }
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
var minArgs = 0
var maxArgs = 0
if params != nil {
for _, p := range params {
if maxArgs == -1 {
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
// } else if p.IsRepeat() {
// maxArgs = -1
// continue
}
if p.IsOptional() {
maxArgs++
} else if maxArgs == minArgs {
minArgs++
maxArgs++
} else {
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
}
if p.IsRepeat() {
maxArgs = -1
}
}
}
ctx.funcStore[name] = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
}
return nil
}
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
funcNames = make([]string, 0)
for name := range ctx.funcStore {
if acceptor != nil {
if acceptor(name) {
funcNames = append(funcNames, name)
}
} else {
funcNames = append(funcNames, name)
}
}
return
}
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists {
functor := info.functor
result, err = functor.Invoke(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+19 -10
View File
@@ -12,29 +12,40 @@ import (
type SimpleStore struct { type SimpleStore struct {
varStore map[string]any varStore map[string]any
funcStore map[string]*funcInfo funcStore map[string]ExprFunc
} }
func NewSimpleStore() *SimpleStore { func NewSimpleStore() *SimpleStore {
ctx := &SimpleStore{ ctx := &SimpleStore{
varStore: make(map[string]any), varStore: make(map[string]any),
funcStore: make(map[string]*funcInfo), funcStore: make(map[string]ExprFunc),
} }
//ImportBuiltinsFuncs(ctx)
return ctx return ctx
} }
func filterRefName(name string) bool { return name[0] != '@' }
func filterPrivName(name string) bool { return name[0] != '_' }
func (ctx *SimpleStore) Clone() ExprContext { func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{ return &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' }), varStore: CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }), funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
}
}
func (ctx *SimpleStore) Merge(src ExprContext) {
for _, name := range src.EnumVars(filterRefName) {
ctx.varStore[name], _ = src.GetVar(name)
}
for _, name := range src.EnumFuncs(filterRefName) {
ctx.funcStore[name], _ = src.GetFuncInfo(name)
} }
} }
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) { func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n") sb.WriteString("vars: {\n")
first := true first := true
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) { for _, name := range ctx.EnumVars(filterPrivName) {
if first { if first {
first = false first = false
} else { } else {
@@ -80,8 +91,6 @@ func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
} }
value, _ := ctx.GetFuncInfo(name) value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1)) sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok { if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0)) sb.WriteString(formatter.ToString(0))
} else { } else {
@@ -163,8 +172,8 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
} }
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) { func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists { if info, exists := GetLocalFuncInfo(ctx, name); exists {
functor := info.functor functor := info.Functor()
result, err = functor.Invoke(ctx, name, args) result, err = functor.Invoke(ctx, name, args)
} else { } else {
err = fmt.Errorf("unknown function %s()", name) err = fmt.Errorf("unknown function %s()", name)
-126
View File
@@ -1,126 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-var-store.go
package expr
import (
"fmt"
"strings"
)
type SimpleVarStore struct {
varStore map[string]any
}
func NewSimpleVarStore() *SimpleVarStore {
return &SimpleVarStore{
varStore: make(map[string]any),
}
}
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
}
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
// fmt.Println("*** Cloning context ***")
clone = &SimpleVarStore{
varStore: ctx.cloneVars(),
}
return clone
}
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
v, exists = ctx.varStore[varName]
return
}
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value
}
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue
} else {
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
}
}
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
varNames = make([]string, 0)
for name := range ctx.varStore {
if acceptor != nil {
if acceptor(name) {
varNames = append(varNames, name)
}
} else {
varNames = append(varNames, name)
}
}
return
}
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
return
}
func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) {
return
}
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// }
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
}
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
return nil
}
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
return
}
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}\n")
}
func varsCtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, indent)
return sb.String()
}
func (ctx *SimpleVarStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
+1
View File
@@ -85,6 +85,7 @@ const (
SymFuncDef SymFuncDef
SymList SymList
SymDict SymDict
SymIndex
SymExpression SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>] SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}" SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// ast_test.go // t_ast_test.go
package expr package expr
import ( import (
+5 -2
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// dict_test.go // t_dict_test.go
package expr package expr
import ( import (
@@ -24,10 +24,13 @@ func TestDictParser(t *testing.T) {
/* 1 */ {`{}`, map[any]any{}, nil}, /* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)}, /* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil}, /* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil}, /* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil}, /* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil}, /* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil}, /* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
} }
succeeded := 0 succeeded := 0
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_expr_test.go
package expr
import (
"testing"
)
func TestExpr(t *testing.T) {
section := "Expr"
inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`builtin "os.file"; f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
};
it=$(ds,3);
it++;
it++
`, int64(1), nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3)
parserTest(t, section, inputs)
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_fractions_test.go
package expr
import (
"errors"
"testing"
)
func TestFractionsParser(t *testing.T) {
section := "Fraction"
inputs := []inputType{
/* 1 */ {`1|2`, newFraction(1, 2), nil},
/* 2 */ {`1|2 + 1`, newFraction(3, 2), nil},
/* 3 */ {`1|2 - 1`, newFraction(-1, 2), nil},
/* 4 */ {`1|2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 7 */ {`1|"5"`, nil, errors.New(`denominator must be integer, got string (5)`)},
/* 8 */ {`"1"|5`, nil, errors.New(`numerator must be integer, got string (1)`)},
/* 9 */ {`1|+5`, nil, errors.New(`[1:3] infix operator "|" requires two non-nil operands, got 1`)},
/* 10 */ {`1|(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
/* 12 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1|0`, nil, errors.New(`division by zero`)},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
/* 17 */ {`fract("")`, (*FractionType)(nil), errors.New(`bad syntax`)},
/* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
/* 19 */ {`fract("+1")`, newFraction(1, 1), nil},
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
}
parserTest(t, section, inputs)
}
func TestFractionToStringSimple(t *testing.T) {
source := newFraction(1, 2)
want := "1|2"
got := source.ToString(0)
if got != want {
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
func TestFractionToStringMultiline(t *testing.T) {
source := newFraction(1, 2)
want := "1\n-\n2"
got := source.ToString(MultiLine)
if got != want {
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
// TODO Check this test: the output string ends with a space
func _TestToStringMultilineTty(t *testing.T) {
source := newFraction(-1, 2)
want := "\x1b[4m-1\x1b[0m\n2"
got := source.ToString(MultiLine | TTY)
if got != want {
t.Errorf(`(1,2) -> result = %#v [%T], want = %#v [%T]`, got, got, want, want)
}
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-base_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncBase(t *testing.T) {
section := "Func-Base"
inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil},
/* 2 */ {`v=nil; isNil(v)`, true, nil},
/* 3 */ {`v=5; isNil(v)`, false, nil},
/* 4 */ {`int(true)`, int64(1), nil},
/* 5 */ {`int(false)`, int64(0), nil},
/* 6 */ {`int(3.1)`, int64(3), nil},
/* 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(): too much params -- expected 1, got 2`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert nil to int`)},
/* 12 */ {`isInt(2+1)`, true, nil},
/* 13 */ {`isInt(3.1)`, false, nil},
/* 14 */ {`isFloat(3.1)`, true, nil},
/* 15 */ {`isString("3.1")`, true, nil},
/* 16 */ {`isString("3" + 1)`, true, nil},
/* 17 */ {`isList(["3", 1])`, true, nil},
/* 18 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 19 */ {`isFract(1|3)`, true, nil},
/* 20 */ {`isFract(3|1)`, false, nil},
/* 21 */ {`isRational(3|1)`, true, nil},
/* 22 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 23 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
/* 24 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
/* 25 */ {`fract(1.21)`, newFraction(121, 100), nil},
/* 26 */ {`dec(2)`, float64(2), nil},
/* 27 */ {`dec(2.0)`, float64(2), nil},
/* 28 */ {`dec("2.0")`, float64(2), nil},
/* 29 */ {`dec(true)`, float64(1), nil},
/* 30 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 31 */ {`dec()`, nil, errors.New(`dec(): too few params -- expected 1, got 0`)},
/* 32 */ {`dec(1,2,3)`, nil, errors.New(`dec(): too much params -- expected 1, got 3`)},
/* 33 */ {`isBool(false)`, true, nil},
/* 34 */ {`fract(1|2)`, newFraction(1, 2), nil},
/* 35 */ {`fract(12,2)`, newFraction(6, 1), nil},
/* 36 */ {`bool(2)`, true, nil},
/* 37 */ {`bool(1|2)`, true, nil},
/* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, errors.New(`bool(): can't convert list to bool`)},
/* 42 */ {`dec(false)`, float64(0), nil},
/* 43 */ {`dec(1|2)`, float64(0.5), nil},
/* 44 */ {`dec([1])`, nil, errors.New(`dec(): can't convert list to float`)},
// /* 45 */ {`string([1])`, nil, errors.New(`string(): can't convert list to string`)},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 2)
parserTest(t, section, inputs)
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-import_test.go
package expr
import (
"testing"
)
func TestFuncImport(t *testing.T) {
section := "Func-Import"
inputs := []inputType{
/* 1 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 3 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
/* 4 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-math-arith_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncMathArith(t *testing.T) {
section := "Func-Math-Arith"
inputs := []inputType{
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
/* 3 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 4 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 5 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-os_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncOs(t *testing.T) {
section := "Func-OS"
inputs := []inputType{
/* 1 */ {`builtin "os.file"`, int64(1), nil},
/* 2 */ {`builtin "os.file"; handle=openFile("/etc/hosts"); closeFile(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=openFile("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 4 */ {`builtin "os.file"; handle=createFile("/tmp/dummy"); closeFile(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=appendFile("/tmp/dummy"); writeFile(handle, "bye-bye"); closeFile(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=openFile("/tmp/dummy"); word=readFile(handle, "-"); closeFile(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=readFile(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; writeFile(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-string_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncString(t *testing.T) {
section := "Func-String"
inputs := []inputType{
/* 1 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a integer (1)`)},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got integer (2)`)},
/* 6 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
/* 8 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
/* 9 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
/* 10 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
/* 11 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
/* 12 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 13 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`startsWithStr(): too few params -- expected 2 or more, got 1`)},
/* 14 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
/* 15 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 16 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`endsWithStr(): too few params -- expected 2 or more, got 1`)},
/* 17 */ {`builtin "string"; splitStr("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got integer (1)`)},
/* 19 */ {`builtin "string"; joinStr()`, nil, errors.New(`joinStr(): too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
//t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 19)
parserTest(t, section, inputs)
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_funcs_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncs(t *testing.T) {
section := "Funcs"
inputs := []inputType{
/* 1 */ {`two=func(){2}; two()`, int64(2), nil},
/* 2 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
/* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 4 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 5 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 6 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 7 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 8 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 9 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 10 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil},
/* 14 */ {`two=func(){2}; two(123)`, nil, errors.New(`two(): too much params -- expected 0, got 1`)},
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
}
func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func TestFunctionToStringSimple(t *testing.T) {
source := newGolangFunctor(dummy)
want := "func() {<body>}"
got := source.ToString(0)
if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
func TestFunctionGetFunc(t *testing.T) {
source := newGolangFunctor(dummy)
want := ExprFunc(nil)
got := source.GetFunc()
if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
+1 -4
View File
@@ -1,10 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // t_graph_test.go
// All rights reserved.
// graph_test.go
package expr package expr
import ( import (
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// helpers_test.go // t_helpers_test.go
package expr package expr
import ( import (
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_index_test.go
package expr
import (
"errors"
"testing"
)
func TestCollections(t *testing.T) {
section := "Collection"
inputs := []inputType{
/* 1 */ {`"abcdef"[1:3]`, "bc", nil},
/* 2 */ {`"abcdef"[:3]`, "abc", nil},
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
/* 5 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5)
parserTest(t, section, inputs)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// iterator_test.go // t_iterator_test.go
package expr package expr
import "testing" import "testing"
+39 -24
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// list_test.go // t_list_test.go
package expr package expr
import ( import (
@@ -13,41 +13,58 @@ import (
func TestListParser(t *testing.T) { func TestListParser(t *testing.T) {
section := "List" section := "List"
type inputType struct { /* type inputType struct {
source string source string
wantResult any wantResult any
wantErr error wantErr error
} }
*/
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil}, /* 1 */ {`[]`, newListA(), nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 2 */ {`[1,2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil}, /* 3 */ {`[1,2,"hello"]`, newListA(int64(1), int64(2), "hello"), nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil}, /* 4 */ {`[1+2, not true, "hello"]`, newListA(int64(3), false, "hello"), nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 5 */ {`[1,2]+[3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil}, /* 6 */ {`[1,4,3,2]-[3]`, newListA(int64(1), int64(4), int64(2)), nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil}, /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],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}, /* 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`)}, /* 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}, /* 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}, /* 12 */ {`[1,2,3] << 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 14 */ {`[1,2,3].1`, int64(2), nil}, /* 14 */ {`[1,2,3][1]`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.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}, /* 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`)}, /* 17 */ {`list=["one","two","three"]; list[10]`, nil, errors.New(`[1:34] index 10 out of bounds`)},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil}, /* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil}, /* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil}, /* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil}, /* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil}, /* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil}, /* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil},
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
/* 26 */ {`[0,1,2,3,4][:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 27 */ {`["a", "b", "c"] << ;`, nil, errors.New(`[1:18] infix operator "<<" requires two non-nil operands, got 1`)},
/* 28 */ {`2 << 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator "<<"`)},
/* 29 */ {`but >> ["a", "b", "c"]`, nil, errors.New(`[1:6] infix operator ">>" requires two non-nil operands, got 0`)},
/* 30 */ {`2 >> 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`)},
/* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 33 */ {`a=[1,2]; 5>>a`, newListA(int64(5), int64(1), int64(2)), nil},
/* 34 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, errors.New(`index 5 out of bounds (0, 1)`)},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, errors.New(`[1:12] index/key specification expected, got [] [list]`)},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is nil`)},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, 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}, // /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
} }
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
return
succeeded := 0 succeeded := 0
failed := 0 failed := 0
@@ -61,8 +78,6 @@ func TestListParser(t *testing.T) {
var gotErr error var gotErr error
ctx := NewSimpleStore() ctx := NewSimpleStore()
// ctx.SetVar("var1", int64(123))
// ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx) ImportMathFuncs(ctx)
parser := NewParser(ctx) parser := NewParser(ctx)
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_module-register_test.go
package expr
import (
"testing"
)
func TestIterateModules(t *testing.T) {
section := "Module-Register"
mods := make([]string, 0, 100)
IterateModules(func(name, description string, imported bool) bool {
mods = append(mods, name)
return true
})
t.Logf("%s -- IterateModules(): %v", section, mods)
if len(mods) == 0 {
t.Errorf("Module-List: got-length zero, expected-length greater than zero")
}
// IterateModules(func(name, description string, imported bool) bool {
// return false
// })
// if len(mods) > 0 {
// t.Errorf("Module-List: got-length greater than zero, expected-length zero")
// }
}
+12 -28
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// parser_test.go // t_parser_test.go
package expr package expr
import ( import (
@@ -18,6 +18,8 @@ type inputType struct {
} }
func TestGeneralParser(t *testing.T) { func TestGeneralParser(t *testing.T) {
section := "Parser"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`1+/*5*/2`, int64(3), nil}, /* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil}, /* 2 */ {`3 == 4`, false, nil},
@@ -115,8 +117,8 @@ func TestGeneralParser(t *testing.T) {
/* 94 */ {`false or true`, true, nil}, /* 94 */ {`false or true`, true, nil},
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)}, /* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
/* 96 */ {`a=5; a`, int64(5), nil}, /* 96 */ {`a=5; a`, int64(5), nil},
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)}, /* 97 */ {`2=5`, nil, errors.New(`[1:2] left operand of "=" must be a variable or a collection's item`)},
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)}, /* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable or a collection's item`)},
/* 99 */ {`2+(a=5)`, int64(7), nil}, /* 99 */ {`2+(a=5)`, int64(7), nil},
/* 100 */ {`x ?? "default"`, "default", nil}, /* 100 */ {`x ?? "default"`, "default", nil},
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil}, /* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
@@ -138,33 +140,15 @@ func TestGeneralParser(t *testing.T) {
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)}, /* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)}, /* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
/* 119 */ {`{}`, &DictType{}, nil}, /* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`1|2`, newFraction(1, 2), nil}, /* 120 */ {`v=10; v++; v`, int64(11), nil},
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil}, /* 121 */ {`1+1|2+0.5`, float64(2), nil},
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil}, /* 122 */ {`1.2()`, newFraction(6, 5), nil},
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil}, /* 123 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
/* 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},
/* 140 */ {`1.2()`, newFraction(6, 5), nil},
/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "General", inputs, 102) // parserTestSpec(t, section, inputs, 102)
parserTest(t, "General", inputs) parserTest(t, section, inputs)
} }
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) { func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
@@ -219,7 +203,7 @@ func doTest(t *testing.T, section string, input *inputType, count int) (good boo
eq := reflect.DeepEqual(gotResult, input.wantResult) eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ { if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%T], want = %v [%T]", count, input.source, gotResult, gotResult, input.wantResult, input.wantResult) t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, typeName(gotResult), input.wantResult, typeName(input.wantResult))
good = false good = false
} }
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_template_test.go
package expr
import (
"testing"
)
func TestRelational(t *testing.T) {
section := "Relational"
inputs := []inputType{
/* 1 */ {`1 == 3-2`, true, nil},
/* 2 */ {`"a" == "a"`, true, nil},
/* 3 */ {`true == false`, false, nil},
/* 4 */ {`1.0 == 3.0-2`, true, nil},
/* 5 */ {`[1,2] == [2,1]`, false, nil},
/* 6 */ {`1 != 3-2`, false, nil},
/* 7 */ {`"a" != "a"`, false, nil},
/* 8 */ {`true != false`, true, nil},
/* 9 */ {`1.0 != 3.0-2`, false, nil},
/* 10 */ {`[1,2] != [2,1]`, true, nil},
/* 11 */ {`1|2 == 1|3`, false, nil},
/* 12 */ {`1|2 != 1|3`, true, nil},
/* 13 */ {`1|2 == 4|8`, true, nil},
/* 14 */ {`1 < 8`, true, nil},
/* 15 */ {`1 <= 8`, true, nil},
/* 16 */ {`"a" < "b"`, true, nil},
/* 17 */ {`"a" <= "b"`, true, nil},
/* 18 */ {`1.0 < 8`, true, nil},
/* 19 */ {`1.0 <= 8`, true, nil},
/* 20 */ {`1.0 <= 1.0`, true, nil},
/* 21 */ {`1.0 == 1`, true, nil},
/* 22 */ {`1|2 < 1|3`, false, nil},
/* 23 */ {`1|2 <= 1|3`, false, nil},
/* 24 */ {`1|2 > 1|3`, true, nil},
/* 25 */ {`1|2 >= 1|3`, true, nil},
/* 26 */ {`[1,2,3] > [2]`, true, nil},
/* 27 */ {`[1,2,3] > [9]`, false, nil},
/* 28 */ {`[1,2,3] >= [6]`, false, nil},
/* 29 */ {`[1,2,3] >= [2,6]`, false, nil},
/* 30 */ {`[1,[2,3],4] > [[3,2]]`, true, nil},
/* 31 */ {`[1,[2,[3,4]],5] > [[[4,3],2]]`, true, nil},
/* 32 */ {`[[4,3],2] IN [1,[2,[3,4]],5]`, true, nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 31)
parserTest(t, section, inputs)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// scanner_test.go // t_scanner_test.go
package expr package expr
import ( import (
+3 -3
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// strings_test.go // t_strings_test.go
package expr package expr
import ( import (
@@ -14,8 +14,8 @@ func TestStringsParser(t *testing.T) {
/* 2 */ {`"uno" + 2`, `uno2`, nil}, /* 2 */ {`"uno" + 2`, `uno2`, nil},
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil}, /* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil}, /* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc".1`, `b`, nil}, /* 5 */ {`"abc"[1]`, `b`, nil},
/* 5 */ {`#"abc"`, int64(3), nil}, /* 6 */ {`#"abc"`, int64(3), nil},
} }
parserTest(t, "String", inputs) parserTest(t, "String", inputs)
} }
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_template_test.go
package expr
import (
"testing"
)
func TestSomething(t *testing.T) {
section := "Something"
inputs := []inputType{
/* 1 */ {`1`, int64(1), nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 1)
parserTest(t, section, inputs)
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// term_test.go // t_term_test.go
package expr package expr
import ( import (
+1 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// token_test.go // t_token_test.go
package expr package expr
import ( import (
+18 -1
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// utils_test.go // t_utils_test.go
package expr package expr
import ( import (
@@ -54,6 +54,14 @@ func TestIsString(t *testing.T) {
succeeded++ succeeded++
} }
count++
if isIterator("fake") {
t.Errorf(`%d: isIterator("fake") -> result = true, want false`, count)
failed++
} else {
succeeded++
}
count++ count++
b, ok := toBool(true) b, ok := toBool(true)
if !(ok && b) { if !(ok && b) {
@@ -72,6 +80,15 @@ func TestIsString(t *testing.T) {
succeeded++ succeeded++
} }
count++
b, ok = toBool([]int{})
if ok {
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
failed++
} else {
succeeded++
}
t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed) t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
} }
+15 -5
View File
@@ -12,6 +12,7 @@ type termPriority uint32
const ( const (
priNone termPriority = iota priNone termPriority = iota
priRange
priBut priBut
priAssign priAssign
priOr priOr
@@ -155,15 +156,15 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
if index64, ok := computedValue.(int64); ok { if index64, ok := computedValue.(int64); ok {
i = int(index64) i = int(index64)
} else { } else {
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue) err = self.Errorf("%s, got %s (%v)", valueDescription, typeName(computedValue), computedValue)
} }
return return
} }
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error { func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
leftType := getTypeName(leftValue) leftType := typeName(leftValue)
leftText := getFormatted(leftValue, Truncate) leftText := getFormatted(leftValue, Truncate)
rightType := getTypeName(rightValue) rightType := typeName(rightValue)
rightText := getFormatted(rightValue, Truncate) rightText := getFormatted(rightValue, Truncate)
return self.tk.Errorf( return self.tk.Errorf(
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q", "left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
@@ -174,8 +175,8 @@ func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
func (self *term) errIncompatibleType(value any) error { func (self *term) errIncompatibleType(value any) error {
return self.tk.Errorf( return self.tk.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%T]", "prefix/postfix operator %q do not support operand '%v' [%s]",
self.source(), value, value) self.source(), value, typeName(value))
} }
func (self *term) Errorf(template string, args ...any) (err error) { func (self *term) Errorf(template string, args ...any) (err error) {
@@ -228,3 +229,12 @@ func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
} }
return return
} }
// NOTE Temporary solution to support function parameters with default value
func (self *term) forceChild(c *term) {
if self.children == nil {
self.children = make([]*term, 0, 1)
}
self.children = append(self.children, c)
}
+6 -4
View File
@@ -40,12 +40,12 @@ func IsDict(v any) (ok bool) {
} }
func IsFract(v any) (ok bool) { func IsFract(v any) (ok bool) {
_, ok = v.(*fraction) _, ok = v.(*FractionType)
return ok return ok
} }
func IsRational(v any) (ok bool) { func IsRational(v any) (ok bool) {
if _, ok = v.(*fraction); !ok { if _, ok = v.(*FractionType); !ok {
_, ok = v.(int64) _, ok = v.(int64)
} }
return ok return ok
@@ -76,7 +76,7 @@ func isIterator(v any) (ok bool) {
func numAsFloat(v any) (f float64) { func numAsFloat(v any) (f float64) {
var ok bool var ok bool
if f, ok = v.(float64); !ok { if f, ok = v.(float64); !ok {
if fract, ok := v.(*fraction); ok { if fract, ok := v.(*FractionType); ok {
f = fract.toFloat() f = fract.toFloat()
} else { } else {
i, _ := v.(int64) i, _ := v.(int64)
@@ -204,8 +204,10 @@ func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (a
func toInt(value any, description string) (i int, err error) { func toInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok { if valueInt64, ok := value.(int64); ok {
i = int(valueInt64) i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else { } else {
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value) err = fmt.Errorf("%s expected integer, got %s (%v)", description, typeName(value), value)
} }
return return
} }