Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8144122d2c | |||
| 188ea354ee | |||
| 2fc6bbfe10 | |||
| 847d85605e | |||
| 9c29392389 | |||
| a7b6e6f8d2 | |||
| a16ac70e4a | |||
| ab2e3f0528 | |||
| 974835a8ef | |||
| 457a656073 | |||
| 9e63e1402e | |||
| e4ded4f746 | |||
| 4e3af837e6 | |||
| ca12722c93 | |||
| d96123ab02 | |||
| f2d6d63017 | |||
| 905b2af7fa | |||
| 9307473d08 | |||
| 10a596a4cd | |||
| 609fb21505 | |||
| 7650a4a441 | |||
| f51d6023ae | |||
| 99454227d5 | |||
| 75358e5d35 | |||
| 51b272dda8 | |||
| 7f282e5460 | |||
| cff21b40f7 | |||
| 4f432da2b9 | |||
| e4b4b4fb79 | |||
| c04678c426 | |||
| 9bba40f155 | |||
| f66cd1fdb1 | |||
| f41ea96d17 | |||
| d84e690ef3 | |||
| 4b25a07699 | |||
| 3736214c5a | |||
| 78cbb7b36f | |||
| 2c87d6bf9e | |||
| 691c213d17 | |||
| fa136cb70b | |||
| 76dd01afcd | |||
| 4283fab816 | |||
| 03d4c192c2 | |||
| e5f63c3d9d | |||
| d545a35acf | |||
| e4275e2cb6 | |||
| 1ff5770264 | |||
| ba32e2dccf | |||
| f22b5a6f0b | |||
| 7c8dbb0ac7 | |||
| e5c5920db0 | |||
| 61efdb4eef | |||
| 82ec78719d | |||
| 554ff1a9dd | |||
| 6bb891e09d | |||
| 1c4ffd7d64 | |||
| b92b19e1dd | |||
| 9967918418 | |||
| 6c14c07d66 | |||
| 9ea170e53b | |||
| a543360151 | |||
| 24a25bbf94 | |||
| d6a1607041 | |||
| 4d43ab2c2f | |||
| 9bd4a0ba23 | |||
| 2b184cf3f2 | |||
| 263e419d9a | |||
| c39970fa7e | |||
| 14bb9e942b | |||
| 9451958218 | |||
| 91fdc1926e | |||
| 9a3abdf1b6 | |||
| ac3e690f87 |
@@ -119,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
if self.forest != nil {
|
if self.forest != nil {
|
||||||
for _, root := range self.forest {
|
for _, root := range self.forest {
|
||||||
if result, err = root.compute(ctx); err == nil {
|
if result, err = root.compute(ctx); err == nil {
|
||||||
ctx.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
} else {
|
} else {
|
||||||
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
||||||
break
|
break
|
||||||
@@ -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.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// } else {
|
// } else {
|
||||||
// err = errors.New("empty expression")
|
// err = errors.New("empty expression")
|
||||||
|
|||||||
+15
-15
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,17 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
paramCount = "count"
|
||||||
|
paramItem = "item"
|
||||||
paramParts = "parts"
|
paramParts = "parts"
|
||||||
paramSeparator = "separator"
|
paramSeparator = "separator"
|
||||||
paramSource = "source"
|
paramSource = "source"
|
||||||
|
paramSuffix = "suffix"
|
||||||
|
paramPrefix = "prefix"
|
||||||
|
paramStart = "start"
|
||||||
|
paramEnd = "end"
|
||||||
|
paramValue = "value"
|
||||||
|
paramEllipsis = "..."
|
||||||
|
paramFilepath = "filepath"
|
||||||
|
paramDirpath = "dirpath"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,10 +5,14 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
typeAny = "any"
|
||||||
typeBoolean = "boolean"
|
typeBoolean = "boolean"
|
||||||
typeFloat = "decimal"
|
typeFloat = "float"
|
||||||
typeFraction = "fraction"
|
typeFraction = "fraction"
|
||||||
|
typeHandle = "handle"
|
||||||
typeInt = "integer"
|
typeInt = "integer"
|
||||||
|
typeItem = "item"
|
||||||
typeNumber = "number"
|
typeNumber = "number"
|
||||||
|
typePair = "pair"
|
||||||
typeString = "string"
|
typeString = "string"
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-2
@@ -15,14 +15,16 @@ func exportVar(ctx ExprContext, name string, value any) {
|
|||||||
if name[0] == '@' {
|
if name[0] == '@' {
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
}
|
}
|
||||||
ctx.setVar(name, value)
|
ctx.UnsafeSetVar(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||||
if name[0] == '@' {
|
if name[0] == '@' {
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
}
|
}
|
||||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||||
|
// ctx.RegisterFuncInfo(name, info)
|
||||||
|
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
|
|||||||
+18
-11
@@ -4,39 +4,46 @@
|
|||||||
// context.go
|
// context.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
// ---- Function template
|
|
||||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
|
||||||
|
|
||||||
// ---- Functor interface
|
// ---- Functor interface
|
||||||
type Functor interface {
|
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)
|
||||||
|
GetFunc() ExprFunc
|
||||||
|
GetParams() []ExprFuncParam
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleFunctor struct {
|
// ---- Function Param Info
|
||||||
f FuncTemplate
|
type ExprFuncParam interface {
|
||||||
}
|
Name() string
|
||||||
|
Type() string
|
||||||
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
IsOptional() bool
|
||||||
return functor.f(ctx, name, args)
|
IsRepeat() bool
|
||||||
|
DefaultValue() any
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Function Info
|
// ---- Function Info
|
||||||
type ExprFunc interface {
|
type ExprFunc interface {
|
||||||
|
Formatter
|
||||||
Name() string
|
Name() string
|
||||||
MinArgs() int
|
MinArgs() int
|
||||||
MaxArgs() int
|
MaxArgs() int
|
||||||
Functor() Functor
|
Functor() Functor
|
||||||
|
Params() []ExprFuncParam
|
||||||
|
ReturnType() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----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)
|
||||||
setVar(varName string, value any)
|
UnsafeSetVar(varName string, value any)
|
||||||
EnumVars(func(name string) (accept bool)) (varNames []string)
|
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||||
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||||
Call(name string, args []any) (result any, err error)
|
Call(name string, args []any) (result any, err error)
|
||||||
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||||
|
RegisterFuncInfo(info ExprFunc)
|
||||||
|
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||||
}
|
}
|
||||||
|
|||||||
+130
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
+196
-102
@@ -22,7 +22,7 @@ Expressions calculator
|
|||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
#TODO: Work in progress (last update on 2024/05/16, 7:08 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.
|
||||||
@@ -44,7 +44,7 @@ Here are some examples of execution.
|
|||||||
.Run `dev-expr` in REPL mode and ask for help
|
.Run `dev-expr` in REPL mode and ask for help
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
|
# Type 'exit' or Ctrl+D to quit the program.
|
||||||
|
|
||||||
[user]$ ./dev-expr
|
[user]$ ./dev-expr
|
||||||
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
|
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
|
||||||
@@ -122,7 +122,7 @@ _Expr_ supports three type of numbers:
|
|||||||
|
|
||||||
. [blue]#Integers#
|
. [blue]#Integers#
|
||||||
. [blue]#Floats#
|
. [blue]#Floats#
|
||||||
. [blue]#Factions# internally are stored as _pairs of_ Golang _int64_ values.
|
. [blue]#Factions#
|
||||||
|
|
||||||
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
|
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ _dec-seq_ = _see-integer-literal-syntax_
|
|||||||
`>>>` [blue]`4.5E-3` +
|
`>>>` [blue]`4.5E-3` +
|
||||||
[green]`0.0045` +
|
[green]`0.0045` +
|
||||||
`>>>` [blue]`4.5E10` +
|
`>>>` [blue]`4.5E10` +
|
||||||
[green]`4.5e+10` +
|
[green]`4.5e+10`
|
||||||
|
|
||||||
|
|
||||||
.Arithmetic operators
|
.Arithmetic operators
|
||||||
@@ -207,7 +207,7 @@ _sign_ = "**+**" | "**-**" +
|
|||||||
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
|
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
|
||||||
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
|
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
|
||||||
_dec-seq_ = _see-integer-literal-syntax_ +
|
_dec-seq_ = _see-integer-literal-syntax_ +
|
||||||
_digit-seq_ = _see-integer-literal-syntax_ +
|
_digit-seq_ = _see-integer-literal-syntax_
|
||||||
====
|
====
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
@@ -235,12 +235,13 @@ _digit-seq_ = _see-integer-literal-syntax_ +
|
|||||||
|
|
||||||
Fractions can be used together with integers and floats in expressions.
|
Fractions can be used together with integers and floats in expressions.
|
||||||
|
|
||||||
|
.Examples
|
||||||
`>>>` [blue]`1|2 + 5` +
|
`>>>` [blue]`1|2 + 5` +
|
||||||
[green]`11|2` +
|
[green]`11|2` +
|
||||||
`>>>` [blue]`4 - 1|2` +
|
`>>>` [blue]`4 - 1|2` +
|
||||||
[green]`7|2` +
|
[green]`7|2` +
|
||||||
`>>>` [blue]`1.0 + 1|2` +
|
`>>>` [blue]`1.0 + 1|2` +
|
||||||
[green]`1.5` +
|
[green]`1.5`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -265,10 +266,10 @@ Some arithmetic operators can also be used with strings.
|
|||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
|
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` -> _"onetwo"_ +
|
||||||
[blue]`"one" + 2` _["one2"]_
|
[blue]`"one" + 2` -> _"one2"_
|
||||||
|
|
||||||
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The items of strings can be accessed using the dot `.` operator.
|
The items of strings can be accessed using the dot `.` operator.
|
||||||
@@ -279,21 +280,22 @@ _item_ = _string-expr_ "**.**" _integer-expr_
|
|||||||
====
|
====
|
||||||
|
|
||||||
.String examples
|
.String examples
|
||||||
`>>>` [blue]`s="abc"` [gray]_assign the string to variable s_ +
|
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ +
|
||||||
[green]`abc` +
|
[green]`abc` +
|
||||||
`>>>` [blue]`s.1` [gray]_char at position 1 (starting from 0)_ +
|
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
|
||||||
[green]`b` +
|
[green]`b` +
|
||||||
`>>>` [blue]`s.(-1)` [gray]_char at position -1, the rightmost one_ +
|
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
|
||||||
[green]`c` +
|
[green]`c` +
|
||||||
`>>>` [blue]`\#s` [gray]_number of chars_ +
|
`>>>` [blue]`\#s` [gray]_// number of chars_ +
|
||||||
[gren]`3` +
|
[gren]`3` +
|
||||||
`>>>` [blue]`#"abc"` [gray]_number of chars_ +
|
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
|
||||||
[green]`3` +
|
[green]`3`
|
||||||
|
|
||||||
=== Boolean
|
|
||||||
|
=== Booleans
|
||||||
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||||
|
|
||||||
.Relational operators
|
.Relational operators^(*)^
|
||||||
[cols="^1,^2,6,4"]
|
[cols="^1,^2,6,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
@@ -312,6 +314,8 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
|
|||||||
[blue]`"b" \<= "b"` -> _true_
|
[blue]`"b" \<= "b"` -> _true_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
|
||||||
|
|
||||||
|
|
||||||
.Boolean operators
|
.Boolean operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
@@ -341,35 +345,47 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
|
|||||||
====
|
====
|
||||||
|
|
||||||
=== Lists
|
=== Lists
|
||||||
_Expr_ supports list of mixed-type values, also specified by normal expressions.
|
_Expr_ supports list of mixed-type values, also specified by normal expressions. Internally, _Expr_'s lists are Go arrays.
|
||||||
|
|
||||||
.List examples
|
.List literal syntax
|
||||||
[source,go]
|
====
|
||||||
----
|
*_list_* = _empty-list_ | _non-empty-list_ +
|
||||||
[1, 2, 3] // List of integers
|
_empty-list_ = "**[]**" +
|
||||||
["one", "two", "three"] // List of strings
|
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
||||||
["one", 2, false, 4.1] // List of mixed-types
|
====
|
||||||
["one"+1, 2.0*(9-2)] // List of expressions
|
|
||||||
[ [1,"one"], [2,"two"]] // List of lists
|
.Examples
|
||||||
----
|
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
|
||||||
|
[green]`[1, 2, 3]` +
|
||||||
|
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
|
||||||
|
[green]`["one", "two", "three"]` +
|
||||||
|
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
|
||||||
|
[green]`["one", 2, false, 4.1]` +
|
||||||
|
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
|
||||||
|
[green]`["one1", 14]` +
|
||||||
|
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
|
||||||
|
[green]`[[1, "one"], [2, "two"]]`
|
||||||
|
|
||||||
.List operators
|
.List operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` _[ [1,2,3] ]_
|
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
|
||||||
|
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
|
||||||
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
|
||||||
|
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
|
||||||
|
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
|
||||||
|
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
|
||||||
|
[blue]`6 in [1,2,3]` -> _false_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The items of array can be accessed using the dot `.` operator.
|
The items of array can be accessed using the dot `.` operator.
|
||||||
|
|
||||||
.Item access syntax
|
.Item access syntax
|
||||||
[source,bnf]
|
====
|
||||||
----
|
_item_ = _list-expr_ "**.**" _integer-expr_
|
||||||
<item> ::= <list-expr>"."<index-expr>
|
====
|
||||||
----
|
|
||||||
|
|
||||||
.Items of list
|
.Items of list
|
||||||
`>>>` [blue]`[1,2,3].1` +
|
`>>>` [blue]`[1,2,3].1` +
|
||||||
@@ -385,113 +401,183 @@ The items of array can be accessed using the dot `.` operator.
|
|||||||
`>>>` [blue]`list.(10)` +
|
`>>>` [blue]`list.(10)` +
|
||||||
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
||||||
`>>>` [blue]`#list` +
|
`>>>` [blue]`#list` +
|
||||||
[green]`3`
|
[green]`3` +
|
||||||
|
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
|
||||||
|
[green]`c`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
== Dictionaries
|
=== Dictionaries
|
||||||
The _dictionary_ data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. Dictionary literals are sequences of pairs separated by comma `,`; sequences are enclosed between brace brackets.
|
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
|
||||||
|
|
||||||
.Dictionary examples
|
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
|
||||||
[source,go]
|
|
||||||
----
|
|
||||||
{1:"one", 2:"two"}
|
|
||||||
{"one":1, "two": 2}
|
|
||||||
{"sum":1+2+3, "prod":1*2*3}
|
|
||||||
----
|
|
||||||
|
|
||||||
WARNING: Support for dictionaries is still ongoing.
|
.Dict literal syntax
|
||||||
|
====
|
||||||
== Variables
|
*_dict_* = _empty-dict_ | _non-empty-dict_ +
|
||||||
A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go _ExprContext_ interface, e.g. _SimpleVarStore_ or _SimpleFuncStore_.
|
_empty-dict_ = "**{}**" +
|
||||||
|
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
|
||||||
|
====
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||||
----
|
[green]`{1: "one", 2: "two"}` +
|
||||||
a=1
|
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||||
x = 5.2 * (9-3)
|
[green]`{"one": 1, "two": 2}` +
|
||||||
x = 1; y = 2*x
|
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||||
----
|
[green]`{"sum": 6, "prod": 6}`
|
||||||
|
|
||||||
|
|
||||||
|
.Dict operators
|
||||||
|
[cols="^2,^2,4,5"]
|
||||||
|
|===
|
||||||
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
|
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
|
||||||
|
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
|
||||||
|
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
|
||||||
|
[blue]`"six" in {"one":1, "two":2}` -> _false_
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
== Variables
|
||||||
|
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
|
||||||
|
|
||||||
|
.Variable literal syntax
|
||||||
|
====
|
||||||
|
*_variable_* = _identifier_ "*=*" _any-value_ +
|
||||||
|
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
|
||||||
|
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
|
||||||
|
====
|
||||||
|
|
||||||
|
NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`a=1` +
|
||||||
|
[green]`1` +
|
||||||
|
`>>>` [blue]`a_b=1+2` +
|
||||||
|
[green]`1+2` +
|
||||||
|
`>>>` [blue]`a_b` +
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
|
||||||
|
[green]`31.200000000000003` +
|
||||||
|
`>>>` [blue]`x = 1; y = 2*x` +
|
||||||
|
[green]`2` +
|
||||||
|
`>>>` [blue]`_a=2` +
|
||||||
|
[red]`Parse Error: [1:2] unexpected token "_"` +
|
||||||
|
`>>>` [blue]`1=2` +
|
||||||
|
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
|
||||||
|
|
||||||
|
|
||||||
== Other operations
|
== Other operations
|
||||||
|
|
||||||
=== [blue]`;` operator
|
=== [blue]`;` operator
|
||||||
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.
|
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
|
||||||
|
|
||||||
|
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
|
||||||
|
|
||||||
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
||||||
|
|
||||||
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
`>>>` [blue]`a=1; b=2; c=3; a+b+c` +
|
||||||
----
|
[green]`6`
|
||||||
a=1; b=2; c=3; a+b+c // returns 6
|
|
||||||
----
|
The value of each sub-expression is stored in the automatica variable _last_.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
`>>>` [blue]`2+3; b=last+10; last` +
|
||||||
|
[green]`15`
|
||||||
|
|
||||||
|
|
||||||
=== [blue]`but` operator
|
=== [blue]`but` operator
|
||||||
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
|
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.
|
||||||
|
|
||||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
.Examples
|
||||||
|
[blue]`5 but 2` +
|
||||||
|
[green]`2` +
|
||||||
|
[blue]`x=2*3 but x-1` +
|
||||||
|
[green]`5`.
|
||||||
|
|
||||||
|
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||||
|
|
||||||
=== Assignment operator [blue]`=`
|
=== Assignment operator [blue]`=`
|
||||||
The assignment operator [blue]`=` is used to define variables in the evaluation context or to change their value (see _ExprContext_).
|
The assignment operator [blue]`=` is used to define variables or to change their value in the evaluation context (see _ExprContext_).
|
||||||
|
|
||||||
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
`>>>` [blue]`a=15+1`
|
||||||
----
|
[green]`16`
|
||||||
a=15+1 // returns 16
|
|
||||||
----
|
|
||||||
|
|
||||||
=== Selector operator [blue]`? : ::`
|
=== Selector operator [blue]`? : ::`
|
||||||
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
||||||
|
|
||||||
.Syntax
|
.Selector literal Syntax
|
||||||
[source,bnf]
|
_selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
|
||||||
----
|
_selector-case_ = [_match-list_] _case-value_ +
|
||||||
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>]
|
_match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
|
||||||
<selector-case> ::= [<match-list>] <case-value>
|
_item_ = _expression_ +
|
||||||
<match-list> ::= "["<item>{","<items>}"]"
|
_case-multi-expression_ = "*{*" _multi-expression_ "*}*" +
|
||||||
<item> ::= <expression
|
_multi-expression_ = _expression_ { "*;*" _expression_ } +
|
||||||
<case-multi-expression> ::= "{" <multi-expression> "}"
|
_default-multi-expression_ = _multi-expression_
|
||||||
<multi-expression> ::= <expression> {";" <expression>}
|
|
||||||
----
|
|
||||||
|
|
||||||
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision finds a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation.
|
In other words, the selector operator evaluates the _select-expression_ on the left-hand side of the [blue]`?` symbol; it then compares the result obtained with the values listed in the __match-list__'s, from left to right. If the comparision finds a match with a value in a _match-list_, the associated _case-multi-expression_ is evaluted, and its result will be the final result of the selection operation.
|
||||||
|
|
||||||
The match lists are optional. In that case, the position, from left to right, of the `<selector-case>` is used as match-list. Of course, that only works if the select-expression results in an integer.
|
The match lists are optional. In that case, the position, from left to right, of the _selector-case_ is used as _match-list_. Of course, that only works if the _select-expression_ results in an integer.
|
||||||
|
|
||||||
The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.
|
The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that if the value of the _select-expression_ does not match any _match-list_, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the [blue]`::` symbol (double-colon). Also note that the default expression has no _match-list_.
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
`>>>` [blue]`1 ? {"a"} : {"b"}` +
|
||||||
----
|
[green]`b` +
|
||||||
`>>>` [blue]`1 ? {"a"} : {"b"}`
|
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
|
||||||
[green]`b`
|
[green]`c' +
|
||||||
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}`
|
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
|
||||||
[green]`c'
|
[green]`b` +
|
||||||
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`
|
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
|
||||||
[green]`b`
|
[red]`Parse Error: [1:34] case list in default clause` +
|
||||||
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`
|
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
|
||||||
[red]`Parse Error: [1:34] case list in default clause`
|
[green]`b` +
|
||||||
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`
|
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` +
|
||||||
[green]`b`
|
[green]`b` +
|
||||||
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`
|
`>>>` [blue]`10 ? {"a"} : {"b"}` +
|
||||||
[green]`b`
|
|
||||||
`>>>` [blue]`10 ? {"a"} : {"b"}`
|
|
||||||
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
|
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
|
||||||
|
|
||||||
----
|
|
||||||
|
=== Variable default value [blue]`??` and [blue]`?=`
|
||||||
|
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.
|
||||||
|
|
||||||
|
IMPORTANT: If the left variable is defined, the right expression is not evuated at all.
|
||||||
|
|
||||||
|
The [blue]`??` do not change the status of the left variable.
|
||||||
|
|
||||||
|
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`var ?? (1+2)`'
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`var` +
|
||||||
|
[red]`Eval Error: undefined variable or function "var"` +
|
||||||
|
`>>>` [blue]`var ?= (1+2)` +
|
||||||
|
[green]`3` +
|
||||||
|
`>>>` [blue]`var` +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
|
||||||
|
|
||||||
== Priorities of operators
|
== Priorities of operators
|
||||||
The table below shows all supported operators by decreasing priorities.
|
The table below shows all supported operators by decreasing priorities.
|
||||||
|
|
||||||
.Operators priorities
|
.Operators priorities
|
||||||
[cols="^2,^2,^2,^5,^5"]
|
[cols="^2,^2,^2,^5,^6"]
|
||||||
|===
|
|===
|
||||||
| Priority | Operators | Position | Operation | Operands and results
|
| Priority | Operators | Position | Operation | Operands and results
|
||||||
|
|
||||||
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
|
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
|
||||||
|
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
|
||||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||||
@@ -503,28 +589,36 @@ The table below shows all supported operators by decreasing priorities.
|
|||||||
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
||||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
||||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
||||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
||||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
||||||
|
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
|
||||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
||||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
||||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
||||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
||||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
||||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
||||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_
|
||||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
||||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
||||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
||||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
||||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
||||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
|
||||||
|
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
|
||||||
|
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
== 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.
|
||||||
|
|
||||||
|
|||||||
+371
-148
@@ -549,31 +549,32 @@ pre.rouge .ss {
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#_strings">2.2. Strings</a></li>
|
<li><a href="#_strings">2.2. Strings</a></li>
|
||||||
<li><a href="#_boolean">2.3. Boolean</a></li>
|
<li><a href="#_booleans">2.3. Booleans</a></li>
|
||||||
<li><a href="#_lists">2.4. Lists</a></li>
|
<li><a href="#_lists">2.4. Lists</a></li>
|
||||||
|
<li><a href="#_dictionaries">2.5. Dictionaries</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#_dictionaries">3. Dictionaries</a></li>
|
<li><a href="#_variables">3. Variables</a></li>
|
||||||
<li><a href="#_variables">4. Variables</a></li>
|
<li><a href="#_other_operations">4. Other operations</a>
|
||||||
<li><a href="#_other_operations">5. Other operations</a>
|
|
||||||
<ul class="sectlevel2">
|
<ul class="sectlevel2">
|
||||||
<li><a href="#_operator">5.1. <code class="blue">;</code> operator</a></li>
|
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
|
||||||
<li><a href="#_but_operator">5.2. <code class="blue">but</code> operator</a></li>
|
<li><a href="#_but_operator">4.2. <code class="blue">but</code> operator</a></li>
|
||||||
<li><a href="#_assignment_operator">5.3. Assignment operator <code class="blue">=</code></a></li>
|
<li><a href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></li>
|
||||||
<li><a href="#_selector_operator">5.4. Selector operator <code class="blue">? : ::</code></a></li>
|
<li><a href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></li>
|
||||||
|
<li><a href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code> and <code class="blue">?=</code></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#_priorities_of_operators">6. Priorities of operators</a></li>
|
<li><a href="#_priorities_of_operators">5. Priorities of operators</a></li>
|
||||||
<li><a href="#_functions">7. Functions</a>
|
<li><a href="#_functions">6. Functions</a>
|
||||||
<ul class="sectlevel2">
|
<ul class="sectlevel2">
|
||||||
<li><a href="#_function_calls">7.1. Function calls</a></li>
|
<li><a href="#_function_calls">6.1. Function calls</a></li>
|
||||||
<li><a href="#_function_definitions">7.2. Function definitions</a></li>
|
<li><a href="#_function_definitions">6.2. Function definitions</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#_builtins">8. Builtins</a>
|
<li><a href="#_builtins">7. Builtins</a>
|
||||||
<ul class="sectlevel2">
|
<ul class="sectlevel2">
|
||||||
<li><a href="#_builtin_functions">8.1. Builtin functions</a></li>
|
<li><a href="#_builtin_functions">7.1. Builtin functions</a></li>
|
||||||
<li><a href="#_import">8.2. <em class="blue">import()</em></a></li>
|
<li><a href="#_import">7.2. <em class="blue">import()</em></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -584,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/16, 7:08 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>
|
||||||
@@ -622,7 +623,7 @@ pre.rouge .ss {
|
|||||||
<div class="listingblock">
|
<div class="listingblock">
|
||||||
<div class="title">Run <code>dev-expr</code> in REPL mode and ask for help</div>
|
<div class="title">Run <code>dev-expr</code> in REPL mode and ask for help</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.</span>
|
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Type 'exit' or Ctrl+D to quit the program.</span>
|
||||||
|
|
||||||
<span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
|
<span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
|
||||||
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
|
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
|
||||||
@@ -742,7 +743,7 @@ pre.rouge .ss {
|
|||||||
<p><span class="blue">Floats</span></p>
|
<p><span class="blue">Floats</span></p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p><span class="blue">Factions</span> internally are stored as <em>pairs of</em> Golang <em>int64</em> values.</p>
|
<p><span class="blue">Factions</span></p>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
@@ -854,7 +855,7 @@ pre.rouge .ss {
|
|||||||
<code>>>></code> <code class="blue">4.5E-3</code><br>
|
<code>>>></code> <code class="blue">4.5E-3</code><br>
|
||||||
<code class="green">0.0045</code><br>
|
<code class="green">0.0045</code><br>
|
||||||
<code>>>></code> <code class="blue">4.5E10</code><br>
|
<code>>>></code> <code class="blue">4.5E10</code><br>
|
||||||
<code class="green">4.5e+10</code><br></p>
|
<code class="green">4.5e+10</code></p>
|
||||||
</div>
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 2. Arithmetic operators</caption>
|
<caption class="title">Table 2. Arithmetic operators</caption>
|
||||||
@@ -918,7 +919,7 @@ pre.rouge .ss {
|
|||||||
<em>num-den-spec</em> = <em>digit-seq</em> "<strong>|</strong>" <em>digit-seq</em><br>
|
<em>num-den-spec</em> = <em>digit-seq</em> "<strong>|</strong>" <em>digit-seq</em><br>
|
||||||
<em>float-spec</em> = <em>dec-seq</em> "<strong>.</strong>" [<em>dec-seq</em>] "<strong>(</strong>" <em>dec-seq</em> "<strong>)</strong>"<br>
|
<em>float-spec</em> = <em>dec-seq</em> "<strong>.</strong>" [<em>dec-seq</em>] "<strong>(</strong>" <em>dec-seq</em> "<strong>)</strong>"<br>
|
||||||
<em>dec-seq</em> = <em>see-integer-literal-syntax</em><br>
|
<em>dec-seq</em> = <em>see-integer-literal-syntax</em><br>
|
||||||
<em>digit-seq</em> = <em>see-integer-literal-syntax</em><br></p>
|
<em>digit-seq</em> = <em>see-integer-literal-syntax</em></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -947,12 +948,13 @@ pre.rouge .ss {
|
|||||||
<p>Fractions can be used together with integers and floats in expressions.</p>
|
<p>Fractions can be used together with integers and floats in expressions.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
|
<div class="title">Examples</div>
|
||||||
<p><code>>>></code> <code class="blue">1|2 + 5</code><br>
|
<p><code>>>></code> <code class="blue">1|2 + 5</code><br>
|
||||||
<code class="green">11|2</code><br>
|
<code class="green">11|2</code><br>
|
||||||
<code>>>></code> <code class="blue">4 - 1|2</code><br>
|
<code>>>></code> <code class="blue">4 - 1|2</code><br>
|
||||||
<code class="green">7|2</code><br>
|
<code class="green">7|2</code><br>
|
||||||
<code>>>></code> <code class="blue">1.0 + 1|2</code><br>
|
<code>>>></code> <code class="blue">1.0 + 1|2</code><br>
|
||||||
<code class="green">1.5</code><br></p>
|
<code class="green">1.5</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -997,14 +999,14 @@ pre.rouge .ss {
|
|||||||
<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>concatenation</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>concatenation</em></p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Join two strings or two <em>stringable</em> values</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Join two strings or two <em>stringable</em> values</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" + "two"</code> <em>["onetwo"]</em><br>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" + "two"</code> → <em>"onetwo"</em><br>
|
||||||
<code class="blue">"one" + 2</code> <em>["one2"]</em></p></td>
|
<code class="blue">"one" + 2</code> → <em>"one2"</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<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>repeat</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>repeat</em></p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Make <em>n</em> copy of a string</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Make <em>n</em> copy of a string</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" * 2</code> <em>["oneone"]</em></p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" * 2</code> → <em>"oneone"</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -1021,25 +1023,25 @@ pre.rouge .ss {
|
|||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<div class="title">String examples</div>
|
<div class="title">String examples</div>
|
||||||
<p><code>>>></code> <code class="blue">s="abc"</code> <em class="gray">assign the string to variable s</em><br>
|
<p><code>>>></code> <code class="blue">s="abc"</code> <em class="gray">// assign the string to variable s</em><br>
|
||||||
<code class="green">abc</code><br>
|
<code class="green">abc</code><br>
|
||||||
<code>>>></code> <code class="blue">s.1</code> <em class="gray">char at position 1 (starting from 0)</em><br>
|
<code>>>></code> <code class="blue">s.1</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
|
||||||
<code class="green">b</code><br>
|
<code class="green">b</code><br>
|
||||||
<code>>>></code> <code class="blue">s.(-1)</code> <em class="gray">char at position -1, the rightmost one</em><br>
|
<code>>>></code> <code class="blue">s.(-1)</code> <em class="gray">// char at position -1, the rightmost one</em><br>
|
||||||
<code class="green">c</code><br>
|
<code class="green">c</code><br>
|
||||||
<code>>>></code> <code class="blue">#s</code> <em class="gray">number of chars</em><br>
|
<code>>>></code> <code class="blue">#s</code> <em class="gray">// number of chars</em><br>
|
||||||
<code class="gren">3</code><br>
|
<code class="gren">3</code><br>
|
||||||
<code>>>></code> <code class="blue">#"abc"</code> <em class="gray">number of chars</em><br>
|
<code>>>></code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
|
||||||
<code class="green">3</code><br></p>
|
<code class="green">3</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_boolean"><a class="anchor" href="#_boolean"></a><a class="link" href="#_boolean">2.3. Boolean</a></h3>
|
<h3 id="_booleans"><a class="anchor" href="#_booleans"></a><a class="link" href="#_booleans">2.3. Booleans</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
|
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
|
||||||
</div>
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 4. Relational operators</caption>
|
<caption class="title">Table 4. Relational operators<sup>(*)</sup></caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width: 7.6923%;">
|
<col style="width: 7.6923%;">
|
||||||
<col style="width: 15.3846%;">
|
<col style="width: 15.3846%;">
|
||||||
@@ -1099,6 +1101,9 @@ pre.rouge .ss {
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><sup>(*)</sup> See also the <code class="blue">in</code> operator in the <em>list</em> and <em>dictionary</em> sections.</p>
|
||||||
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 5. Boolean operators</caption>
|
<caption class="title">Table 5. Boolean operators</caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
@@ -1171,18 +1176,31 @@ pre.rouge .ss {
|
|||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_lists"><a class="anchor" href="#_lists"></a><a class="link" href="#_lists">2.4. Lists</a></h3>
|
<h3 id="_lists"><a class="anchor" href="#_lists"></a><a class="link" href="#_lists">2.4. Lists</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions.</p>
|
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions. Internally, <em>Expr</em>'s lists are Go arrays.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="exampleblock">
|
||||||
<div class="title">List examples</div>
|
<div class="title">Example 5. List literal syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre class="rouge highlight"><code data-lang="go"><span class="p">[</span><span class="m">1</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="m">3</span><span class="p">]</span> <span class="c">// List of integers</span>
|
<div class="paragraph">
|
||||||
<span class="p">[</span><span class="s">"one"</span><span class="p">,</span> <span class="s">"two"</span><span class="p">,</span> <span class="s">"three"</span><span class="p">]</span> <span class="c">// List of strings</span>
|
<p><strong><em>list</em></strong> = <em>empty-list</em> | <em>non-empty-list</em><br>
|
||||||
<span class="p">[</span><span class="s">"one"</span><span class="p">,</span> <span class="m">2</span><span class="p">,</span> <span class="no">false</span><span class="p">,</span> <span class="m">4.1</span><span class="p">]</span> <span class="c">// List of mixed-types</span>
|
<em>empty-list</em> = "<strong>[]</strong>"<br>
|
||||||
<span class="p">[</span><span class="s">"one"</span><span class="o">+</span><span class="m">1</span><span class="p">,</span> <span class="m">2.0</span><span class="o">*</span><span class="p">(</span><span class="m">9</span><span class="o">-</span><span class="m">2</span><span class="p">)]</span> <span class="c">// List of expressions</span>
|
<em>non-empty-list</em> = "<strong>[</strong>" <em>any-value</em> {"<strong>,</strong>" _any-value} "<strong>]</strong>"<br></p>
|
||||||
<span class="p">[</span> <span class="p">[</span><span class="m">1</span><span class="p">,</span><span class="s">"one"</span><span class="p">],</span> <span class="p">[</span><span class="m">2</span><span class="p">,</span><span class="s">"two"</span><span class="p">]]</span> <span class="c">// List of lists</span></code></pre>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="title">Examples</div>
|
||||||
|
<p><code>>>></code> <code class="blue">[1,2,3]</code> <em class="gray">// List of integers</em><br>
|
||||||
|
<code class="green">[1, 2, 3]</code><br>
|
||||||
|
<code>>>></code> <code class="blue">["one", "two", "three"]</code> <em class="gray">// List of strings</em><br>
|
||||||
|
<code class="green">["one", "two", "three"]</code><br>
|
||||||
|
<code>>>></code> <code class="blue">["one", 2, false, 4.1]</code> <em class="gray">// List of mixed-types</em><br>
|
||||||
|
<code class="green">["one", 2, false, 4.1]</code><br>
|
||||||
|
<code>>>></code> <code class="blue">["one"+1, 2.0*(9-2)]</code> <em class="gray">// List of expressions</em><br>
|
||||||
|
<code class="green">["one1", 14]</code><br>
|
||||||
|
<code>>>></code> <code class="blue">[ [1,"one"], [2,"two"]]</code> <em class="gray">// List of lists</em><br>
|
||||||
|
<code class="green">[[1, "one"], [2, "two"]]</code></p>
|
||||||
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 6. List operators</caption>
|
<caption class="title">Table 6. List operators</caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
@@ -1204,23 +1222,50 @@ pre.rouge .ss {
|
|||||||
<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>Join</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Join</em></p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Joins two lists</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Joins two lists</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] + [3]</code> <em>[ [1,2,3] ]</em></p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] + [3]</code> → <em>[1,2,3]</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<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>Difference</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Difference</em></p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock">Left list without elements in the right list</p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Left list without elements in the right list</p></td>
|
||||||
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2,3] - [2]</code> <em>[ [1,3] ]</em></p></td>
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2,3] - [2]</code> → <em>[1,3]</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>Front insertion</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Insert an item in front</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">0 >> [1,2]</code> → <em>[0,1,2]</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>Back insertion</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Insert an item at end</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2] << 3</code> → <em>[1,2,3]</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>List item</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Item at given position</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">[1,2.3].1</code> → <em>2</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item in list</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">True if item is in list</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">2 in [1,2,3]</code> → <em>true</em><br>
|
||||||
|
<code class="blue">6 in [1,2,3]</code> → <em>false</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The items of array can be accessed using the dot <code>.</code> operator.</p>
|
<p>The items of array can be accessed using the dot <code>.</code> operator.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Item access syntax</div>
|
<div class="title">Example 6. Item access syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre class="rouge highlight"><code data-lang="bnf"><item> ::= <list-expr>"."<index-expr></code></pre>
|
<div class="paragraph">
|
||||||
|
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
@@ -1238,62 +1283,136 @@ pre.rouge .ss {
|
|||||||
<code>>>></code> <code class="blue">list.(10)</code><br>
|
<code>>>></code> <code class="blue">list.(10)</code><br>
|
||||||
<code class="red">Eval Error: [1:9] index 10 out of bounds</code><br>
|
<code class="red">Eval Error: [1:9] index 10 out of bounds</code><br>
|
||||||
<code>>>></code> <code class="blue">#list</code><br>
|
<code>>>></code> <code class="blue">#list</code><br>
|
||||||
<code class="green">3</code></p>
|
<code class="green">3</code><br>
|
||||||
|
<code>>>></code> <code class="blue">index=2; ["a", "b", "c", "d"].index</code><br>
|
||||||
|
<code class="green">c</code></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="sect2">
|
||||||
</div>
|
<h3 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">2.5. Dictionaries</a></h3>
|
||||||
<div class="sect1">
|
|
||||||
<h2 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">3. Dictionaries</a></h2>
|
|
||||||
<div class="sectionbody">
|
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The <em>dictionary</em> data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>. Dictionary literals are sequences of pairs separated by comma <code>,</code>; sequences are enclosed between brace brackets.</p>
|
<p>The <em>dictionary</em>, or <em>dict</em>, data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="paragraph">
|
||||||
<div class="title">Dictionary examples</div>
|
<p>Dictionary literals are sequences of pairs separated by comma <code class="blue">,</code> enclosed between brace brackets.</p>
|
||||||
|
</div>
|
||||||
|
<div class="exampleblock">
|
||||||
|
<div class="title">Example 7. Dict literal syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre class="rouge highlight"><code data-lang="go"><span class="p">{</span><span class="m">1</span><span class="o">:</span><span class="s">"one"</span><span class="p">,</span> <span class="m">2</span><span class="o">:</span><span class="s">"two"</span><span class="p">}</span>
|
<div class="paragraph">
|
||||||
<span class="p">{</span><span class="s">"one"</span><span class="o">:</span><span class="m">1</span><span class="p">,</span> <span class="s">"two"</span><span class="o">:</span> <span class="m">2</span><span class="p">}</span>
|
<p><strong><em>dict</em></strong> = <em>empty-dict</em> | <em>non-empty-dict</em><br>
|
||||||
<span class="p">{</span><span class="s">"sum"</span><span class="o">:</span><span class="m">1</span><span class="o">+</span><span class="m">2</span><span class="o">+</span><span class="m">3</span><span class="p">,</span> <span class="s">"prod"</span><span class="o">:</span><span class="m">1</span><span class="o">*</span><span class="m">2</span><span class="o">*</span><span class="m">3</span><span class="p">}</span></code></pre>
|
<em>empty-dict</em> = "<strong>{}</strong>"<br>
|
||||||
|
<em>non-empty-dict</em> = "<strong>{</strong>" <em>key-scalar</em> "<strong>:</strong>" <em>any-value</em> {"<strong>,</strong>" <em>key-scalar</em> "<strong>:</strong>" _any-value} "<strong>}</strong>"<br></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="admonitionblock warning">
|
</div>
|
||||||
<table>
|
<div class="paragraph">
|
||||||
|
<div class="title">Examples</div>
|
||||||
|
<p><code>>>></code> <code class="blue">{1:"one", 2:"two"}</code><br>
|
||||||
|
<code class="green">{1: "one", 2: "two"}</code><br>
|
||||||
|
<code>>>></code> <code class="blue">{"one":1, "two": 2}</code><br>
|
||||||
|
<code class="green">{"one": 1, "two": 2}</code><br>
|
||||||
|
<code>>>></code> <code class="blue">{"sum":1+2+3, "prod":1*2*3}</code><br>
|
||||||
|
<code class="green">{"sum": 6, "prod": 6}</code></p>
|
||||||
|
</div>
|
||||||
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
|
<caption class="title">Table 7. Dict operators</caption>
|
||||||
|
<colgroup>
|
||||||
|
<col style="width: 15.3846%;">
|
||||||
|
<col style="width: 15.3846%;">
|
||||||
|
<col style="width: 30.7692%;">
|
||||||
|
<col style="width: 38.4616%;">
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="icon">
|
<th class="tableblock halign-center valign-top">Symbol</th>
|
||||||
<i class="fa icon-warning" title="Warning"></i>
|
<th class="tableblock halign-center valign-top">Operation</th>
|
||||||
</td>
|
<th class="tableblock halign-left valign-top">Description</th>
|
||||||
<td class="content">
|
<th class="tableblock halign-left valign-top">Examples</th>
|
||||||
Support for dictionaries is still ongoing.
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<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>Join</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Joins two dicts</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">{1:"one"}+{6:"six"}</code> → <em>{1: "one", 6: "six"}</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>Dict item value</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">Item value of given key</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">{"one":1, "two":2}."two"</code> → <em>2</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</code></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Key in dict</em></p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock">True if key is in dict</p></td>
|
||||||
|
<td class="tableblock halign-left valign-top"><p class="tableblock"><code class="blue">"one" in {"one":1, "two":2}</code> → <em>true</em><br>
|
||||||
|
<code class="blue">"six" in {"one":1, "two":2}</code> → <em>false</em></p></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect1">
|
<div class="sect1">
|
||||||
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">4. Variables</a></h2>
|
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go <em>ExprContext</em> interface, e.g. <em>SimpleVarStore</em> or <em>SimpleFuncStore</em>.</p>
|
<p><em>Expr</em>, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="exampleblock">
|
||||||
<div class="title">Examples</div>
|
<div class="title">Example 8. Variable literal syntax</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre class="rouge highlight"><code data-lang="go"><span class="n">a</span><span class="o">=</span><span class="m">1</span>
|
<div class="paragraph">
|
||||||
<span class="n">x</span> <span class="o">=</span> <span class="m">5.2</span> <span class="o">*</span> <span class="p">(</span><span class="m">9</span><span class="o">-</span><span class="m">3</span><span class="p">)</span>
|
<p><strong><em>variable</em></strong> = <em>identifier</em> "<strong>=</strong>" <em>any-value</em><br>
|
||||||
<span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="p">;</span> <span class="n">y</span> <span class="o">=</span> <span class="m">2</span><span class="o">*</span><span class="n">x</span></code></pre>
|
<em>identifier</em> = <em>alpha</em> {(<em>alpha</em>)|<em>dec-digit</em>|"<strong>_</strong>"}<br>
|
||||||
|
<em>alpha</em> = "<strong>a</strong>"|"<strong>b</strong>"|…​"<strong>z</strong>"|"<strong>A</strong>"|"<strong>B</strong>"|…​"<strong>Z</strong>"</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="admonitionblock note">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="icon">
|
||||||
|
<i class="fa icon-note" title="Note"></i>
|
||||||
|
</td>
|
||||||
|
<td class="content">
|
||||||
|
The assign operator <code class="blue">=</code> returns the value assigned to the variable.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="title">Examples</div>
|
||||||
|
<p><code>>>></code> <code class="blue">a=1</code><br>
|
||||||
|
<code class="green">1</code><br>
|
||||||
|
<code>>>></code> <code class="blue">a_b=1+2</code><br>
|
||||||
|
<code class="green">1+2</code><br>
|
||||||
|
<code>>>></code> <code class="blue">a_b</code><br>
|
||||||
|
<code class="green">3</code><br>
|
||||||
|
<code>>>></code> <code class="blue">x = 5.2 * (9-3)</code> <em class="gray">// The assigned value has the approximation error typical of the float data-type</em><br>
|
||||||
|
<code class="green">31.200000000000003</code><br>
|
||||||
|
<code>>>></code> <code class="blue">x = 1; y = 2*x</code><br>
|
||||||
|
<code class="green">2</code><br>
|
||||||
|
<code>>>></code> <code class="blue"><em>a=2</code><br>
|
||||||
|
<code class="red">Parse Error: [1:2] unexpected token "</em>"</code><br>
|
||||||
|
<code>>>></code> <code class="blue">1=2</code><br>
|
||||||
|
<code class="red">Parse Error: assign operator ("=") must be preceded by a variable</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect1">
|
<div class="sect1">
|
||||||
<h2 id="_other_operations"><a class="anchor" href="#_other_operations"></a><a class="link" href="#_other_operations">5. Other operations</a></h2>
|
<h2 id="_other_operations"><a class="anchor" href="#_other_operations"></a><a class="link" href="#_other_operations">4. Other operations</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_operator"><a class="anchor" href="#_operator"></a><a class="link" href="#_operator">5.1. <code class="blue">;</code> operator</a></h3>
|
<h3 id="_operator"><a class="anchor" href="#_operator"></a><a class="link" href="#_operator">4.1. <code class="blue">;</code> operator</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The semicolon operator <code class="blue">;</code> is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.</p>
|
<p>The semicolon operator <code class="blue">;</code> is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>An expression that contains <code class="blue">;</code> is called a <em>multi-expression</em> and each component expressione is called a <em>sub-expression</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="admonitionblock important">
|
<div class="admonitionblock important">
|
||||||
<table>
|
<table>
|
||||||
@@ -1319,97 +1438,155 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="paragraph">
|
||||||
<div class="title">Example</div>
|
<div class="title">Example</div>
|
||||||
<div class="content">
|
<p><code>>>></code> <code class="blue">a=1; b=2; c=3; a+b+c</code><br>
|
||||||
<pre class="rouge highlight"><code data-lang="go"><span class="n">a</span><span class="o">=</span><span class="m">1</span><span class="p">;</span> <span class="n">b</span><span class="o">=</span><span class="m">2</span><span class="p">;</span> <span class="n">c</span><span class="o">=</span><span class="m">3</span><span class="p">;</span> <span class="n">a</span><span class="o">+</span><span class="n">b</span><span class="o">+</span><span class="n">c</span> <span class="c">// returns 6</span></code></pre>
|
<code class="green">6</code></p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sect2">
|
|
||||||
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">5.2. <code class="blue">but</code> operator</a></h3>
|
|
||||||
<div class="paragraph">
|
|
||||||
<p><code class="blue">but</code> is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: <code class="blue">5 but 2</code> returns 2, <code class="blue">x=2*3 but x-1</code> returns 5.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><code class="blue">but</code> is very similar to <code class="blue">;</code>. The only difference is that <code class="blue">;</code> can’t be used inside parenthesis <code class="blue">(</code> and <code class="blue">)</code>.</p>
|
<p>The value of each sub-expression is stored in the automatica variable <em>last</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="sect2">
|
|
||||||
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">5.3. Assignment operator <code class="blue">=</code></a></h3>
|
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The assignment operator <code class="blue">=</code> is used to define variables in the evaluation context or to change their value (see <em>ExprContext</em>).
|
|
||||||
The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
|
|
||||||
</div>
|
|
||||||
<div class="listingblock">
|
|
||||||
<div class="title">Example</div>
|
<div class="title">Example</div>
|
||||||
<div class="content">
|
<p><code>>>></code> <code class="blue">2+3; b=last+10; last</code><br>
|
||||||
<pre class="rouge highlight"><code data-lang="go"><span class="n">a</span><span class="o">=</span><span class="m">15</span><span class="o">+</span><span class="m">1</span> <span class="c">// returns 16</span></code></pre>
|
<code class="green">15</code></p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_selector_operator"><a class="anchor" href="#_selector_operator"></a><a class="link" href="#_selector_operator">5.4. Selector operator <code class="blue">? : ::</code></a></h3>
|
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">4.2. <code class="blue">but</code> operator</a></h3>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><code class="blue">but</code> is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="title">Examples</div>
|
||||||
|
<p><code class="blue">5 but 2</code><br>
|
||||||
|
<code class="green">2</code><br>
|
||||||
|
<code class="blue">x=2*3 but x-1</code><br>
|
||||||
|
<code class="green">5</code>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p><code class="blue">but</code> behavior is very similar to <code class="blue">;</code>. The only difference is that <code class="blue">;</code> is not a true operator and can’t be used inside parenthesis <code class="blue">(</code> and <code class="blue">)</code>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sect2">
|
||||||
|
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></h3>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The assignment operator <code class="blue">=</code> is used to define variables or to change their value in the evaluation context (see <em>ExprContext</em>).</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="title">Example</div>
|
||||||
|
<p><code>>>></code> <code class="blue">a=15+1</code>
|
||||||
|
<code class="green">16</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sect2">
|
||||||
|
<h3 id="_selector_operator"><a class="anchor" href="#_selector_operator"></a><a class="link" href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p>
|
<p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="paragraph">
|
||||||
<div class="title">Syntax</div>
|
<div class="title">Selector literal Syntax</div>
|
||||||
<div class="content">
|
<p><em>selector-operator</em> = <em>select-expression</em> "<strong>?</strong>" <em>selector-case</em> { "<strong>:</strong>" <em>selector-case</em> } ["<strong>::</strong>" <em>default-multi-expression</em>]<br>
|
||||||
<pre class="rouge highlight"><code data-lang="bnf"><selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>]
|
<em>selector-case</em> = [<em>match-list</em>] <em>case-value</em><br>
|
||||||
<selector-case> ::= [<match-list>] <case-value>
|
<em>match-list</em> = "<strong>[</strong>" <em>item</em> {"<strong>,</strong>" <em>items</em>} "<strong>]</strong>"<br>
|
||||||
<match-list> ::= "["<item>{","<items>}"]"
|
<em>item</em> = <em>expression</em><br>
|
||||||
<item> ::= <expression
|
<em>case-multi-expression</em> = "<strong>{</strong>" <em>multi-expression</em> "<strong>}</strong>"<br>
|
||||||
<case-multi-expression> ::= "{" <multi-expression> "}"
|
<em>multi-expression</em> = <em>expression</em> { "<strong>;</strong>" <em>expression</em> }<br>
|
||||||
<multi-expression> ::= <expression> {";" <expression>}</code></pre>
|
<em>default-multi-expression</em> = <em>multi-expression</em></p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>In other words, the selector operator evaluates the expression (<code><select-expression></code>) on the left-hand side of the <code>?</code> symbol; it then compares the result obtained with the values listed in the <code><match-list>’s. If the comparision finds a match with a value in a match-list, the associated `<case-multi-expression></code> is evaluted, and its result will be the final result of the selection operation.</p>
|
<p>In other words, the selector operator evaluates the <em>select-expression</em> on the left-hand side of the <code class="blue">?</code> symbol; it then compares the result obtained with the values listed in the <em>match-list</em>'s, from left to right. If the comparision finds a match with a value in a <em>match-list</em>, the associated <em>case-multi-expression</em> is evaluted, and its result will be the final result of the selection operation.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The match lists are optional. In that case, the position, from left to right, of the <code><selector-case></code> is used as match-list. Of course, that only works if the select-expression results in an integer.</p>
|
<p>The match lists are optional. In that case, the position, from left to right, of the <em>selector-case</em> is used as <em>match-list</em>. Of course, that only works if the <em>select-expression</em> results in an integer.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The <code>:</code> symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the <code>::</code> symbol (double-colon). Also note that the default expression has no match-list.</p>
|
<p>The <code class="blue">:</code> symbol (colon) is the separator of the selector-cases. Note that if the value of the <em>select-expression</em> does not match any <em>match-list</em>, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the <code class="blue">::</code> symbol (double-colon). Also note that the default expression has no <em>match-list</em>.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="listingblock">
|
<div class="paragraph">
|
||||||
<div class="title">Examples</div>
|
<div class="title">Examples</div>
|
||||||
<div class="content">
|
<p><code>>>></code> <code class="blue">1 ? {"a"} : {"b"}</code><br>
|
||||||
<pre class="rouge highlight"><code data-lang="go"><span class="s">`>>>`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`1 ? {"a"} : {"b"}`</span>
|
<code class="green">b</code><br>
|
||||||
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`b`</span>
|
<code>>>></code> <code class="blue">10 ? {"a"} : {"b"} :: {"c"}</code><br>
|
||||||
<span class="s">`>>>`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`10 ? {"a"} : {"b"} :: {"c"}`</span>
|
<code class="green">c'<br>
|
||||||
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`c'
|
[green]</code>>>>` <code class="blue">10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}</code><br>
|
||||||
[green]`</span><span class="o">>>></span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
|
<code class="green">b</code><br>
|
||||||
[green]`</span><span class="n">b</span><span class="s">`
|
<code>>>></code> <code class="blue">10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}</code><br>
|
||||||
`</span><span class="o">>>></span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
|
<code class="red">Parse Error: [1:34] case list in default clause</code><br>
|
||||||
[red]`</span><span class="n">Parse</span> <span class="n">Error</span><span class="o">:</span> <span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="m">34</span><span class="p">]</span> <span class="k">case</span> <span class="n">list</span> <span class="n">in</span> <span class="k">default</span> <span class="n">clause</span><span class="s">`
|
<code class="green">>>></code> <code class="blue">10 ? {"a"} :[10] {x="b" but x} :: {"c"}</code><br>
|
||||||
[green]`</span><span class="o">>>></span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span> <span class="n">but</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
|
<code class="green">b</code><br>
|
||||||
[green]`</span><span class="n">b</span><span class="s">`
|
<code>>>></code> <code class="blue">10 ? {"a"} :[10] {x="b"; x} :: {"c"}</code><br>
|
||||||
`</span><span class="o">>>></span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span><span class="p">;</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
|
<code class="green">b</code><br>
|
||||||
[green]`</span><span class="n">b</span><span class="s">`
|
<code>>>></code> <code class="blue">10 ? {"a"} : {"b"}</code><br>
|
||||||
`</span><span class="o">>>></span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span><span class="s">`
|
<code class="red">Eval Error: [1:3] no case catches the value (10) of the selection expression</code></p>
|
||||||
[red]`</span><span class="n">Eval</span> <span class="n">Error</span><span class="o">:</span> <span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">]</span> <span class="n">no</span> <span class="k">case</span> <span class="n">catches</span> <span class="n">the</span> <span class="n">value</span> <span class="p">(</span><span class="m">10</span><span class="p">)</span> <span class="n">of</span> <span class="n">the</span> <span class="n">selection</span> <span class="n">expression</span><span class="s">`
|
|
||||||
</span></code></pre>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sect2">
|
||||||
|
<h3 id="_variable_default_value_and"><a class="anchor" href="#_variable_default_value_and"></a><a class="link" href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code> and <code class="blue">?=</code></a></h3>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.</p>
|
||||||
|
</div>
|
||||||
|
<div class="admonitionblock important">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="icon">
|
||||||
|
<i class="fa icon-important" title="Important"></i>
|
||||||
|
</td>
|
||||||
|
<td class="content">
|
||||||
|
If the left variable is defined, the right expression is not evuated at all.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The <code class="blue">??</code> do not change the status of the left variable.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<p>The <code class="blue">?=</code> assigns the calculated value of the right expression to the left variable.</p>
|
||||||
|
</div>
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="title">Examples</div>
|
||||||
|
<p><code>>>></code> <code class="blue">var ?? (1+2)’
|
||||||
|
[green]`3</code><br>
|
||||||
|
<code>>>></code> <code class="blue">var</code><br>
|
||||||
|
<code class="red">Eval Error: undefined variable or function "var"</code><br>
|
||||||
|
<code>>>></code> <code class="blue">var ?= (1+2)</code><br>
|
||||||
|
<code class="green">3</code><br>
|
||||||
|
<code>>>></code> <code class="blue">var</code><br>
|
||||||
|
<code class="green">3</code></p>
|
||||||
|
</div>
|
||||||
|
<div class="admonitionblock note">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="icon">
|
||||||
|
<i class="fa icon-note" title="Note"></i>
|
||||||
|
</td>
|
||||||
|
<td class="content">
|
||||||
|
These operators have a high priority, in particular higher than the operator <code class="blue">=</code>.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect1">
|
<div class="sect1">
|
||||||
<h2 id="_priorities_of_operators"><a class="anchor" href="#_priorities_of_operators"></a><a class="link" href="#_priorities_of_operators">6. Priorities of operators</a></h2>
|
<h2 id="_priorities_of_operators"><a class="anchor" href="#_priorities_of_operators"></a><a class="link" href="#_priorities_of_operators">5. Priorities of operators</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p>The table below shows all supported operators by decreasing priorities.</p>
|
<p>The table below shows all supported operators by decreasing priorities.</p>
|
||||||
</div>
|
</div>
|
||||||
<table class="tableblock frame-all grid-all stretch">
|
<table class="tableblock frame-all grid-all stretch">
|
||||||
<caption class="title">Table 7. Operators priorities</caption>
|
<caption class="title">Table 8. Operators priorities</caption>
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width: 12.5%;">
|
<col style="width: 11.7647%;">
|
||||||
<col style="width: 12.5%;">
|
<col style="width: 11.7647%;">
|
||||||
<col style="width: 12.5%;">
|
<col style="width: 11.7647%;">
|
||||||
<col style="width: 31.25%;">
|
<col style="width: 29.4117%;">
|
||||||
<col style="width: 31.25%;">
|
<col style="width: 35.2942%;">
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -1422,11 +1599,17 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>ITEM</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>ITEM</strong></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"><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>Item</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>List item</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>collection</em> <code>"."</code> <em>key</em> → <em>any</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"."</code> <em>integer</em> → <em>any</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>Dict item</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"."</code> <em>any</em> → <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>
|
||||||
@@ -1499,7 +1682,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>integer</em> <code>"%"</code> <em>integer</em> → <em>integer</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>integer</em> <code>"%"</code> <em>integer</em> → <em>integer</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top" rowspan="5"><p class="tableblock"><strong>SUM</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>SUM</strong></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"><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>Sum</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Sum</em></p></td>
|
||||||
@@ -1518,6 +1701,12 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"+"</code> <em>list</em> → <em>list</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"+"</code> <em>list</em> → <em>list</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<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>Dict-join</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"+"</code> <em>dict</em> → <em>dict</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<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>Subtraction</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Subtraction</em></p></td>
|
||||||
@@ -1530,7 +1719,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"-"</code> <em>list</em> → <em>list</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> <code>"-"</code> <em>list</em> → <em>list</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>RELATION</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="8"><p class="tableblock"><strong>RELATION</strong></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"><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>less</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>less</em></p></td>
|
||||||
@@ -1567,6 +1756,18 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>comparable</em> <code>"!="</code> <em>comparable</em> → <em>boolean</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>comparable</em> <code>"!="</code> <em>comparable</em> → <em>boolean</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</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>member-of-list</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>"in"</code> <em>list</em> → <em>boolean</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">in</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>key-of-dict</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>"in"</code> <em>dict</em> → <em>boolean</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>NOT</strong></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>NOT</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">not</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">not</code></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Prefix</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Prefix</em></p></td>
|
||||||
@@ -1600,13 +1801,25 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>boolean</em> <code>"||"</code> <em>boolean</em> → <em>boolean</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>boolean</em> <code>"||"</code> <em>boolean</em> → <em>boolean</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>ASSIGN</strong></p></td>
|
<td class="tableblock halign-center valign-top" rowspan="3"><p class="tableblock"><strong>ASSIGN</strong></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"><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>assignment</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>assignment</em></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> "=" <em>any</em> → <em>any</em></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> "=" <em>any</em> → <em>any</em></p></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<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>front-insert</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> ">>" <em>list</em> → <em>list</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>back-insert</em></p></td>
|
||||||
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>list</em> "<<" <em>any</em> → <em>list</em></p></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
|
||||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">but</code></p></td>
|
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">but</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>
|
||||||
@@ -1618,22 +1831,32 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect1">
|
<div class="sect1">
|
||||||
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">7. 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>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_function_calls"><a class="anchor" href="#_function_calls"></a><a class="link" href="#_function_calls">7.1. Function calls</a></h3>
|
<h3 id="_function_calls"><a class="anchor" href="#_function_calls"></a><a class="link" href="#_function_calls">6.1. Function calls</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><mark>TODO: function calls operations</mark></p>
|
<p><mark>TODO: function calls operations</mark></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_function_definitions"><a class="anchor" href="#_function_definitions"></a><a class="link" href="#_function_definitions">7.2. Function definitions</a></h3>
|
<h3 id="_function_definitions"><a class="anchor" href="#_function_definitions"></a><a class="link" href="#_function_definitions">6.2. Function definitions</a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><mark>TODO: function definitions operations</mark></p>
|
<p><mark>TODO: function definitions operations</mark></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1641,17 +1864,17 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect1">
|
<div class="sect1">
|
||||||
<h2 id="_builtins"><a class="anchor" href="#_builtins"></a><a class="link" href="#_builtins">8. Builtins</a></h2>
|
<h2 id="_builtins"><a class="anchor" href="#_builtins"></a><a class="link" href="#_builtins">7. Builtins</a></h2>
|
||||||
<div class="sectionbody">
|
<div class="sectionbody">
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><mark>TODO: builtins</mark></p>
|
<p><mark>TODO: builtins</mark></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_builtin_functions"><a class="anchor" href="#_builtin_functions"></a><a class="link" href="#_builtin_functions">8.1. Builtin functions</a></h3>
|
<h3 id="_builtin_functions"><a class="anchor" href="#_builtin_functions"></a><a class="link" href="#_builtin_functions">7.1. Builtin functions</a></h3>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sect2">
|
<div class="sect2">
|
||||||
<h3 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import">8.2. <em class="blue">import()</em></a></h3>
|
<h3 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import">7.2. <em class="blue">import()</em></a></h3>
|
||||||
<div class="paragraph">
|
<div class="paragraph">
|
||||||
<p><em class="blue">import(<span class="grey"><source-file></span>)</em> loads the multi-expression contained in the specified source and returns its value.</p>
|
<p><em class="blue">import(<span class="grey"><source-file></span>)</em> loads the multi-expression contained in the specified source and returns its value.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1661,7 +1884,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
|
|||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div id="footer-text">
|
<div id="footer-text">
|
||||||
Last updated 2024-05-16 07:10:03 +0200
|
Last updated 2024-06-03 06:26:03 +0200
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -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},
|
|
||||||
/* 10 */ {`
|
|
||||||
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 := NewSimpleFuncStore()
|
|
||||||
// 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,17 +4,64 @@
|
|||||||
// formatter.go
|
// formatter.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type FmtOpt uint16
|
type FmtOpt uint16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TTY FmtOpt = 1 << iota
|
TTY FmtOpt = 1 << iota
|
||||||
MultiLine
|
MultiLine
|
||||||
|
Truncate
|
||||||
Base2
|
Base2
|
||||||
Base8
|
Base8
|
||||||
Base10
|
Base10
|
||||||
Base16
|
Base16
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TruncateEllipsis = "(...)"
|
||||||
|
MinTruncateSize = 10
|
||||||
|
TruncateSize = MinTruncateSize + 15
|
||||||
|
)
|
||||||
|
|
||||||
|
func TruncateString(s string) (trunc string) {
|
||||||
|
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
|
||||||
|
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
ToString(options FmtOpt) string
|
ToString(options FmtOpt) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFormatted(v any, opt FmtOpt) (text string) {
|
||||||
|
if v == nil {
|
||||||
|
text = "(nil)"
|
||||||
|
} else if s, ok := v.(string); ok {
|
||||||
|
text = s
|
||||||
|
} else if formatter, ok := v.(Formatter); ok {
|
||||||
|
text = formatter.ToString(opt)
|
||||||
|
} else {
|
||||||
|
text = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Typer interface {
|
||||||
|
TypeName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeName(v any) (name string) {
|
||||||
|
if v == nil {
|
||||||
|
name = "nil"
|
||||||
|
} else if typer, ok := v.(Typer); ok {
|
||||||
|
name = typer.TypeName()
|
||||||
|
} else if IsInteger(v) {
|
||||||
|
name = "integer"
|
||||||
|
} else if IsFloat(v) {
|
||||||
|
name = "float"
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%T", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+42
-31
@@ -54,6 +54,24 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = (v != 0)
|
||||||
|
case *FractionType:
|
||||||
|
result = v.num != 0
|
||||||
|
case float64:
|
||||||
|
result = v != 0.0
|
||||||
|
case bool:
|
||||||
|
result = v
|
||||||
|
case string:
|
||||||
|
result = len(v) > 0
|
||||||
|
default:
|
||||||
|
err = errCantConvert(name, v, "bool")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
switch v := args[0].(type) {
|
switch v := args[0].(type) {
|
||||||
case int64:
|
case int64:
|
||||||
@@ -94,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")
|
||||||
@@ -111,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 {
|
||||||
@@ -119,10 +137,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
|||||||
}
|
}
|
||||||
case float64:
|
case float64:
|
||||||
result, err = float64ToFraction(v)
|
result, err = float64ToFraction(v)
|
||||||
// var n, d int64
|
|
||||||
// if n, d, err = float64ToFraction(v); err == nil {
|
|
||||||
// result = newFraction(n, d)
|
|
||||||
// }
|
|
||||||
case bool:
|
case bool:
|
||||||
if v {
|
if v {
|
||||||
result = newFraction(1, 1)
|
result = newFraction(1, 1)
|
||||||
@@ -131,17 +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)
|
||||||
// var f float64
|
case *FractionType:
|
||||||
// // TODO temporary implementation
|
|
||||||
// if f, err = strconv.ParseFloat(v, 64); err == nil {
|
|
||||||
// var n, d int64
|
|
||||||
// if n, d, err = float64ToFraction(f); err == nil {
|
|
||||||
// result = newFraction(n, d)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// errors.New("convertion from string to float is ongoing")
|
|
||||||
// }
|
|
||||||
case *fraction:
|
|
||||||
result = v
|
result = v
|
||||||
default:
|
default:
|
||||||
err = errCantConvert(name, v, "float")
|
err = errCantConvert(name, v, "float")
|
||||||
@@ -154,20 +158,27 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
|
anyParams := []ExprFuncParam{
|
||||||
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
|
newFuncParam(paramValue),
|
||||||
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
|
}
|
||||||
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
|
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
|
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
|
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
|
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
|
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
|
||||||
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
|
|
||||||
|
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams)
|
||||||
|
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams)
|
||||||
|
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams)
|
||||||
|
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{
|
||||||
|
newFuncParam(paramValue),
|
||||||
|
newFuncParamFlagDef("denominator", pfOptional, 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+36
@@ -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")
|
||||||
|
}
|
||||||
+6
-2
@@ -137,8 +137,12 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportImportFuncs(ctx ExprContext) {
|
func ImportImportFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
|
newFuncParamFlag(paramFilepath, pfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlag(paramFilepath, pfRepeat),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+13
-8
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -167,8 +167,13 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportMathFuncs(ctx ExprContext) {
|
func ImportMathFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(0)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
|
||||||
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(1)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+100
-57
@@ -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,61 +126,87 @@ 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", &simpleFunctor{f: openFileFunc}, 1, 1)
|
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
newFuncParam(paramFilepath),
|
||||||
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
})
|
||||||
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
|
newFuncParam(paramFilepath),
|
||||||
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
|
})
|
||||||
|
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
|
||||||
|
newFuncParam(paramFilepath),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
newFuncParamFlagDef("limitCh", pfOptional, "\n"),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(typeHandle),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
+59
-47
@@ -33,9 +33,9 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
// if len(args) < 1 {
|
// if len(args) < 1 {
|
||||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||||
// }
|
// }
|
||||||
if sep, ok := args[0].(string); ok {
|
if sep, ok := args[0].(string); ok {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
result = ""
|
result = ""
|
||||||
@@ -62,25 +62,22 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
|
|||||||
var source string
|
var source string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -93,9 +90,6 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
|||||||
var source string
|
var source string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
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])
|
||||||
}
|
}
|
||||||
@@ -108,9 +102,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
result = false
|
result = false
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
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])
|
||||||
}
|
}
|
||||||
@@ -133,9 +125,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
result = false
|
result = false
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
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])
|
||||||
}
|
}
|
||||||
@@ -159,24 +149,20 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
var parts []string
|
var parts []string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
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 {
|
||||||
@@ -196,12 +182,38 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
|||||||
|
|
||||||
// Import above functions in the context
|
// Import above functions in the context
|
||||||
func ImportStringFuncs(ctx ExprContext) {
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
newFuncParam(paramSeparator),
|
||||||
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
|
newFuncParamFlag(paramItem, pfRepeat),
|
||||||
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
})
|
||||||
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
|
|
||||||
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
|
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlagDef(paramStart, pfOptional, int64(0)),
|
||||||
|
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
|
||||||
|
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParam(paramPrefix),
|
||||||
|
newFuncParamFlag("other "+paramPrefix, pfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||||
|
newFuncParam(paramSource),
|
||||||
|
newFuncParam(paramSuffix),
|
||||||
|
newFuncParamFlag("other "+paramSuffix, pfRepeat),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the import function in the import-register.
|
// Register the import function in the import-register.
|
||||||
|
|||||||
@@ -1,89 +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)`)},
|
|
||||||
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
|
|
||||||
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
|
|
||||||
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
|
|
||||||
// /* 64 */ {`string(true)`, "true", nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Setenv("EXPR_PATH", ".")
|
|
||||||
|
|
||||||
//parserTest(t, "Func", inputs[54:55])
|
|
||||||
parserTest(t, "Func", inputs)
|
|
||||||
}
|
|
||||||
+249
@@ -0,0 +1,249 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- Function template
|
||||||
|
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||||
|
|
||||||
|
// ---- Common functor definition
|
||||||
|
type baseFunctor struct {
|
||||||
|
info ExprFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
|
||||||
|
if functor.info != nil {
|
||||||
|
s = functor.info.ToString(opt)
|
||||||
|
} else {
|
||||||
|
s = "func() {<body>}"
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
functor.info = info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) GetFunc() ExprFunc {
|
||||||
|
return functor.info
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Linking with Go functions
|
||||||
|
type golangFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
f FuncTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGolangFunctor(f FuncTemplate) *golangFunctor {
|
||||||
|
return &golangFunctor{f: f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return functor.f(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Linking with Expr functions
|
||||||
|
type exprFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
params []ExprFuncParam
|
||||||
|
expr Expr
|
||||||
|
defCtx ExprContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
|
||||||
|
// 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) {
|
||||||
|
if functor.defCtx != nil {
|
||||||
|
ctx.Merge(functor.defCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range functor.params {
|
||||||
|
if i < len(args) {
|
||||||
|
arg := args[i]
|
||||||
|
if funcArg, ok := arg.(Functor); ok {
|
||||||
|
// ctx.RegisterFunc(p, functor, 0, -1)
|
||||||
|
paramSpecs := funcArg.GetParams()
|
||||||
|
ctx.RegisterFunc(p.Name(), funcArg, typeAny, paramSpecs)
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p.Name(), arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p.Name(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result, err = functor.expr.eval(ctx, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Parameters
|
||||||
|
type paramFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
pfOptional paramFlags = 1 << iota
|
||||||
|
pfRepeat
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcParamInfo struct {
|
||||||
|
name string
|
||||||
|
flags paramFlags
|
||||||
|
defaultValue any
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParam(name string) ExprFuncParam {
|
||||||
|
return &funcParamInfo{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Functions
|
||||||
|
type funcInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
params []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
if params != nil {
|
||||||
|
for _, p := range params {
|
||||||
|
if maxArgs == -1 {
|
||||||
|
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsOptional() {
|
||||||
|
maxArgs++
|
||||||
|
} else if maxArgs == minArgs {
|
||||||
|
minArgs++
|
||||||
|
maxArgs++
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
minArgs--
|
||||||
|
maxArgs = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info = &funcInfo{
|
||||||
|
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||||
|
}
|
||||||
|
functor.SetFunc(info)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
return newFuncInfo("unnamed", functor, returnType, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if len(info.Name()) == 0 {
|
||||||
|
sb.WriteString("func")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(info.Name())
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
sb.WriteString(" {<body>}")
|
||||||
|
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
|
||||||
|
}
|
||||||
+21
-6
@@ -6,7 +6,7 @@ package expr
|
|||||||
|
|
||||||
import "path/filepath"
|
import "path/filepath"
|
||||||
|
|
||||||
var globalCtx *SimpleFuncStore
|
var globalCtx *SimpleStore
|
||||||
|
|
||||||
func ImportInContext(name string) (exists bool) {
|
func ImportInContext(name string) (exists bool) {
|
||||||
var mod *module
|
var mod *module
|
||||||
@@ -40,15 +40,30 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
globalCtx = NewSimpleFuncStore()
|
globalCtx = NewSimpleStore()
|
||||||
|
ImportBuiltinsFuncs(globalCtx)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EvalStringV(source string, args []Arg) (result any, err error) {
|
func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
if isFunc(arg.Value) {
|
if isFunc(arg.Value) {
|
||||||
if f, ok := arg.Value.(FuncTemplate); ok {
|
if f, ok := arg.Value.(FuncTemplate); ok {
|
||||||
functor := &simpleFunctor{f: f}
|
functor := newGolangFunctor(f)
|
||||||
ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-3
@@ -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
@@ -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
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
+3
-2
@@ -4,6 +4,7 @@
|
|||||||
// operand-dict.go
|
// operand-dict.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
|
||||||
// -------- dict term
|
// -------- dict term
|
||||||
func newDictTerm(args map[any]*term) *term {
|
func newDictTerm(args map[any]*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -19,7 +20,7 @@ func newDictTerm(args map[any]*term) *term {
|
|||||||
// -------- dict func
|
// -------- dict func
|
||||||
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
||||||
dict, _ := self.value().(map[any]*term)
|
dict, _ := self.value().(map[any]*term)
|
||||||
items := make(map[any]any, len(dict))
|
items := make(DictType, len(dict))
|
||||||
for key, tree := range dict {
|
for key, tree := range dict {
|
||||||
var param any
|
var param any
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
if param, err = tree.compute(ctx); err != nil {
|
||||||
@@ -28,7 +29,7 @@ func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
items[key] = param
|
items[key] = param
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v = items
|
v = &items
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -7,7 +7,8 @@ package expr
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// -------- expr term
|
// -------- expr term
|
||||||
func newExprTerm(tk *Token) *term {
|
func newExprTerm(root *term) *term {
|
||||||
|
tk := NewValueToken(root.tk.row, root.tk.col, SymExpression, root.source(), root)
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
parent: nil,
|
parent: nil,
|
||||||
@@ -20,8 +21,8 @@ func newExprTerm(tk *Token) *term {
|
|||||||
|
|
||||||
// -------- eval expr
|
// -------- eval expr
|
||||||
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if expr, ok := self.value().(Expr); ok {
|
if expr, ok := self.value().(*term); ok {
|
||||||
v, err = expr.eval(ctx, false)
|
v, err = expr.compute(ctx)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("expression expected, got %T", self.value())
|
err = fmt.Errorf("expression expected, got %T", self.value())
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-38
@@ -22,16 +22,25 @@ 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)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("unknown function %s()", name)
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
@@ -42,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 {
|
||||||
@@ -51,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, ¶ms); 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)
|
||||||
}
|
}
|
||||||
@@ -74,40 +83,23 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func def
|
// -------- eval func def
|
||||||
// TODO
|
|
||||||
type funcDefFunctor struct {
|
|
||||||
params []string
|
|
||||||
expr Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
for i, p := range functor.params {
|
|
||||||
if i < len(args) {
|
|
||||||
arg := args[i]
|
|
||||||
if functor, ok := arg.(Functor); ok {
|
|
||||||
ctx.RegisterFunc(p, functor, 0, -1)
|
|
||||||
} else {
|
|
||||||
ctx.setVar(p, arg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.setVar(p, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err = functor.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)
|
||||||
v = &funcDefFunctor{
|
if len(param.children) > 0 {
|
||||||
params: paramList,
|
flags |= pfOptional
|
||||||
expr: expr,
|
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, ctx)
|
||||||
} 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")
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-20
@@ -66,14 +66,16 @@ func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
||||||
if dictAny, ok := firstChildValue.(map[any]any); ok {
|
// if dictAny, ok := firstChildValue.(map[any]any); ok {
|
||||||
|
if dictAny, ok := firstChildValue.(*DictType); ok {
|
||||||
requiredFields := []string{currentName, nextName}
|
requiredFields := []string{currentName, nextName}
|
||||||
fieldsMask := 0b11
|
fieldsMask := 0b11
|
||||||
foundFields := 0
|
foundFields := 0
|
||||||
ds = make(map[string]Functor)
|
ds = make(map[string]Functor)
|
||||||
for keyAny, item := range dictAny {
|
for keyAny, item := range *dictAny {
|
||||||
if key, ok := keyAny.(string); ok {
|
if key, ok := keyAny.(string); ok {
|
||||||
if functor, ok := item.(*funcDefFunctor); ok {
|
//if functor, ok := item.(*funcDefFunctor); ok {
|
||||||
|
if functor, ok := item.(Functor); ok {
|
||||||
ds[key] = functor
|
ds[key] = functor
|
||||||
if index := slices.Index(requiredFields, key); index >= 0 {
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
foundFields |= 1 << index
|
foundFields |= 1 << index
|
||||||
@@ -146,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
-61
@@ -4,72 +4,14 @@
|
|||||||
// operand-list.go
|
// operand-list.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListType []any
|
|
||||||
|
|
||||||
func (ls *ListType) ToString(opt FmtOpt) 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(']')
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ls *ListType) String() string {
|
|
||||||
return ls.ToString(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -------- 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,
|
||||||
|
|||||||
+4
-4
@@ -8,16 +8,16 @@ import "fmt"
|
|||||||
|
|
||||||
// -------- variable term
|
// -------- variable term
|
||||||
func newVarTerm(tk *Token) *term {
|
func newVarTerm(tk *Token) *term {
|
||||||
return &term{
|
t := &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classVar,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: nil,
|
children: nil,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
priority: priValue,
|
priority: priValue,
|
||||||
evalFunc: evalVar,
|
evalFunc: evalVar,
|
||||||
}
|
}
|
||||||
|
t.tk.Sym = SymVariable
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
|
|||||||
+58
-13
@@ -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 != SymIdentifier {
|
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,22 +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 {
|
||||||
var minArgs, maxArgs int = 0, -1
|
if info := functor.GetFunc(); info != nil {
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
|
||||||
|
} else if funcDef, ok := functor.(*exprFunctor); ok {
|
||||||
|
paramSpecs := ForAll(funcDef.params, func(p ExprFuncParam) ExprFuncParam { return p })
|
||||||
|
|
||||||
funcName := rightChild.source()
|
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs)
|
||||||
if info, exists := ctx.GetFuncInfo(funcName); exists {
|
} else {
|
||||||
minArgs = info.MinArgs()
|
err = self.Errorf("unknown function %s()", rightChild.source())
|
||||||
maxArgs = info.MaxArgs()
|
|
||||||
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
|
|
||||||
l := len(funcDef.params)
|
|
||||||
minArgs = l
|
|
||||||
maxArgs = l
|
|
||||||
}
|
}
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.source(), v)
|
err = assignValue(ctx, leftTerm, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
v = nil
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-16
@@ -8,9 +8,7 @@ package expr
|
|||||||
|
|
||||||
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priCoalesce,
|
priority: priCoalesce,
|
||||||
@@ -26,7 +24,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -34,11 +32,7 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
// if _, ok := rightValue.(Functor); ok {
|
|
||||||
// err = errCoalesceNoFunc(self.children[1])
|
|
||||||
// } else {
|
|
||||||
v = rightValue
|
v = rightValue
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -63,7 +57,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
leftTerm := self.children[0]
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,20 +66,18 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = leftValue
|
v = leftValue
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if functor, ok := rightValue.(Functor); ok {
|
if functor, ok := rightValue.(Functor); ok {
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{
|
||||||
|
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
ctx.setVar(leftTerm.source(), rightValue)
|
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// utils
|
|
||||||
// func errCoalesceNoFunc(t *term) error {
|
|
||||||
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
||||||
|
|||||||
+4
-4
@@ -20,18 +20,18 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
var sourceCtx ExprContext
|
var sourceCtx ExprContext
|
||||||
if len(self.children) == 0 {
|
if self.children == nil || len(self.children) == 0 {
|
||||||
sourceCtx = ctx
|
sourceCtx = ctx
|
||||||
|
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
|
||||||
|
sourceCtx = globalCtx
|
||||||
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
||||||
if dc, ok := childValue.(*dataCursor); ok {
|
if dc, ok := childValue.(*dataCursor); ok {
|
||||||
sourceCtx = dc.ctx
|
sourceCtx = dc.ctx
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sourceCtx != nil {
|
if sourceCtx != nil {
|
||||||
if formatter, ok := ctx.(Formatter); ok {
|
if formatter, ok := sourceCtx.(Formatter); ok {
|
||||||
v = formatter.ToString(0)
|
v = formatter.ToString(0)
|
||||||
} else {
|
} else {
|
||||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||||
|
|||||||
+6
-53
@@ -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,39 +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 map[any]any:
|
|
||||||
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 *dataCursor:
|
|
||||||
// if indexTerm.symbol() == SymIdentifier {
|
|
||||||
// opName := indexTerm.source()
|
|
||||||
// if opName == resetName {
|
|
||||||
// _, err = unboxedValue.Reset()
|
|
||||||
// } else if opName == cleanName {
|
|
||||||
// _, err = unboxedValue.Clean()
|
|
||||||
// } else {
|
|
||||||
// err = indexTerm.Errorf("iterators do not support command %q", opName)
|
|
||||||
// }
|
|
||||||
// v = err == nil
|
|
||||||
// }
|
|
||||||
case ExtIterator:
|
case ExtIterator:
|
||||||
if indexTerm.symbol() == SymIdentifier {
|
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{})
|
||||||
@@ -88,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
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-351
@@ -4,204 +4,13 @@
|
|||||||
// operand-fraction.go
|
// operand-fraction.go
|
||||||
package expr
|
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 (
|
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- fraction term
|
// -------- fraction term
|
||||||
func newFractionTerm(tk *Token) *term {
|
func newFractionTerm(tk *Token) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -246,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)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-in.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- in term
|
||||||
|
|
||||||
|
func newInTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priRelational,
|
||||||
|
evalFunc: evalIn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func hasKey(d map[any]any, target any) (ok bool) {
|
||||||
|
// _, ok = d[target]
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsList(rightValue) {
|
||||||
|
list, _ := rightValue.(*ListType)
|
||||||
|
v = list.indexDeepSameCmp(leftValue) >= 0
|
||||||
|
} else if IsDict(rightValue) {
|
||||||
|
dict, _ := rightValue.(*DictType)
|
||||||
|
v = dict.hasKey(leftValue)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwIn, newInTerm)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -37,6 +37,9 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
list, _ := rightValue.(*ListType)
|
list, _ := rightValue.(*ListType)
|
||||||
newList := append(ListType{leftValue}, *list...)
|
newList := append(ListType{leftValue}, *list...)
|
||||||
v = &newList
|
v = &newList
|
||||||
|
if self.children[1].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(self.children[1].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
@@ -54,12 +57,33 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
list, _ := leftValue.(*ListType)
|
list, _ := leftValue.(*ListType)
|
||||||
newList := append(*list, rightValue)
|
newList := append(*list, rightValue)
|
||||||
v = &newList
|
v = &newList
|
||||||
|
if self.children[0].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(self.children[0].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
// var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if IsList(leftValue) {
|
||||||
|
// list, _ := leftValue.(*ListType)
|
||||||
|
// newList := append(*list, rightValue)
|
||||||
|
// v = &newList
|
||||||
|
// if
|
||||||
|
// } else {
|
||||||
|
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymInsert, newInsertTerm)
|
registerTermConstructor(SymInsert, newInsertTerm)
|
||||||
|
|||||||
+3
-2
@@ -30,8 +30,9 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
s, _ := childValue.(string)
|
s, _ := childValue.(string)
|
||||||
v = int64(len(s))
|
v = int64(len(s))
|
||||||
} else if IsDict(childValue) {
|
} else if IsDict(childValue) {
|
||||||
m, _ := childValue.(map[any]any)
|
// m, _ := childValue.(map[any]any)
|
||||||
v = int64(len(m))
|
m, _ := childValue.(*DictType)
|
||||||
|
v = int64(len(*m))
|
||||||
} else if it, ok := childValue.(Iterator); ok {
|
} else if it, ok := childValue.(Iterator); ok {
|
||||||
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
||||||
count, _ := extIt.CallOperation(countName, nil)
|
count, _ := extIt.CallOperation(countName, nil)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
|
|
||||||
if it, ok := childValue.(Iterator); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
v, err = it.Next()
|
v, err = it.Next()
|
||||||
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable {
|
||||||
v = childValue
|
v = childValue
|
||||||
i, _ := childValue.(int64)
|
i, _ := childValue.(int64)
|
||||||
ctx.SetVar(self.children[0].source(), i+1)
|
ctx.SetVar(self.children[0].source(), i+1)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-15
@@ -38,15 +38,11 @@ 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) {
|
} else if IsList(leftValue) && IsList(rightValue) {
|
||||||
var leftList, rightList *ListType
|
var leftList, rightList *ListType
|
||||||
var ok bool
|
leftList, _ = leftValue.(*ListType)
|
||||||
if leftList, ok = leftValue.(*ListType); !ok {
|
rightList, _ = rightValue.(*ListType)
|
||||||
leftList = &ListType{leftValue}
|
|
||||||
}
|
|
||||||
if rightList, ok = rightValue.(*ListType); !ok {
|
|
||||||
rightList = &ListType{rightValue}
|
|
||||||
}
|
|
||||||
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||||
for _, item := range *leftList {
|
for _, item := range *leftList {
|
||||||
sumList = append(sumList, item)
|
sumList = append(sumList, item)
|
||||||
@@ -55,19 +51,17 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
sumList = append(sumList, item)
|
sumList = append(sumList, item)
|
||||||
}
|
}
|
||||||
v = &sumList
|
v = &sumList
|
||||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
|
||||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
} else {
|
} else {
|
||||||
v, err = sumAnyFract(leftValue, rightValue)
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
} else if IsDict(leftValue) && IsDict(rightValue) {
|
} else if IsDict(leftValue) && IsDict(rightValue) {
|
||||||
leftDict, _ := leftValue.(map[any]any)
|
leftDict, _ := leftValue.(*DictType)
|
||||||
rightDict, _ := rightValue.(map[any]any)
|
rightDict, _ := rightValue.(*DictType)
|
||||||
c := CloneMap(leftDict)
|
c := leftDict.clone()
|
||||||
for key, value := range rightDict {
|
c.merge(rightDict)
|
||||||
c[key] = value
|
|
||||||
}
|
|
||||||
v = c
|
v = c
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
|||||||
@@ -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,11 +241,11 @@ 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 {
|
||||||
subtree = newDictTerm(args)
|
subtree = newDictTerm(args)
|
||||||
|
// subtree = newMapTerm(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -293,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 {
|
||||||
@@ -351,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
|
||||||
@@ -367,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)
|
||||||
@@ -389,7 +368,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
||||||
subTree.root.priority = priValue
|
subTree.root.priority = priValue
|
||||||
err = tree.addTerm(subTree.root)
|
err = tree.addTerm(newExprTerm(subTree.root))
|
||||||
currentTerm = subTree.root
|
currentTerm = subTree.root
|
||||||
}
|
}
|
||||||
case SymFuncCall:
|
case SymFuncCall:
|
||||||
@@ -400,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 {
|
||||||
@@ -438,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)
|
||||||
@@ -455,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()
|
||||||
@@ -463,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
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// simple-func-store.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SimpleFuncStore struct {
|
|
||||||
SimpleVarStore
|
|
||||||
funcStore map[string]*funcInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
type funcInfo struct {
|
|
||||||
name string
|
|
||||||
minArgs int
|
|
||||||
maxArgs int
|
|
||||||
functor Functor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *funcInfo) ToString(opt FmtOpt) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
var i int
|
|
||||||
sb.WriteString("func(")
|
|
||||||
for i = 0; i < info.minArgs; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
sb.WriteString(", ")
|
|
||||||
}
|
|
||||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
|
||||||
}
|
|
||||||
for ; i < info.maxArgs; i++ {
|
|
||||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
|
||||||
}
|
|
||||||
if info.maxArgs < 0 {
|
|
||||||
if info.minArgs > 0 {
|
|
||||||
sb.WriteString(", ")
|
|
||||||
}
|
|
||||||
sb.WriteString("...")
|
|
||||||
}
|
|
||||||
sb.WriteString(") {...}")
|
|
||||||
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
|
|
||||||
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
|
|
||||||
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) 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
|
|
||||||
}
|
|
||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// simple-store.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleStore struct {
|
||||||
|
varStore map[string]any
|
||||||
|
funcStore map[string]ExprFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSimpleStore() *SimpleStore {
|
||||||
|
ctx := &SimpleStore{
|
||||||
|
varStore: make(map[string]any),
|
||||||
|
funcStore: make(map[string]ExprFunc),
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRefName(name string) bool { return name[0] != '@' }
|
||||||
|
func filterPrivName(name string) bool { return name[0] != '_' }
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) Clone() ExprContext {
|
||||||
|
return &SimpleStore{
|
||||||
|
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
|
||||||
|
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) {
|
||||||
|
sb.WriteString("vars: {\n")
|
||||||
|
first := true
|
||||||
|
for _, name := range ctx.EnumVars(filterPrivName) {
|
||||||
|
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 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))
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
sb.WriteString(formatter.ToString(0))
|
||||||
|
} else {
|
||||||
|
sb.WriteString(fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString("\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
varsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
funcsCtxToBuilder(&sb, ctx, 0)
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||||
|
v, exists = ctx.varStore[varName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
ctx.varStore[varName] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) 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 *SimpleStore) 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 *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||||
|
info, exists = ctx.funcStore[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
|
||||||
|
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
|
||||||
|
var info *funcInfo
|
||||||
|
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
|
||||||
|
ctx.funcStore[name] = info
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) 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 *SimpleStore) Call(name string, args []any) (result any, err error) {
|
||||||
|
if info, exists := GetLocalFuncInfo(ctx, name); exists {
|
||||||
|
functor := info.Functor()
|
||||||
|
result, err = functor.Invoke(ctx, name, args)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,121 +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) setVar(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) 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()
|
|
||||||
}
|
|
||||||
@@ -72,6 +72,7 @@ const (
|
|||||||
SymIdentifier
|
SymIdentifier
|
||||||
SymBool
|
SymBool
|
||||||
SymInteger
|
SymInteger
|
||||||
|
SymVariable
|
||||||
SymFloat
|
SymFloat
|
||||||
SymFraction
|
SymFraction
|
||||||
SymString
|
SymString
|
||||||
@@ -84,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> "}"
|
||||||
@@ -99,6 +101,7 @@ const (
|
|||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
SymKwBuiltin
|
SymKwBuiltin
|
||||||
|
SymKwIn
|
||||||
SymKwInclude
|
SymKwInclude
|
||||||
SymKwNil
|
SymKwNil
|
||||||
)
|
)
|
||||||
@@ -112,6 +115,7 @@ func init() {
|
|||||||
"BUILTIN": SymKwBuiltin,
|
"BUILTIN": SymKwBuiltin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
|
"IN": SymKwIn,
|
||||||
"INCLUDE": SymKwInclude,
|
"INCLUDE": SymKwInclude,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
"OR": SymKwOr,
|
"OR": SymKwOr,
|
||||||
|
|||||||
@@ -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 (
|
||||||
@@ -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,9 +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},
|
||||||
|
/* 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
|
||||||
@@ -41,7 +45,7 @@ func TestDictParser(t *testing.T) {
|
|||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("var1", int64(123))
|
ctx.SetVar("var1", int64(123))
|
||||||
ctx.SetVar("var2", "abc")
|
ctx.SetVar("var2", "abc")
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
@@ -101,3 +105,49 @@ func TestDictParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDictToStringMultiLine(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-ML"
|
||||||
|
want := `{
|
||||||
|
"first": 1
|
||||||
|
}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(MultiLine)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(MultiLine): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDictToString(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-SL"
|
||||||
|
want := `{"first": 1}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(0)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(0): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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,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,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 (
|
||||||
@@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
|
|||||||
|
|
||||||
func TestEvalString(t *testing.T) {
|
func TestEvalString(t *testing.T) {
|
||||||
|
|
||||||
ctx := NewSimpleVarStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("a", uint8(1))
|
ctx.SetVar("a", uint8(1))
|
||||||
ctx.SetVar("b", int8(2))
|
ctx.SetVar("b", int8(2))
|
||||||
ctx.SetVar("f", 2.0)
|
ctx.SetVar("f", 2.0)
|
||||||
@@ -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,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"
|
||||||
@@ -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,38 +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},
|
||||||
|
/* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), 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
|
||||||
|
|
||||||
@@ -57,9 +77,7 @@ func TestListParser(t *testing.T) {
|
|||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("var1", int64(123))
|
|
||||||
ctx.SetVar("var2", "abc")
|
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
@@ -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")
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -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},
|
||||||
@@ -98,7 +100,7 @@ func TestGeneralParser(t *testing.T) {
|
|||||||
/* 77 */ {`5 % 2`, int64(1), nil},
|
/* 77 */ {`5 % 2`, int64(1), nil},
|
||||||
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
||||||
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
||||||
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
|
||||||
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
||||||
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
||||||
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, 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},
|
||||||
@@ -137,33 +139,31 @@ func TestGeneralParser(t *testing.T) {
|
|||||||
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||||
/* 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 */ {`{}`, map[any]any{}, 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", ".")
|
||||||
parserTest(t, "General", inputs)
|
// parserTestSpec(t, section, inputs, 102)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
for _, count := range spec {
|
||||||
|
good := doTest(t, section, &inputs[count-1], count)
|
||||||
|
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parserTest(t *testing.T, section string, inputs []inputType) {
|
func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||||
@@ -172,37 +172,7 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
|||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
for i, input := range inputs {
|
for i, input := range inputs {
|
||||||
var expr Expr
|
good := doTest(t, section, &input, i+1)
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, section, 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
|
||||||
|
|
||||||
if !eq /*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 {
|
if good {
|
||||||
succeeded++
|
succeeded++
|
||||||
} else {
|
} else {
|
||||||
@@ -212,6 +182,40 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
|||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
|
||||||
|
var expr Expr
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
ctx := NewSimpleStore()
|
||||||
|
parser := NewParser(ctx)
|
||||||
|
|
||||||
|
logTest(t, count, section, 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||||
|
|
||||||
|
if !eq /*gotResult != 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotErr != input.wantErr {
|
||||||
|
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||||
if wantErr == nil {
|
if wantErr == nil {
|
||||||
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||||
@@ -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,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 (
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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,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 (
|
||||||
@@ -31,6 +31,7 @@ func TestGetRoom(t *testing.T) {
|
|||||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetChildrenCount(t *testing.T) {
|
func TestGetChildrenCount(t *testing.T) {
|
||||||
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
|
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
|
||||||
|
|
||||||
@@ -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 (
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_utils_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsString(t *testing.T) {
|
||||||
|
count := 0
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsBool(true) {
|
||||||
|
t.Errorf("%d: IsBool(true) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsString("abc") {
|
||||||
|
t.Errorf("%d: IsString(\"abc\") -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsInteger(int64(123)) {
|
||||||
|
t.Errorf("%d: IsInteger(123) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsFloat(1.23) {
|
||||||
|
t.Errorf("%d: IsFloat(1.23) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if !IsFloat(numAsFloat(123)) {
|
||||||
|
t.Errorf("%d: IsFloat(numAsFloat(123)) -> result = false, want true", count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
if isIterator("fake") {
|
||||||
|
t.Errorf(`%d: isIterator("fake") -> result = true, want false`, count)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
b, ok := toBool(true)
|
||||||
|
if !(ok && b) {
|
||||||
|
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
|
||||||
|
count++
|
||||||
|
b, ok = toBool("abc")
|
||||||
|
if !(ok && b) {
|
||||||
|
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToIntOk(t *testing.T) {
|
||||||
|
source := int64(64)
|
||||||
|
wantValue := int(64)
|
||||||
|
wantErr := error(nil)
|
||||||
|
|
||||||
|
gotValue, gotErr := toInt(source, "test")
|
||||||
|
|
||||||
|
if gotErr != nil || gotValue != wantValue {
|
||||||
|
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||||
|
source, gotValue, gotErr, wantValue, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToIntErr(t *testing.T) {
|
||||||
|
source := uint64(64)
|
||||||
|
wantValue := 0
|
||||||
|
wantErr := errors.New(`test expected integer, got uint64 (64)`)
|
||||||
|
|
||||||
|
gotValue, gotErr := toInt(source, "test")
|
||||||
|
|
||||||
|
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
|
||||||
|
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||||
|
source, gotValue, gotErr, wantValue, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnyInteger(t *testing.T) {
|
||||||
|
type inputType struct {
|
||||||
|
source any
|
||||||
|
wantValue int64
|
||||||
|
wantOk bool
|
||||||
|
}
|
||||||
|
section := "utils.anyInteger"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {int8(-8), int64(-8), true},
|
||||||
|
/* 2 */ {int16(-16), int64(-16), true},
|
||||||
|
/* 3 */ {int32(-32), int64(-32), true},
|
||||||
|
/* 4 */ {int64(-64), int64(-64), true},
|
||||||
|
/* 5 */ {uint8(8), int64(8), true},
|
||||||
|
/* 6 */ {uint16(16), int64(16), true},
|
||||||
|
/* 7 */ {uint32(32), int64(32), true},
|
||||||
|
/* 8 */ {uint64(64), int64(64), true},
|
||||||
|
/* 9 */ {int(-1), int64(-1), true},
|
||||||
|
/* 10 */ {true, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
gotValue, gotOk := anyInteger(input.source)
|
||||||
|
if gotOk != input.wantOk || gotValue != input.wantValue {
|
||||||
|
t.Errorf("%d: anyInteger(%v) -> gotOk = %t, wantOk = %t; gotValue = %v, wantValue = %v",
|
||||||
|
i+1, input.source, gotOk, input.wantOk, gotValue, input.wantValue)
|
||||||
|
failed++
|
||||||
|
} else {
|
||||||
|
succeeded++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ type termPriority uint32
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
priNone termPriority = iota
|
priNone termPriority = iota
|
||||||
|
priRange
|
||||||
priBut
|
priBut
|
||||||
priAssign
|
priAssign
|
||||||
priOr
|
priOr
|
||||||
@@ -155,23 +156,27 @@ 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 := typeName(leftValue)
|
||||||
|
leftText := getFormatted(leftValue, Truncate)
|
||||||
|
rightType := typeName(rightValue)
|
||||||
|
rightText := getFormatted(rightValue, Truncate)
|
||||||
return self.tk.Errorf(
|
return self.tk.Errorf(
|
||||||
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
|
||||||
leftValue, leftValue,
|
leftText, leftType,
|
||||||
rightValue, rightValue,
|
rightText, rightType,
|
||||||
self.source())
|
self.source())
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -224,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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,17 +35,17 @@ func IsList(v any) (ok bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsDict(v any) (ok bool) {
|
func IsDict(v any) (ok bool) {
|
||||||
_, ok = v.(map[any]any)
|
_, ok = v.(*DictType)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -147,6 +147,12 @@ func fromGenericAny(v any) (exprAny any, ok bool) {
|
|||||||
if exprAny, ok = anyFloat(v); ok {
|
if exprAny, ok = anyFloat(v); ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if exprAny, ok = v.(*DictType); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exprAny, ok = v.(*ListType); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,8 +204,18 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
||||||
|
result := make([]V, len(ts))
|
||||||
|
for i, t := range ts {
|
||||||
|
result[i] = fn(t)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user