Compare commits

...

178 Commits

Author SHA1 Message Date
camoroso 4aa0113c6a new string test file 2024-05-10 07:33:59 +02:00
camoroso d035fa0d5e operator-dot.go: fixed string index 2024-05-10 07:32:45 +02:00
camoroso cf73b5c98d Doc: add link to dev-expr download page 2024-05-10 06:39:48 +02:00
camoroso 8346e28340 Doc: fixed some typo and added some list details 2024-05-10 04:49:03 +02:00
camoroso 9c2eca40d7 utils.go: added IsBool() and IsFract() 2024-05-10 04:48:13 +02:00
camoroso c3198e4c79 parser_test.go: did not show the correct section name 2024-05-10 04:47:34 +02:00
camoroso 99e0190b9c operator-dot.go: fixed the index range checking 2024-05-10 04:45:51 +02:00
camoroso d4f63a3837 funcs_test.go: fixed and extended 2024-05-10 04:44:08 +02:00
camoroso 389d48b646 func-base.go: added functions to check data-type of a value 2024-05-10 04:43:14 +02:00
camoroso 1f7b9131fc The 'last' variable now also receives the value of the final expression 2024-05-10 04:41:26 +02:00
camoroso dce49fd2b7 Doc: fractions and dictionaries 2024-05-09 07:20:22 +02:00
camoroso 8ee0bb5701 Some data-type check functions (e.g. IsInteger()) exported 2024-05-08 07:53:01 +02:00
camoroso b2b0bb04c5 formatter.go: number base options added 2024-05-08 07:51:38 +02:00
camoroso f3abf5e77c Doc: dev-expr description updated 2024-05-08 07:51:01 +02:00
camoroso 34b7799177 parser_test.go: test added (mixed number types) 2024-05-07 07:23:38 +02:00
camoroso 8c3c54913a Doc: dev-expr introduction 2024-05-07 07:22:32 +02:00
camoroso 5910345c08 operator-{sum,prod}.go: Fixed sum and prod operation with a fraction and a float 2024-05-06 17:32:39 +02:00
camoroso 8b4dad1381 utils.go: new function isNumOrFract(x); it returns true if x is a float, an integer, ora a fraction 2024-05-06 17:31:12 +02:00
camoroso 6ef468408c operator-sum.go: Fixed sum of fraction and float 2024-05-06 17:16:30 +02:00
camoroso 3a30d890c6 operator-assign.go: improvements that allow to define function aliases (TODO: maybe *funcDefFunctor else-if section can be removed 2024-05-06 16:01:50 +02:00
camoroso 71ab417a56 funcs_test.go: added many new tests 2024-05-06 15:32:44 +02:00
camoroso 7cfb89c25c parser_test.go: removed commented code 2024-05-06 15:32:00 +02:00
camoroso 569dbfda9d expr_test.go: removed temporary test 2024-05-06 15:31:28 +02:00
camoroso f342dfe9f3 operand-list.go: ListType constructor functions newList() and newListA() 2024-05-06 15:30:23 +02:00
camoroso 5378952394 func-string.go: added three new functions (splitStr, startsWithStr, endsWithString) to the 'string' builtin module 2024-05-06 15:29:13 +02:00
camoroso a9d6a82011 operand-func.go: improved error report when functions reveive less params than expected 2024-05-06 15:26:45 +02:00
camoroso 43b74131fb dict_test.go: first draft 2024-05-06 10:41:08 +02:00
camoroso cb0eac54b2 list_test.go: new tests on member access 2024-05-06 10:40:19 +02:00
camoroso b219b55878 parser_test.go: refactored 2024-05-06 10:39:21 +02:00
camoroso 56c86f917e funcs_test.go: Added Setenv("EXPR_PATH",".") 2024-05-06 10:38:45 +02:00
camoroso 539a4b44e9 new test files and some refactorings 2024-05-06 05:52:25 +02:00
camoroso 74df927179 func-import.go: renamed include() as importAll() 2024-05-06 05:50:36 +02:00
camoroso 510966c497 operator-insert.go: replaced []any with ListType 2024-05-06 04:43:29 +02:00
camoroso c977e82d9e doc/Expr.adoc: description of the selector operator; added operators to the priority table 2024-05-06 04:24:27 +02:00
camoroso 903f1ae1ce adjusted some error messages and added the section name in logs 2024-05-06 04:21:50 +02:00
camoroso 9c66056c18 func-math.go: improved error messages reporting wrong datatype of items 2024-05-06 04:18:04 +02:00
camoroso f55a48aa26 operator-dot.go: replaced []any with *ListType 2024-05-06 04:15:46 +02:00
camoroso 434ddee733 operator-context.go: changed priPrePost with priIncDec 2024-05-06 04:14:54 +02:00
camoroso fcced6149f operator-post-inc.go: changed priPrePost with priIncDec 2024-05-06 04:14:03 +02:00
camoroso 1f0f9cae22 term.go: priPrePost renamed as priIncDec 2024-05-06 04:11:43 +02:00
camoroso 1d8569d3a9 the function call procedure now check the number of actual parameters against the numer of formal parameters 2024-05-04 22:35:03 +02:00
camoroso 0fdd51049d parser.go: simplified the parser of the function parameters 2024-05-04 19:10:02 +02:00
camoroso f9ed5776cd token.go: function IsSymbol() 2024-05-04 19:08:02 +02:00
camoroso a2c0a24494 added symbol '..' and '...'; improved some error reports in parser.go 2024-05-04 18:47:00 +02:00
camoroso 2c5f02cc69 list iterators now support start, stop e step 2024-05-04 08:07:49 +02:00
camoroso d9fbe6f36d data-cursor.go: now supports the 'index' command 2024-05-04 01:24:13 +02:00
camoroso e6174aca82 operator-length.go: now returns index+1 when used with iterators; that is the number of iterations 2024-05-04 01:23:32 +02:00
camoroso a736bba2c7 iter-list.go: now supports the 'index' command 2024-05-04 01:21:36 +02:00
camoroso 7724cabdcc removed commented code 2024-05-04 00:57:21 +02:00
camoroso 16557d70de the iterator operator now can be defined providing values or a list of values directly 2024-05-04 00:51:15 +02:00
camoroso 04e71a1b3f operator-iter-value.go: now operates on Iterator instead of *dataCursor 2024-05-04 00:47:20 +02:00
camoroso e463bd61d8 operator-post-inc.go: now operates on Iterator instead of *dataCursor 2024-05-04 00:39:22 +02:00
camoroso 419af7bfea data-cursor.go: reset and clean operations return (bool,error) 2024-05-04 00:37:31 +02:00
camoroso 6c604812ee iter-list.go: implemntation of the ExtInterface and coperation reset 2024-05-04 00:35:27 +02:00
camoroso 5cf0bfbad4 func-math.go: use of ExtIterator to perform clean operation 2024-05-04 00:33:38 +02:00
camoroso a838361ea8 operator-dot.go: specific *dataCursor case replaced by general ExtIterator 2024-05-04 00:30:35 +02:00
camoroso 0dbb0ba515 iterator.go: new interface ExtIterator derived from Iterator 2024-05-04 00:28:17 +02:00
camoroso 7a0ba26aa3 some expression and data files used by tests 2024-05-03 08:48:29 +02:00
camoroso 0bca3333aa parser_test.go: added tests from 143 to 159 (global coverage is 74.4%) 2024-05-03 06:38:30 +02:00
camoroso 02b7a6df6c improved the string representation of the content 2024-05-03 06:33:47 +02:00
camoroso 8d9963207e operator-context.go: now supports contextes that implement the Formatter interface 2024-05-03 06:31:36 +02:00
camoroso f9486fa1bd operand-list.go: adding of String() function 2024-05-03 06:29:18 +02:00
camoroso 360ebce015 improved usability of the list iterator 2024-05-03 06:26:17 +02:00
camoroso dc9eca83e8 operator-builtin.go: Fixed a problem with the list of forms due to changing []any in ListType 2024-05-03 05:30:58 +02:00
camoroso 2c55167dd0 operator-fraction.go: the constructor newFraction() does call simplifyIntegers() on the num e den 2024-05-02 11:04:20 +02:00
camoroso c124e880c4 operator-context.go: now it returns function keys too 2024-05-02 11:02:01 +02:00
camoroso 4db015e4b1 func-math.go: Fixed mul() error: the previous version recursively called doAdd() instead of doMul() 2024-05-02 10:58:05 +02:00
camoroso 5809de419f func-math.go: mul() and add() now support fractions 2024-05-01 21:53:54 +02:00
camoroso 7c748f0e31 formatter.go: format options added 2024-05-01 21:53:03 +02:00
camoroso cd6b7982ee sum and prod now support fraction too 2024-05-01 21:51:43 +02:00
camoroso e00886b1ed operator-fraction.go: many new functions supporting fraction operations 2024-05-01 21:49:15 +02:00
camoroso 49904f9097 the new ListType type takes the of []any 2024-05-01 21:47:27 +02:00
camoroso 2d0d03b975 utils.go: numAsFloat() now supports also fraction 2024-05-01 21:44:55 +02:00
camoroso 8cb048edb0 common-type-names.go: typeFraction added 2024-05-01 21:43:42 +02:00
camoroso cb3d8827fa operator-fraction.go -- new data-type and operator fraction 2024-05-01 09:48:09 +02:00
camoroso 4c83764332 term.go: priFraction added 2024-05-01 09:47:28 +02:00
camoroso c0c2ab8b4e func-bolerplate.bash: generate Go source for a new functions module 2024-05-01 07:13:52 +02:00
camoroso 288e93d708 func-string.go: string utils module 2024-05-01 07:10:59 +02:00
camoroso 92e862da19 common definitions collections 2024-05-01 07:10:11 +02:00
camoroso dc6975e56c term.go: better error message in term.toInt() 2024-05-01 07:09:18 +02:00
camoroso 6a2d3c53fd func-common.go common-errors.go 2024-05-01 07:09:01 +02:00
camoroso 5643a57bcc utils.go: new function toInt() converts any(int64) to int 2024-05-01 07:07:36 +02:00
camoroso 52fb398cd8 data-cursor.go: deep changes to better handle the data-source 2024-05-01 05:59:54 +02:00
camoroso cdbe3dfc22 commented tracing code 2024-05-01 05:57:08 +02:00
camoroso 7aabd068ed func-math.go: add() now supports general iterators. TODO: do the same thing to mul() 2024-05-01 05:55:37 +02:00
camoroso 924f5da725 Clone functions now filter ref variables to avoid to pass them to child context 2024-05-01 05:53:56 +02:00
camoroso d657cbb51e func-os.go: fixed the handling of err==io.EOF 2024-05-01 05:48:37 +02:00
camoroso be874503ec iterator.go: new error function errInvalidDataSource() 2024-05-01 05:46:24 +02:00
camoroso 056d42d328 utils.go: Copy and Clone maps with filter function. Also added isIterator(). 2024-05-01 05:45:10 +02:00
camoroso aa66d07caa Iterators now support generic operation interface based on methods HasOperation() and CallOperation().
Also fixed mul() function that called doAdd() instead of doMul().
2024-04-28 06:45:39 +02:00
camoroso fc0e1ffaee New function isFunctor() 2024-04-28 06:43:57 +02:00
camoroso bf8f1a175f modules are now represented by a struct that holds import-function, module description and importa status.
It is also available the new function IterateModules() to explore all modules.
2024-04-28 05:41:13 +02:00
camoroso 6dd8283308 function-register.go module-register.go 2024-04-28 04:52:02 +02:00
camoroso 06ab303b9e accessing to the Reset() and Clean()functions of an iterator is now done by identifiers, i.e. reset, not by string, i.e. "reset". 2024-04-28 04:44:19 +02:00
camoroso 2ccbdb2254 Fixed Clean() and added error message when calling optional missing functions 2024-04-28 04:41:43 +02:00
camoroso c5fca70cfc operator-length.go: Fix: the returned value was int, instead of int64 2024-04-27 22:37:56 +02:00
camoroso 895778f236 operator-dot.go: '*dataCursor' case added; to be enhanced 2024-04-27 22:34:23 +02:00
camoroso 81c85afbea operand-iterator.go: accepts two new optional functions 'reset' and 'clean' from data-source 2024-04-27 22:32:57 +02:00
camoroso 354cb79580 data-cursor.go: added Reset() e Clean() functions 2024-04-27 22:31:14 +02:00
camoroso 327bffa01f symbol.go: typo 2024-04-27 22:30:18 +02:00
camoroso c99be491df operator-dot.go: refactoring 2024-04-27 14:44:52 +02:00
camoroso 60effe8f1b new prefix operator 'include' 2024-04-27 09:48:45 +02:00
camoroso 824b9382be termo.go: changed the name of a variable 2024-04-27 09:48:05 +02:00
camoroso 9ce6b7255b corrected a comment 2024-04-27 09:47:24 +02:00
camoroso 9dbf472630 helpers.go: two new functions: EvalStream() and EvalFile() 2024-04-27 09:46:03 +02:00
camoroso 723976b37e merged with datasource-context 2024-04-27 06:19:12 +02:00
camoroso 361b84f31f moved the exportVar() and exportFunc() functions from data-cursor.go to context-helpers.go 2024-04-27 06:16:11 +02:00
camoroso 70892aa980 removed commented code 2024-04-27 06:14:09 +02:00
camoroso 10eec286fa new operator '111123' that returns the content of the current context or the context of an iterator 2024-04-27 06:12:30 +02:00
camoroso 894b1884eb temporary 2024-04-26 21:03:22 +02:00
camoroso d2bab5fd9e context clone & export function moved from operand-func.go to context-helpers.go 2024-04-26 21:03:00 +02:00
camoroso f94f369547 context clone & export function moved from operand-func.go to context-helpers.go 2024-04-26 20:12:56 +02:00
camoroso 107ec4958f operator-length.go: the prefix operator '#' now accept iterator oeprand; it returns the index of the current item 2024-04-26 08:04:55 +02:00
camoroso a22047e84e operand-func.go: removed commented code 2024-04-26 08:02:52 +02:00
camoroso 80b7d5b988 operand-dict.go: removed commented code 2024-04-26 08:02:22 +02:00
camoroso 2ab896bbac the dataCursor struct has been moved fro operand-iterator.go to the new file data-cursor.go 2024-04-26 08:01:35 +02:00
camoroso 62e16219f7 func-math.go/add: now supports the general iterator interface 2024-04-26 04:47:59 +02:00
camoroso 750c660331 first working implementation of iterators and some fixes to the parser around lists analysis 2024-04-26 04:45:42 +02:00
camoroso 7a88449cd1 updated some error messages; add some tests on the dict data-type; Use of reflect.DeepEqual() to compare the test results with the desired results. 2024-04-26 04:43:36 +02:00
camoroso b14dc2f1ee provisional implementation of the postfix ++ operator 2024-04-26 04:37:50 +02:00
camoroso d354102c6a adapted to the new GetFuncInfo() specification 2024-04-26 04:36:03 +02:00
camoroso 761ec868e6 operator-assign.go: little refactor 2024-04-26 04:31:31 +02:00
camoroso 7941c2dfec the FlatArrayIterator has been moved to the new file iter-list.go 2024-04-26 04:30:43 +02:00
camoroso ebb2811ed3 context.go: added exists return value to the GetFuncInfo() 2024-04-26 04:28:50 +02:00
camoroso 75c0c0f681 Fix ast.go: the insert() didn't check the returned error 2024-04-26 04:26:20 +02:00
camoroso 268a968548 term.go: added two new priorities (priIterValue and priPrePost); new function term.symbol()) 2024-04-26 04:23:39 +02:00
camoroso 323308d86f expressions now support dict data-type 2024-04-21 14:24:56 +02:00
camoroso b28d6a8f02 commented out a test for a future new operator based on the caret symbol 2024-04-21 07:12:59 +02:00
camoroso ab82bcf1ef preparation for the definition of the iterators 2024-04-21 07:11:58 +02:00
camoroso a628bfac39 New symbol '^' (caret). The scanner now returns an error token if can't recognise a symbol. 2024-04-21 07:10:19 +02:00
camoroso d1122da566 coalesce operators '??' and '?=' now accepts function definitions too 2024-04-20 09:40:07 +02:00
camoroso 6ae5ca34ed expr_test.go, more tests on the int() function 2024-04-20 08:50:05 +02:00
camoroso 730b59e6d3 funcs_test.go, more tests on the int() function 2024-04-20 07:41:58 +02:00
camoroso f198ba47e1 new convert function int() 2024-04-20 07:29:42 +02:00
camoroso 943ef3327e empty expressions no more return error, now they return nil 2024-04-20 06:56:26 +02:00
camoroso 475ef3c80a parser_test.go: test added on isNil() function 2024-04-20 06:54:51 +02:00
camoroso 3c0307524b simple-func-store.go: now imports 'builtins' module on creation 2024-04-20 06:53:30 +02:00
camoroso c27e487fc3 new function isNil() 2024-04-20 06:52:33 +02:00
camoroso ed973c9b7b fixed all errors in test files 2024-04-20 06:04:35 +02:00
camoroso 15bbfacd47 all constant value are now stored in the same data struct (same constructor). Also nil const added 2024-04-20 05:39:49 +02:00
camoroso 04f934ab04 'nil' keyword added 2024-04-20 05:38:00 +02:00
camoroso 591b4ffc19 Expr.adoc: 'work in progress' added 2024-04-19 14:30:40 +02:00
camoroso fe9ab9ebd2 Expr.adoc, corrected a typo 2024-04-19 09:20:01 +02:00
camoroso 7198749063 new operator dot '.' used to select an item or character from a list or a string respectively 2024-04-19 09:05:26 +02:00
camoroso b76481bbf2 new operator 'builtin' 2024-04-19 00:19:11 +02:00
camoroso 4f05e5c90a funcs-math.go registered as 'math.arith' 2024-04-19 00:18:23 +02:00
camoroso 8ad25afdc4 function register 2024-04-19 00:16:49 +02:00
camoroso 54bc759f70 expressions now support intger value in bin (0b), oct (0o), and hex (0x) form 2024-04-17 14:15:50 +02:00
camoroso b6887af77a added the prefix operator '#' (length of string and array) 2024-04-17 07:12:32 +02:00
camoroso 353d495c50 two new operators added: '<<' and '>> 2024-04-16 03:54:50 +02:00
camoroso f45b2c0a88 func-os.go: set of functions to work with files 2024-04-15 07:01:34 +02:00
camoroso 624e3ac0f2 parser_test.go: changed ImportImportFunc(s) 2024-04-15 07:00:40 +02:00
camoroso 35fcbd2bce expr_test.go: added a test in passing functions as parameters 2024-04-15 06:59:27 +02:00
camoroso 2150181303 func-import.go: renamed ImportImportFunc as ImportImportFuncs 2024-04-15 06:57:29 +02:00
camoroso d643e24a1b doc: Syntax of expressions moved from README.adoc to doc/Expr.doc 2024-04-14 08:16:01 +02:00
camoroso 43e631f2e8 enabled passing functions as parameters in function call 2024-04-14 07:38:28 +02:00
camoroso f1afbf9b49 parser.go: removed commented code 2024-04-14 07:35:43 +02:00
camoroso ed6af6603a commented out some test code 2024-04-13 22:15:20 +02:00
camoroso 51e740d243 expr_test.go: fixed test expression 2024-04-13 22:14:28 +02:00
camoroso 53dacd5332 operator-selector.go: fixed the case match algorithm 2024-04-13 22:13:16 +02:00
camoroso e297f2a1d3 expr_test.go: new test file to verify complex expressions 2024-04-13 10:11:44 +02:00
camoroso 70cdb9367e changed the structer of the selector components: now all case are a list value assigned as right operand of the selector operator 2024-04-13 10:10:25 +02:00
camoroso efd9af9030 graph.go: remove unused Reticle code 2024-04-13 06:05:04 +02:00
camoroso b67d896415 Merge branch 'main' into feat_graph 2024-04-13 06:01:06 +02:00
camoroso f03ae555fc added the copywrite comment header in source files that lacked it 2024-04-13 06:00:22 +02:00
camoroso f36ae33acc added the graph source files 2024-04-13 05:54:34 +02:00
camoroso b9ea96f649 merge: solved a lot of conflicts caused by an incorect commit removal 2024-04-13 05:47:10 +02:00
camoroso 838ab9fd7e control: exported all control variables by renaming them in upper case 2024-04-13 05:16:23 +02:00
camoroso ad6b4b872f token_test.go: rewritten using a more structured form 2024-04-13 05:08:38 +02:00
camoroso c75e6485e0 term_test.go: changed some fmt.Println() to t.Log() 2024-04-13 05:07:38 +02:00
camoroso 7b80f7f03b helpers_test.go: removed the printed messages "Hello World!" 2024-04-13 05:06:43 +02:00
camoroso 7f9fd570b2 parser_test.go: changed the notice message about EXPR_PATH 2024-04-13 04:40:48 +02:00
camoroso b17d2250a4 parser.go: fixed a problem on the selector operator 2024-04-13 04:37:15 +02:00
camoroso dda10749d0 token.go: better implementation of the function String() 2024-04-13 04:33:59 +02:00
camoroso 07ca84170e operand-selector-case.go: String() function added to the selectorCase type 2024-04-13 04:32:54 +02:00
camoroso 55e136e9bc ast.go: String() function addedd to the Expr interface 2024-04-13 04:31:53 +02:00
camoroso e493c40c7b builtin-funcs: export import functions (made their names uppercase) 2024-04-13 04:18:14 +02:00
camoroso 95605232ab README.adoc: updated draft 2024-04-09 09:11:47 +02:00
77 changed files with 5798 additions and 808 deletions
+8 -227
View File
@@ -1,5 +1,5 @@
= Expr
Expressions calculator
= README
README about the Expressions calculator
:authors: Celestino Amoroso
:docinfo: shared
:encoding: utf-8
@@ -135,232 +135,13 @@ func main() {
}
----
=== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
== Context of evaluation
Unless helpers functions like _expr.EvalStringA()_ are used, a _context_ is required to compute an expession.
==== Numbers
Numbers can be integers (GO int64) or float (GO float64). In mixed operations involving integers and floats, integers are automatically promoted to floats.
.Arithmetic operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ +
[blue]`-(+2)` _[-2]_
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` _[1]_ +
[blue]`4 + 0.5` _[4.5]_
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` _[2]_ +
[blue]`4 - 0.5` _[3.5]_
| [blue]`*` | _product_ | Multiply two values | `-1 * 2` _[-2]_ +
[blue]`4 * 0.5` _[2.0]_
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`-1 / 2` _[0]_ +
[blue]`1.0 / 2` _[0.5]_
| [blue]`./` | _Float division_ | Force float division | [blue]`-1 ./ 2` _[-0.5]_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` _[1]_
|===
==== String
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
Some arithmetic operators can also be used with strings.
.String operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
[blue]`"one" + 2` _["one2"]_
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|===
==== Boolean
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
.Relational operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ +
[blue]`"a" == "a"` _[true]_
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` _[true]_ +
[blue]`"a" != "a"` _[false]_
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ +
[blue]`"a" < "b"` _[true]_
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` _[false]_ +
[blue]`"b" \<= "b"` _[true]_
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ +
[blue]`"a" < "b"` _[false]_
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` _[true]_ +
[blue]`"b" \<= "b"` _[true]_
|===
.Boolean operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ +
[blue]`NOT (2 < 1)` _[true]_
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` _[false]_ +
[blue]`"a" < "b" AND NOT (2 < 1)` _[true]_
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` _[true]_ +
[blue]`"a" == "b" OR (2 == 1)` _[false]_
|===
[CAUTION]
====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of operators [blue]`and` and [blue]`or` is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
.Example
[source,go]
----
2 > (a=1) or (a=8) > 0; a // <1>
----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
====
==== List
_Expr_ supports list of mixed-type values, also specified by normal expressions.
.List examples
[source,go]
----
[1, 2, 3] // List of integers
["one", "two", "three"] // List of strings
["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
----
.List operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [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] ]_
|===
=== Variables
#TODO: variables#
=== Other operations
==== [blue]`;` operator
The semicolon operator [blue]`;` is an infixed operator. It evaluates the left expression first and then the right expression. The latter is the final result.
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.
.Example
[source,go]
----
a=1; b=2; c=3; a+b+c // returns 6
----
==== [blue]`but` operator
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`;(` and [blue]`)`.
==== Assignment operator [blue]`=`
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (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.
.Example
[source,go]
----
a=15+1 // returns 16
----
==== Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Syntax
[source,bnf]
----
<selector-operator> ::= <expression> "?" <selector-case> { ":" <selector-case> } ["::" <case-value>]
<selector-case> ::= [<list>] <case-value>
<case-value> ::= "{" <multi-expr> "}"
<multi-expr> ::= <expr> {";" <expr>}
----
.Example
[source,go]
----
1 ? {"a"} : {"b"} // returns "b"
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
----
=== Priorities of operators
The table below shows all supported operators by decreasing priorities.
.Operators priorities
[cols="^2,^2,^2,^5,<5"]
|===
| Priority | Operators | Position | Operation | Operands and results
1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ "!" -> _integer_
1+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| ("+"\|"-") _number_ -> _number_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ "*" _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ "*" _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ "/" _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ "./" _number_ -> _float_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ "%" _integer_ -> _integer_
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ "+" _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) "+" (_string_\|_number_) -> _string_
| [blue]`+` | _Infix_ | _List-join_ | _list_ "+" _list_ -> _list_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ "-" _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ "-" _list_ -> _list_
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ "<" _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ "\<=" _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ ">" _comparable_ -> _boolean_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ ">=" _comparable_ -> _boolean_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ "==" _comparable_ -> _boolean_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ "!=" _comparable_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | "not" _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
| [blue]`||` | _Infix_ | _or_ | _boolean_ "||" _boolean_ -> _boolean_
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|===
=== Functions
==== Function calls
#TODO: function calls operations#
==== Function definitions
#TODO: function definitions operations#
==== Builtins
#TODO: builtins#
A context is an object that implements the _expr.ExprContext_ interface. This interface specifies a set of function to handle variables and functions.
Variables and functions can be added to a context both programmatically and ad an effect of the expression computation.
== Expressions syntax
See #TODO link to doc/Expr.html#
+8 -6
View File
@@ -5,13 +5,13 @@
package expr
import (
"errors"
"strings"
)
type Expr interface {
Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error)
String() string
}
//-------- ast
@@ -63,7 +63,7 @@ func (self *ast) addToken2(tk *Token) (t *term, err error) {
if t = newTerm(tk, nil); t != nil {
err = self.addTerm(t)
} else {
err = tk.Errorf("No term constructor for token %q", tk.String())
err = tk.Errorf("unexpected token %q", tk.String())
}
return
}
@@ -83,8 +83,9 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
subRoot, err = self.insert(last, node)
if subRoot, err = self.insert(last, node); err == nil {
subRoot.setParent(tree)
}
} else {
node.setParent(tree)
}
@@ -118,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil {
ctx.setVar(control_last_result, result)
ctx.setVar(ControlLastResult, result)
} else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
break
@@ -127,9 +128,10 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
}
if err == nil {
result, err = self.root.compute(ctx)
ctx.setVar(ControlLastResult, result)
}
} else {
err = errors.New("empty expression")
// } else {
// err = errors.New("empty expression")
}
return
}
+1 -1
View File
@@ -44,7 +44,7 @@ func TestAddTokensBad(t *testing.T) {
func TestAddUnknownTokens(t *testing.T) {
tk0 := NewToken(0, 0, SymPercent, "%")
wantErr := errors.New(`No term constructor for token "%"`)
wantErr := errors.New(`unexpected token "%"`)
tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-errors.go
package expr
import (
"fmt"
)
// --- General errors
func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
}
func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
}
// --- Parameter errors
func errOneParam(funcName string) error {
return fmt.Errorf("%s() requires exactly one param", funcName)
}
func errMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
}
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
}
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)
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-params.go
package expr
const (
paramParts = "parts"
paramSeparator = "separator"
paramSource = "source"
)
+14
View File
@@ -0,0 +1,14 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-type-names.go
package expr
const (
typeBoolean = "boolean"
typeFloat = "decimal"
typeFraction = "fraction"
typeInt = "integer"
typeNumber = "number"
typeString = "string"
)
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context-helpers.go
package expr
func cloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
if sourceCtx != nil {
clonedCtx = sourceCtx.Clone()
}
return
}
func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := isEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
// fmt.Printf("\tExporting %q\n", refName)
refValue, _ := sourceCtx.GetVar(refName)
exportVar(destCtx, refName, refValue)
}
// Export functions
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
exportFunc(destCtx, refName, info)
}
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ type ExprContext interface {
setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) ExprFunc
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
}
+13 -10
View File
@@ -1,13 +1,16 @@
// preset.go
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// control.go
package expr
import "strings"
// Preset control variables
const (
control_last_result = "_last"
control_bool_shortcut = "_bool_shortcut"
control_import_path = "_import_path"
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlImportPath = "_import_path"
)
// Other control variables
@@ -21,23 +24,23 @@ const (
)
func initDefaultVars(ctx ExprContext) {
ctx.setVar(control_bool_shortcut, true)
ctx.setVar(control_import_path, init_import_path)
ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlImportPath, init_import_path)
}
func enable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.setVar(name, true)
ctx.SetVar(name, true)
} else {
ctx.setVar("_"+name, true)
ctx.SetVar("_"+name, true)
}
}
func disable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.setVar(name, false)
ctx.SetVar(name, false)
} else {
ctx.setVar("_"+name, false)
ctx.SetVar("_"+name, false)
}
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// data-cursors.go
package expr
import (
"errors"
"io"
)
type dataCursor struct {
ds map[string]Functor
ctx ExprContext
index int
resource any
nextFunc Functor
cleanFunc Functor
resetFunc Functor
currentFunc Functor
}
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
dc = &dataCursor{
ds: ds,
index: -1,
ctx: ctx.Clone(),
}
return
}
// func mapToString(m map[string]Functor) string {
// var sb strings.Builder
// sb.WriteByte('{')
// for key, _ := range m {
// if sb.Len() > 1 {
// sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
// } else {
// sb.WriteString(fmt.Sprintf("%q: func(){}", key))
// }
// }
// sb.WriteByte('}')
// return sb.String()
// }
func (dc *dataCursor) String() string {
return "$()"
/*
var sb strings.Builder
sb.WriteString(fmt.Sprintf(`$(
index: %d,
ds: %s,
ctx: `, dc.index, mapToString(dc.ds)))
CtxToBuilder(&sb, dc.ctx, 1)
sb.WriteByte(')')
return sb.String()
*/
}
func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == indexName
if !exists {
f, ok := dc.ds[name]
exists = ok && isFunctor(f)
}
return
}
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if name == indexName {
value = int64(dc.Index())
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc {
value, err = dc.Clean()
} else if functor == dc.resetFunc {
value, err = dc.Reset()
} else {
ctx := cloneContext(dc.ctx)
value, err = functor.Invoke(ctx, name, []any{})
exportObjects(dc.ctx, ctx)
}
} else {
err = errNoOperation(name)
}
return
}
func (dc *dataCursor) Reset() (success bool, err error) {
if dc.resetFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
dc.index = -1
}
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errNoOperation(resetName)
}
success = err == nil
return
}
func (dc *dataCursor) Clean() (success bool, err error) {
if dc.cleanFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
dc.resource = nil
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errors.New("no 'clean' function defined in the data-source")
}
success = err == nil
return
}
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx)
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
err = io.EOF
}
exportObjects(dc.ctx, ctx)
return
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
}
}
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else {
err = errInvalidDataSource()
}
return
}
func (dc *dataCursor) Index() int {
return dc.index
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict_test.go
package expr
import (
"errors"
"strings"
"testing"
)
func TestDictParser(t *testing.T) {
section := "Dict"
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil},
/* 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},
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
// /* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
// /* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
// /* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// /* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
// /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
// /* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
// /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
// /* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
// /* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 14 */ {`[1,2,3].1`, int64(2), nil},
// /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
// /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
}
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "Dict", 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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
+447
View File
@@ -0,0 +1,447 @@
= Expr
Expressions calculator
:authors: Celestino Amoroso
:docinfo: shared
:encoding: utf-8
:toc: right
:toclevels: 4
//:toc-title: Indice Generale
:icons: font
:icon-set: fi
:numbered:
//:table-caption: Tabella
//:figure-caption: Diagramma
:docinfo1:
:sectlinks:
:sectanchors:
:source-highlighter: rouge
// :rouge-style: ThankfulEyes
:rouge-style: gruvbox
// :rouge-style: colorful
//:rouge-style: monokay
toc::[]
#TODO: Work in progress (last update on 2024/05/10, 06:52 a.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology
#TODO#
image::expression-diagram.png[]
=== `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, beyond in additon to the automatic test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
It cat work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
Here are some examples of execution.
.Run `dev-expr` in REPL mode and ask for help
[source,shell]
----
# Assume the expr source directory. Type 'exit' or Ctrl+D to quit the program.
[user]$ tools/expr -- Expressions calculator v1.7.0,2024/05/08 (celestino.amoroso@portale-stac.it)
Type help to get the list of command.
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help
--- REPL commands:
base -- Set the integer output base: 2, 8, 10, or 16
exit -- Exit the program
help -- Show command list
ml -- Enable/Disable multi-line output
mods -- List builtin modules
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
--- Command line options:
-b <builtin> Import builtin modules.
<builtin> can be a list of module names or a glob-pattern.
Use the special value 'all' or the pattern '*' to import all modules.
-e <expression> Evaluate <expression> instead of standard-input
-i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu
-m, --modules List all builtin modules
-p Print prefix form
-t Print tree form <2>
>>>
----
<1> Only available for single fraction values
<2> Work in progress
.REPL examples
[source,shell]
----
[user]$ tools/expr -- Expressions calculator v1.6.1,2024/05/06 (celestino.amoroso@portale-stac.it)
Type help to get the list of command.
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> 2+3
5
>>> 2+3*(4-1.5)
9.5
>>> 0xFD + 0b1 + 0o1 <1>
255
>>> 1|2 + 2|3 <2>
7|6
>>> ml <3>
>>> 1|2 + 2|3
7
-
6
>>> 1+2 but 5|2+0.5 <4>
3
>>> 1+2; 5|2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
<2> Fractions: numerator | denominator.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
=== Numbers
Numbers can be integers (GO int64) or float (GO float64). In mixed operations involving integers and floats, integers are automatically promoted to floats.
.Arithmetic operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ +
[blue]`-(+2)` _[-2]_
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` _[1]_ +
[blue]`4 + 0.5` _[4.5]_
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` _[2]_ +
[blue]`4 - 0.5` _[3.5]_
| [blue]`*` | _product_ | Multiply two values | `-1 * 2` _[-2]_ +
[blue]`4 * 0.5` _[2.0]_
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`-1 / 2` _[0]_ +
[blue]`1.0 / 2` _[0.5]_
| [blue]`./` | _Float division_ | Force float division | [blue]`-1 ./ 2` _[-0.5]_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` _[1]_
|===
=== Fractions
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
.Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` +
[green]`1|2` +
`>>>` [blue]`4|6` +
[green]`2|3` [gray]_Fractions are always reduced to their lowest terms_ +
`>>>` [blue]`1|2 + 2|3` +
[green]`7|6` +
`>>>` [blue]`1|2 * 2|3` +
[green]`1|3` +
`>>>` [blue]`1|2 / 1|3` +
[green]`3|2` +
`>>>` [blue]`1|2 ./ 1|3` [gray]_Force decimal division_ +
[green]`1.5` +
`>>>` [blue]`-1|2` +
[green]`-1|2` +
`>>>` [blue]`1|-2` [gray]_Wrong sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
`>>>` [blue]`1|(-2)` +
[green]`-1|2`
// ----
Fractions can be used together with integers and floats in expressions.
`>>>` [blue]`1|2 + 5` +
[green]`11|2` +
`>>>` [blue]`4 - 1|2` +
[green]`7|2` +
`>>>` [blue]`1.0 + 1|2` +
[green]`1.5` +
=== Strings
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
Some arithmetic operators can also be used with strings.
.String operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
[blue]`"one" + 2` _["one2"]_
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|===
=== Boolean
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
.Relational operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ +
[blue]`"a" == "a"` _[true]_
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` _[true]_ +
[blue]`"a" != "a"` _[false]_
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ +
[blue]`"a" < "b"` _[true]_
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` _[false]_ +
[blue]`"b" \<= "b"` _[true]_
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ +
[blue]`"a" < "b"` _[false]_
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` _[true]_ +
[blue]`"b" \<= "b"` _[true]_
|===
.Boolean operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ +
[blue]`NOT (2 < 1)` _[true]_
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` _[false]_ +
[blue]`"a" < "b" AND NOT (2 < 1)` _[true]_
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` _[true]_ +
[blue]`"a" == "b" OR (2 == 1)` _[false]_
|===
[CAUTION]
====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of operators [blue]`and` and [blue]`or` is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
.Example
[source,go]
----
2 > (a=1) or (a=8) > 0; a // <1>
----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
====
=== Lists
_Expr_ supports list of mixed-type values, also specified by normal expressions.
.List examples
[source,go]
----
[1, 2, 3] // List of integers
["one", "two", "three"] // List of strings
["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
----
.List operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [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] ]_
|===
The items of array can be accessed using the dot `.` operator.
.Item access syntax
[source,bnf]
----
<item> ::= <list-expr>"."<index-expr>
----
.Items of list
[source,go]
----
`>>>` [blue]`[1,2,3].1`
[green]`2`
`>>>` [blue]`list=[1,2,3]; list.1`
[green]`2`
`>>>` [blue]`["one","two","three"].1`
[green]`two`
`>>>` [blue]`list=["one","two","three"]; list.(2-1)`
[green]`two`
`>>>` [blue]`list.(-1)`
[green]`three`
`>>>` [blue]`list.(-1)`
[green]`three`
`>>>` [blue]`list.(10)`
----
== Dictionaries
The _dictionary_ data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. Dictionary literals are sequences of pairs separated by comma `,`; sequences are enclosed between brace brackets.
.List examples
[source,go]
----
{1:"one", 2:"two"}
{"one":1, "two": 2}
{"sum":1+2+3, "prod":1*2*3}
----
WARNING: Support for dictionaries is still ongoing.
== Variables
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
.Examples
[source,go]
----
a=1
x = 5.2 * (9-3)
x = 1; y = 2*x
----
== Other operations
=== [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.
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.
.Example
[source,go]
----
a=1; b=2; c=3; a+b+c // returns 6
----
=== [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 very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [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 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
[source,go]
----
a=15+1 // returns 16
----
=== Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Syntax
[source,bnf]
----
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>]
<selector-case> ::= [<match-list>] <case-value>
<match-list> ::= "["<item>{","<items>}"]"
<item> ::= <expression
<case-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 find 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 `:` 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.
.Examples
[source,go]
----
1 ? {"a"} : {"b"} // returns "b"
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
----
== Priorities of operators
The table below shows all supported operators by decreasing priorities.
.Operators priorities
[cols="^2,^2,^2,^5,^5"]
|===
| Priority | Operators | Position | Operation | Operands and results
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|===
== Functions
Functions in _Expr_ are very similar to functions in many programming languages.
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.
=== Function calls
#TODO: function calls operations#
=== Function definitions
#TODO: function definitions operations#
== Builtins
#TODO: builtins#
=== Builtin functions
=== [blue]_import()_
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
+1497
View File
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+82
View File
@@ -0,0 +1,82 @@
// 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)
}
+33
View File
@@ -0,0 +1,33 @@
builtin ["os.file", "base"];
readInt=func(fh){
line=readFile(fh);
line ? [nil] {nil} :: {int(line)}
};
ds={
"init":func(filename){
fh=openFile(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
fh
},
"current":func(){
prev
},
"next":func(fh){
current ?
[nil] {current}
:: {@prev=current; @current=readInt(fh) but current}
},
"clean":func(fh){
closeFile(fh)
}
}
//;f=$(ds, "int.list")
/*
;f++
;f++
;f++
*/
//;add(f)
+20
View File
@@ -0,0 +1,20 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// formatter.go
package expr
type FmtOpt uint16
const (
TTY FmtOpt = 1 << iota
MultiLine
Base2
Base8
Base10
Base16
)
type Formatter interface {
ToString(options FmtOpt) string
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-builtins.go
package expr
import (
"math"
"strconv"
)
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
result = args[0] == nil
} else {
err = errOneParam(name)
}
return
}
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsInteger(args[0])
return
}
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsFloat(args[0])
return
}
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsBool(args[0])
return
}
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsString(args[0])
return
}
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsFract(args[0])
return
}
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsList(args[0])
return
}
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsDict(args[0])
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
switch v := args[0].(type) {
case int64:
result = v
case float64:
result = int64(math.Trunc(v))
case bool:
if v {
result = int64(1)
} else {
result = int64(0)
}
case string:
var i int
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
}
} else {
err = errOneParam(name)
}
return
}
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
}
func init() {
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}
+13 -6
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-import.go
package expr
@@ -17,7 +20,7 @@ func importFunc(ctx ExprContext, name string, args []any) (result any, err error
return importGeneral(ctx, name, args)
}
func includeFunc(ctx ExprContext, name string, args []any) (result any, err error) {
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
enable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
@@ -27,12 +30,12 @@ func importGeneral(ctx ExprContext, name string, args []any) (result any, err er
dirList = addEnvImportDirs(dirList)
dirList = addPresetImportDirs(ctx, dirList)
result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args))
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
return
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isString(paramValue) /*|| isList(paramValue)*/) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
@@ -51,7 +54,7 @@ func addEnvImportDirs(dirList []string) []string {
}
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, control_import_path); exists {
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
@@ -133,7 +136,11 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
return
}
func importImportFunc(ctx ExprContext) {
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
}
func init() {
registerImport("import", ImportImportFuncs, "Functions import() and include()")
}
+90 -21
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs-math.go
package expr
@@ -6,36 +9,65 @@ import (
"io"
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isNumber(paramValue) || isList(paramValue)) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
funcName, paramPos+1, subPos+1, level, paramValue)
}
return
}
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var sumAsFloat = false
func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result any, err error) {
var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0
var intSum int64 = 0
var fractSum *fraction
var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
if list, ok := v.(*ListType); ok {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(Iterator); ok {
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
return
}
}
} else if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
break
}
}
count++
if !sumAsFloat && isFloat(v) {
if !sumAsFloat {
if IsFloat(v) {
sumAsFloat = true
if sumAsFract {
floatSum = fractSum.toFloat()
} else {
floatSum = float64(intSum)
}
} else if !sumAsFract && isFraction(v) {
fractSum = newFraction(intSum, 1)
sumAsFract = true
}
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else if sumAsFract {
var item *fraction
var ok bool
if item, ok = v.(*fraction); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
fractSum = sumFract(fractSum, item)
} else {
iv, _ := v.(int64)
intSum += iv
@@ -45,6 +77,8 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
err = nil
if sumAsFloat {
result = floatSum
} else if sumAsFract {
result = fractSum
} else {
result = intSum
}
@@ -53,33 +87,62 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
}
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1)
return
}
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
var mulAsFloat = false
func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result any, err error) {
var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0
var intProd int64 = 1
var fractProd *fraction
var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
if list, ok := v.(*ListType); ok {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(Iterator); ok {
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
return
}
}
} else {
if err = checkNumberParamExpected(name, v, count, level, it.Index()); err != nil {
break
}
}
count++
if !mulAsFloat && isFloat(v) {
if !mulAsFloat {
if IsFloat(v) {
mulAsFloat = true
if mulAsFract {
floatProd = fractProd.toFloat()
} else {
floatProd = float64(intProd)
}
} else if !mulAsFract && isFraction(v) {
fractProd = newFraction(intProd, 1)
mulAsFract = true
}
}
if mulAsFloat {
floatProd *= numAsFloat(v)
} else if mulAsFract {
var item *fraction
var ok bool
if item, ok = v.(*fraction); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
fractProd = mulFract(fractProd, item)
} else {
iv, _ := v.(int64)
intProd *= iv
@@ -89,6 +152,8 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
err = nil
if mulAsFloat {
result = floatProd
} else if mulAsFract {
result = fractProd
} else {
result = intProd
}
@@ -97,11 +162,15 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
}
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doMul(ctx, name, NewFlatArrayIterator(args))
result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1)
return
}
func importMathFuncs(ctx ExprContext) {
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
}
func init() {
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()")
}
+171
View File
@@ -0,0 +1,171 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-os.go
package expr
import (
"bufio"
"fmt"
"io"
"os"
)
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
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)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush()
}
if err == nil {
err = fh.Close()
}
}
} else {
err = fmt.Errorf("%s(): invalid file handle", name)
}
result = err == nil
return
}
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
result, err = fmt.Fprint(w.writer, args[1:]...)
}
}
}
return
}
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
result = nil
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if r, ok := handle.(*osReader); ok {
var limit byte = '\n'
var v string
if len(args) > 1 {
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 {
result = v[0 : len(v)-1]
} else {
result = v
}
}
if err == io.EOF {
err = nil
}
}
}
}
return
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
}
func init() {
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
}
+211
View File
@@ -0,0 +1,211 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-string.go
package expr
import (
"fmt"
"io"
"strings"
)
// --- Start of function definitions
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
var sb strings.Builder
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if it.Index() > 0 {
sb.WriteString(sep)
}
if s, ok := v.(string); ok {
sb.WriteString(s)
} else {
err = errExpectedGot(funcName, typeString, v)
return
}
}
if err == nil || err == io.EOF {
err = nil
result = sb.String()
}
return
}
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSeparator)
}
if sep, ok := args[0].(string); ok {
if len(args) == 1 {
result = ""
} else if len(args) == 2 {
if ls, ok := args[1].(*ListType); ok {
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
} else if it, ok := args[1].(Iterator); ok {
result, err = doJoinStr(name, sep, it)
} else {
err = errInvalidParameterValue(name, paramParts, args[1])
}
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
}
} else {
err = errWrongParamType(name, paramSeparator, typeString, args[0])
}
return
}
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var start = 0
var count = -1
var source string
var ok bool
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
if len(args) > 1 {
if start, err = toInt(args[1], name+"()"); err != nil {
return
}
if len(args) > 2 {
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
}
if start < 0 {
start = len(source) + start
}
}
if count < 0 {
count = len(source) - start
}
end := min(start+count, len(source))
result = source[start:end]
return
}
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
result = strings.TrimSpace(source)
return
}
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
result = false
if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok {
if strings.HasPrefix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
break
}
}
return
}
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
result = false
if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok {
if strings.HasSuffix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
break
}
}
return
}
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source, sep string
var count int = -1
var parts []string
var ok bool
if len(args) < 1 {
return result, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
if len(args) >= 2 {
if sep, ok = args[1].(string); !ok {
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 count > 0 {
parts = strings.SplitN(source, sep, count)
} else if count < 0 {
parts = strings.Split(source, sep)
} else {
parts = []string{}
}
list := make(ListType, len(parts))
for i, part := range parts {
list[i] = part
}
result = &list
return
}
// --- End of function definitions
// Import above functions in the context
func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
registerImport("string", ImportStringFuncs, "string utilities")
}
+72
View File
@@ -0,0 +1,72 @@
// 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},
/* 46 */ {`isFloat(3.1)`, true, nil},
/* 47 */ {`isString("3.1")`, true, nil},
/* 48 */ {`isString("3" + 1)`, true, nil},
/* 49 */ {`isList(["3", 1])`, true, nil},
/* 50 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 51 */ {`isFract(3|1)`, true, nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTest(t, "Func", inputs[25:26])
parserTest(t, "Func", inputs)
}
+122
View File
@@ -0,0 +1,122 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph.go
package expr
import (
"fmt"
"strings"
)
func BuildGraph(root *term) (g string) {
//var sb strings.Builder
g = fmt.Sprintf("tree: %v", root)
r := NewReticle(root)
fmt.Println(r)
return
}
type NodeRef struct {
node *term
pos int
label string
}
type Level []*NodeRef
type Reticle struct {
levels []Level
left, right int
colsWidth []int
}
func NewExprReticle(e Expr) *Reticle {
tree, _ := e.(*ast)
return NewReticle(tree.root)
}
func NewReticle(tree *term) *Reticle {
r := &Reticle{levels: make([]Level, 0)}
r.build(tree, 0, 0)
r.computeCharWidth()
return r
}
func (r *Reticle) computeCharWidth() {
numCol := r.right - r.left + 1
r.colsWidth = make([]int, numCol)
for _, level := range r.levels {
for _, ref := range level {
c := ref.pos - r.left
if v := ref.node.value(); v != nil {
ref.label = fmt.Sprintf("%v", v)
} else {
ref.label = ref.node.source()
}
r.colsWidth[c] = max(r.colsWidth[c], len(ref.label)+2) // +2 to make room for brakets
}
}
}
func (r *Reticle) build(node *term, depth, pos int) {
var level Level
if node == nil {
return
}
if len(r.levels) == depth {
level = make(Level, 0)
r.levels = append(r.levels, level)
} else {
level = r.levels[depth]
}
ref := &NodeRef{node: node, pos: pos}
level = append(level, ref)
if r.left > pos {
r.left = pos
}
if r.right < pos {
r.right = pos
}
if node.children != nil {
halfPos := len(node.children) / 2
childPos := pos - halfPos - 1
for _, child := range node.children {
childPos++
if childPos == pos && (len(node.children)&1) == 0 {
childPos++
}
r.build(child, depth+1, childPos)
}
}
r.levels[depth] = level
}
func (r *Reticle) nodeInLevel(level []*NodeRef, index int) (node *NodeRef) {
for _, ref := range level {
if ref.pos-r.left == index {
node = ref
break
}
}
return
}
func (r *Reticle) String() string {
var sb strings.Builder
for _, level := range r.levels {
for j, w := range r.colsWidth {
var label string
if ref := r.nodeInLevel(level, j); ref != nil {
s := "(" + ref.label + ")"
label = fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(s))/2, s))
} else {
label = strings.Repeat(" ", w)
}
sb.WriteString(label)
}
sb.WriteByte('\n')
}
return sb.String()
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph_test.go
package expr
import (
"fmt"
"testing"
)
func TestGraph(t *testing.T) {
// tk0 := NewToken(0, 0, SymChangeSign, "-")
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "200", 200)
tree := NewAst()
// tree.addToken(tk0)
tree.addToken(tk1)
tree.addToken(tk2)
tree.addToken(tk3)
tk4 := NewToken(0, 0, SymPlus, "-")
tk5 := NewValueToken(0, 0, SymInteger, "50", 50)
tree.addToken(tk4)
tree.addToken(tk5)
g := BuildGraph(tree.root)
fmt.Println(g)
}
+26
View File
@@ -1,8 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers.go
package expr
import (
"fmt"
"io"
"os"
"strings"
)
@@ -56,3 +61,24 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
}
return
}
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
var tree *ast
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx)
}
return
}
func EvalFile(ctx ExprContext, filePath string) (result any, err error) {
var fh *os.File
if fh, err = os.Open(filePath); err != nil {
return nil, err
}
defer fh.Close()
result, err = EvalStream(ctx, fh)
return
}
+3 -2
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers_test.go
package expr
@@ -45,7 +48,6 @@ func TestEvalStringA(t *testing.T) {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
fmt.Println("Hello World!")
}
func TestEvalString(t *testing.T) {
@@ -76,5 +78,4 @@ func TestEvalString(t *testing.T) {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
fmt.Println("Hello World!")
}
+4
View File
@@ -0,0 +1,4 @@
10
20
5
12
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// it-range.go
package expr
+132
View File
@@ -0,0 +1,132 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-list.go
package expr
import (
"fmt"
"io"
)
type ListIterator struct {
a *ListType
count int
index int
start int
stop int
step int
}
func NewListIterator(list *ListType, args []any) (it *ListIterator) {
var argc int = 0
listLen := len(([]any)(*list))
if args != nil {
argc = len(args)
}
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 {
if i, err := toInt(args[0], "start index"); err == nil {
if i < 0 {
i = listLen + i
}
it.start = i
}
if argc >= 2 {
if i, err := toInt(args[1], "stop index"); err == nil {
if i < 0 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := toInt(args[2], "step"); err == nil {
if i < 0 {
i = -i
}
if it.start > it.stop {
it.step = -i
} else {
it.step = i
}
}
}
}
}
it.index = it.start - it.step
return
}
func NewArrayIterator(array []any) (it *ListIterator) {
it = &ListIterator{a: (*ListType)(&array), count: 0, index: -1, start: 0, stop: len(array) - 1, step: 1}
return
}
func NewAnyIterator(value any) (it *ListIterator) {
if value == nil {
it = NewArrayIterator([]any{})
} else if list, ok := value.(*ListType); ok {
it = NewListIterator(list, nil)
} else if array, ok := value.([]any); ok {
it = NewArrayIterator(array)
} else if it1, ok := value.(*ListIterator); ok {
it = it1
} else {
it = NewArrayIterator([]any{value})
}
return
}
func (it *ListIterator) String() string {
var l = 0
if it.a != nil {
l = len(*it.a)
}
return fmt.Sprintf("$(#%d)", l)
}
func (it *ListIterator) HasOperation(name string) bool {
yes := name == resetName || name == indexName || name == countName
return yes
}
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
switch name {
case resetName:
v, err = it.Reset()
case indexName:
v = int64(it.Index())
case countName:
v = it.count
default:
err = errNoOperation(name)
}
return
}
func (it *ListIterator) Current() (item any, err error) {
a := *(it.a)
if it.index >= 0 && it.index <= it.stop {
item = a[it.index]
} else {
err = io.EOF
}
return
}
func (it *ListIterator) Next() (item any, err error) {
it.index += it.step
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *ListIterator) Index() int {
return it.index
}
func (it *ListIterator) Reset() (bool, error) {
it.index = it.start
return true, nil
}
+18
View File
@@ -0,0 +1,18 @@
ds={
"init":func(end){@end=end; @current=0; @prev=@current},
"current":func(){prev},
"next":func(){
(current <= end) ? [true] {@current=current+1; @prev=current} :: {nil}
},
"reset":func(){@current=0; @prev=@current}
}
// Example
//;
//it=$(ds,3);
//it++;
//it."reset"
//it++;
//it++;
//add(it)
+28 -23
View File
@@ -1,37 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator.go
package expr
import "io"
import (
"errors"
"fmt"
)
// Operator names
const (
initName = "init"
cleanName = "clean"
resetName = "reset"
nextName = "next"
currentName = "current"
indexName = "index"
countName = "count"
)
type Iterator interface {
Reset()
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
}
type FlatArrayIterator struct {
a []any
index int
type ExtIterator interface {
Iterator
HasOperation(name string) bool
CallOperation(name string, args []any) (value any, err error)
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
func errNoOperation(name string) error {
return fmt.Errorf("no %q function defined in the data-source", name)
}
func (it *FlatArrayIterator) Reset() {
it.index = 0
}
func (it *FlatArrayIterator) Next() (item any, err error) {
if it.index < len(it.a) {
item = it.a[it.index]
it.index++
} else {
err = io.EOF
}
return
}
func (it *FlatArrayIterator) Index() int {
return it.index - 1
func errInvalidDataSource() error {
return errors.New("invalid data-source")
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator_test.go
package expr
import "testing"
func TestIteratorParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
/* 2 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
/* 4 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
}
// inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil},
// }
parserTest(t, "Iterator", inputs)
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list_test.go
package expr
import (
"errors"
"strings"
"testing"
)
func TestListParser(t *testing.T) {
section := "List"
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 14 */ {`[1,2,3].1`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
}
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "List", 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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// module-register.go
package expr
import (
"fmt"
"path/filepath"
)
type module struct {
importFunc func(ExprContext)
description string
imported bool
}
func newModule(importFunc func(ExprContext), description string) *module {
return &module{importFunc, description, false}
}
var moduleRegister map[string]*module
func registerImport(name string, importFunc func(ExprContext), description string) {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
if _, exists := moduleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
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) {
if op != nil {
for name, mod := range moduleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
}
+7 -48
View File
@@ -4,52 +4,10 @@
// operand-const.go
package expr
// -------- bool const term
func newBoolTerm(tk *Token) *term {
// -------- const term
func newConstTerm(tk *Token) *term {
return &term{
tk: *tk,
// class: classConst,
// kind: kindBool,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- integer const term
func newIntegerTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- float const term
func newFloatTerm(tk *Token) *term {
return &term{
tk: *tk,
// class: classConst,
// kind: kindFloat,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- string const term
func newStringTerm(tk *Token) *term {
return &term{
tk: *tk,
// class: classConst,
// kind: kindString,
parent: nil,
children: nil,
position: posLeaf,
@@ -66,8 +24,9 @@ func evalConst(ctx ExprContext, self *term) (v any, err error) {
// init
func init() {
registerTermConstructor(SymString, newStringTerm)
registerTermConstructor(SymInteger, newIntegerTerm)
registerTermConstructor(SymFloat, newFloatTerm)
registerTermConstructor(SymBool, newBoolTerm)
registerTermConstructor(SymString, newConstTerm)
registerTermConstructor(SymInteger, newConstTerm)
registerTermConstructor(SymFloat, newConstTerm)
registerTermConstructor(SymBool, newConstTerm)
registerTermConstructor(SymKwNil, newConstTerm)
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-dict.go
package expr
// -------- dict term
func newDictTerm(args map[any]*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymDict, "{}", args),
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalDict,
}
}
// -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) {
dict, _ := self.value().(map[any]*term)
items := make(map[any]any, len(dict))
for key, tree := range dict {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
items[key] = param
}
if err == nil {
v = items
}
return
}
-5
View File
@@ -27,8 +27,3 @@ func evalExpr(ctx ExprContext, self *term) (v any, err error) {
}
return
}
// init
// func init() {
// registerTermConstructor(SymExpression, newExprTerm)
// }
+31 -37
View File
@@ -6,14 +6,13 @@ package expr
import (
"errors"
"fmt"
)
// -------- function call term
func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil,
children: args,
position: posLeaf,
@@ -23,9 +22,28 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
}
// -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
if info, exists := ctx.GetFuncInfo(name); exists {
if info.MinArgs() > len(params) {
if info.MaxArgs() < 0 {
err = fmt.Errorf("too few params -- expected %d or more, got %d", info.MinArgs(), len(params))
} else {
err = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
}
}
if info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := parentCtx.Clone()
ctx := cloneContext(parentCtx)
name, _ := self.tk.Value.(string)
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
params := make([]any, len(self.children))
for i, tree := range self.children {
var param any
@@ -35,44 +53,21 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
params[i] = param
}
if err == nil {
if err = checkFunctionCall(ctx, name, params); err == nil {
if v, err = ctx.Call(name, params); err == nil {
exportAll := isEnabled(ctx, control_export_all)
// Export variables
for _, refName := range ctx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
refValue, _ := ctx.GetVar(refName)
exportVar(parentCtx, refName, refValue)
}
// Export functions
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info := ctx.GetFuncInfo(refName); info != nil {
exportFunc(parentCtx, refName, info)
}
exportObjects(parentCtx, ctx)
}
}
}
return
}
func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
// -------- function definition term
func newFuncDefTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
tk: *tk, // value is the expression body
parent: nil,
children: args, // arg[0]=formal-param-list, arg[1]=*ast
children: args, // function params
position: posLeaf,
priority: priValue,
evalFunc: evalFuncDef,
@@ -89,7 +84,12 @@ type funcDefFunctor struct {
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
for i, p := range functor.params {
if i < len(args) {
ctx.setVar(p, args[i])
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)
}
@@ -104,12 +104,6 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
paramList := make([]string, 0, len(self.children))
for _, param := range self.children {
paramList = append(paramList, param.source())
// if paramName, ok := param.value().(string); ok {
// paramList = append(paramList, paramName)
// } else {
// err = fmt.Errorf("invalid function definition: formal param nr %d must be an identifier", i+1)
// break
// }
}
v = &funcDefFunctor{
params: paramList,
+184
View File
@@ -0,0 +1,184 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-iterator.go
package expr
import (
"fmt"
"slices"
"strings"
)
// -------- iterator term
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
tk.Sym = SymIterator
children := make([]*term, 0, 1+len(args))
children = append(children, dsTerm)
children = append(children, args...)
return &term{
tk: *tk,
parent: nil,
children: children,
position: posLeaf,
priority: priValue,
evalFunc: evalIterator,
}
}
func newIteratorTerm(tk *Token, args []*term) *term {
tk.Sym = SymIterator
return &term{
tk: *tk,
parent: nil,
children: args,
position: posLeaf,
priority: priValue,
evalFunc: evalIterator,
}
}
// -------- eval iterator
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
values = make([]any, len(a))
for i, t := range a {
var value any
if value, err = t.compute(ctx); err == nil {
values[i] = value
} else {
break
}
}
return
}
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
if len(self.children) < 1 || self.children[0] == nil {
err = self.Errorf("missing the data-source parameter")
return
}
value, err = self.children[0].compute(ctx)
return
}
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
if dictAny, ok := firstChildValue.(map[any]any); ok {
requiredFields := []string{currentName, nextName}
fieldsMask := 0b11
foundFields := 0
ds = make(map[string]Functor)
for keyAny, item := range dictAny {
if key, ok := keyAny.(string); ok {
if functor, ok := item.(*funcDefFunctor); ok {
ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 {
foundFields |= 1 << index
}
}
}
}
// check required functions
if foundFields != fieldsMask {
missingFields := make([]string, 0, len(requiredFields))
for index, field := range requiredFields {
if (foundFields & (1 << index)) == 0 {
missingFields = append(missingFields, field)
}
}
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
}
}
return
}
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
var firstChildValue any
var ds map[string]Functor
if firstChildValue, err = evalFirstChild(ctx, self); err != nil {
return
}
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil {
return
}
if ds != nil {
dc := newDataCursor(ctx, ds)
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var args []any
if len(self.children) > 1 {
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
initCtx := dc.ctx.Clone()
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
return
}
exportObjects(dc.ctx, initCtx)
}
dc.nextFunc, _ = ds[nextName]
dc.currentFunc, _ = ds[currentName]
dc.cleanFunc, _ = ds[cleanName]
dc.resetFunc, _ = ds[resetName]
v = dc
} else if list, ok := firstChildValue.(*ListType); ok {
var args []any
if args, err = evalSibling(ctx, self.children, nil); err == nil {
v = NewListIterator(list, args)
}
} else {
var list []any
if list, err = evalSibling(ctx, self.children, firstChildValue); err == nil {
v = NewArrayIterator(list)
}
}
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) {
items := make([]any, 0, len(terms))
for i, tree := range terms {
var param any
if i == 0 {
if firstChildValue == nil {
continue
}
param = firstChildValue
} else if param, err = tree.compute(ctx); err != nil {
break
}
items = append(items, param)
}
if err == nil {
list = items
}
return
}
+68 -5
View File
@@ -4,12 +4,74 @@
// operand-list.go
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
func newListTermA(args ...*term) *term {
return newListTerm(args)
}
func newListTerm(args []*term) *term {
return &term{
tk: *NewToken(0, 0, SymList, "[]"),
tk: *NewValueToken(0, 0, SymList, "[]", args),
parent: nil,
children: args,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalList,
@@ -18,8 +80,9 @@ func newListTerm(args []*term) *term {
// -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) {
items := make([]any, len(self.children))
for i, tree := range self.children {
list, _ := self.value().([]*term)
items := make(ListType, len(list))
for i, tree := range list {
var param any
if param, err = tree.compute(ctx); err != nil {
break
@@ -27,7 +90,7 @@ func evalList(ctx ExprContext, self *term) (v any, err error) {
items[i] = param
}
if err == nil {
v = items
v = &items
}
return
}
+16 -1
View File
@@ -4,7 +4,10 @@
// operand-selector-case.go
package expr
import "fmt"
import (
"fmt"
"strings"
)
// -------- selector case term
@@ -13,6 +16,18 @@ type selectorCase struct {
caseExpr Expr
}
func (sc *selectorCase) String() string {
var sb strings.Builder
if sc.filterList != nil {
sc.filterList.toString(&sb)
sb.WriteByte(' ')
}
sb.WriteByte('{')
sb.WriteString(sc.caseExpr.String())
sb.WriteByte('}')
return sb.String()
}
func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
tk := NewValueToken(row, col, SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
return &term{
+7 -2
View File
@@ -23,8 +23,13 @@ func newVarTerm(tk *Token) *term {
// -------- eval func
func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool
if v, exists = ctx.GetVar(self.tk.source); !exists {
err = fmt.Errorf("undefined variable %q", self.tk.source)
name := self.source()
if v, exists = ctx.GetVar(name); !exists {
if info, exists := ctx.GetFuncInfo(name); exists {
v = info.Functor()
} else {
err = fmt.Errorf("undefined variable or function %q", name)
}
}
return
}
+17 -4
View File
@@ -1,5 +1,5 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// All rights reserightChilded.
// operator-assign.go
package expr
@@ -27,11 +27,24 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
return
}
if v, err = self.children[1].compute(ctx); err == nil {
rightChild := self.children[1]
if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
var minArgs, maxArgs int = 0, -1
funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists {
minArgs = info.MinArgs()
maxArgs = info.MaxArgs()
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
l := len(funcDef.params)
minArgs = l
maxArgs = l
}
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
} else {
ctx.setVar(leftTerm.tk.source, v)
ctx.setVar(leftTerm.source(), v)
}
}
return
+2 -2
View File
@@ -48,7 +48,7 @@ func newAndTerm(tk *Token) (inst *term) {
}
func evalAnd(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, control_bool_shortcut) {
if isEnabled(ctx, ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self)
} else {
v, err = evalAndWithoutShortcut(ctx, self)
@@ -117,7 +117,7 @@ func newOrTerm(tk *Token) (inst *term) {
}
func evalOr(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, control_bool_shortcut) {
if isEnabled(ctx, ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self)
} else {
v, err = evalOrWithoutShortcut(ctx, self)
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-builtin.go
package expr
import "io"
//-------- builtin term
func newBuiltinTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalBuiltin,
}
}
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
count := 0
if IsString(childValue) {
module, _ := childValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module)
} else {
var moduleSpec any
it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) {
count++
} else {
err = self.Errorf("unknown module %q", module)
break
}
} else {
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec)
break
}
}
if err == io.EOF {
err = nil
}
}
if err == nil {
v = int64(count)
}
return
}
// init
func init() {
registerTermConstructor(SymKwBuiltin, newBuiltinTerm)
}
+9 -9
View File
@@ -34,11 +34,11 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if _, ok := rightValue.(Functor); ok {
err = errCoalesceNoFunc(self.children[1])
} else {
// if _, ok := rightValue.(Functor); ok {
// err = errCoalesceNoFunc(self.children[1])
// } else {
v = rightValue
}
// }
}
return
}
@@ -71,8 +71,8 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if _, ok := rightValue.(Functor); ok {
err = errCoalesceNoFunc(self.children[1])
if functor, ok := rightValue.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else {
v = rightValue
ctx.setVar(leftTerm.source(), rightValue)
@@ -82,9 +82,9 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
}
// utils
func errCoalesceNoFunc(t *term) error {
return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
}
// func errCoalesceNoFunc(t *term) error {
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
// }
// init
func init() {
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-context-value.go
package expr
//-------- context term
func newContextTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIncDec,
evalFunc: evalContextValue,
}
}
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
var sourceCtx ExprContext
if len(self.children) == 0 {
sourceCtx = ctx
} else if childValue, err = self.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
} else {
return
}
if sourceCtx != nil {
if formatter, ok := ctx.(Formatter); ok {
v = formatter.ToString(0)
} else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
d := make(map[string]any)
for _, key := range keys {
d[key], _ = sourceCtx.GetVar(key)
}
keys = sourceCtx.EnumFuncs(func(name string) bool { return true })
for _, key := range keys {
d[key], _ = sourceCtx.GetFuncInfo(key)
}
v = d
}
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoubleDollar, newContextTerm)
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-dot.go
package expr
import "fmt"
// -------- dot term
func newDotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalDot,
}
}
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) {
var leftValue, rightValue any
if err = self.checkOperands(); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
indexTerm := self.children[1]
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:
if indexTerm.symbol() == SymIdentifier {
opName := indexTerm.source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, []any{})
} else {
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false
}
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDot, newDotTerm)
}
+1 -1
View File
@@ -25,7 +25,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
return
}
if isInteger(leftValue) {
if IsInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 {
f := int64(1)
for k := int64(1); k <= i; k++ {
+282
View File
@@ -0,0 +1,282 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-fraction.go
package expr
import (
"errors"
"fmt"
"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 (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
func newFractionTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 2),
position: posInfix,
priority: priFraction,
evalFunc: evalFraction,
}
}
// -------- eval func
func evalFraction(ctx ExprContext, self *term) (v any, err error) {
var numValue, denValue any
var num, den int64
var ok bool
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
return
}
if num, ok = numValue.(int64); !ok {
err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue)
return
}
if den, ok = denValue.(int64); !ok {
err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue)
return
}
if den == 0 {
err = errors.New("division by zero")
return
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
num = num / g
den = den / g
if den == 1 {
v = num
} else {
v = &fraction{num, den}
}
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
func init() {
registerTermConstructor(SymVertBar, newFractionTerm)
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-include.go
package expr
//-------- include term
func newIncludeTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalInclude,
}
}
func evalInclude(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
count := 0
if IsList(childValue) {
list, _ := childValue.([]any)
for i, filePathSpec := range list {
if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil {
count++
} else {
err = self.Errorf("can't load file %q", filePath)
break
}
} else {
err = self.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
break
}
}
} else if IsString(childValue) {
filePath, _ := childValue.(string)
v, err = EvalFile(ctx, filePath)
} else {
err = self.errIncompatibleType(childValue)
}
if err == nil {
v = count
}
return
}
// init
func init() {
registerTermConstructor(SymKwInclude, newIncludeTerm)
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-insert.go
package expr
//-------- insert term
func newInsertTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalInsert,
}
}
func newAppendTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalAppend,
}
}
func evalInsert(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)
newList := append(ListType{leftValue}, *list...)
v = &newList
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalAppend(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
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymInsert, newInsertTerm)
registerTermConstructor(SymAppend, newAppendTerm)
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-iter-value.go
package expr
//-------- iter value term
func newIterValueTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIterValue,
evalFunc: evalIterValue,
}
}
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(Iterator); ok {
v, err = it.Current()
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-length.go
package expr
//-------- length term
func newLengthTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalLength,
}
}
func evalLength(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
if IsList(childValue) {
list, _ := childValue.([]any)
v = int64(len(list))
} else if IsString(childValue) {
s, _ := childValue.(string)
v = int64(len(s))
} else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)
v, _ = toInt(count, "")
} else {
v = int64(it.Index() + 1)
}
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymHash, newLengthTerm)
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
package expr
// -------- post increment term
func newPostIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priIncDec,
evalFunc: evalPostInc,
}
}
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm)
}
+11 -7
View File
@@ -30,13 +30,15 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
return
}
if isString(leftValue) && isInteger(rightValue) {
if IsString(leftValue) && IsInteger(rightValue) {
s, _ := leftValue.(string)
n, _ := rightValue.(int64)
v = strings.Repeat(s, int(n))
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) * numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
@@ -69,14 +71,16 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
} else {
v = numAsFloat(leftValue) / d
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
@@ -110,7 +114,7 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
@@ -144,7 +148,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
return
}
if isInteger(leftValue) && isInteger(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
err = errors.New("division by zero")
+9 -9
View File
@@ -25,15 +25,15 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li == ri
} else {
v = numAsFloat(leftValue) == numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls == rs
@@ -111,15 +111,15 @@ func evalLess(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li < ri
} else {
v = numAsFloat(leftValue) < numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls < rs
@@ -148,15 +148,15 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
if IsNumber(leftValue) && IsNumber(rightValue) {
if IsInteger(leftValue) && IsInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li <= ri
} else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
} else if IsString(leftValue) && IsString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls <= rs
+12 -9
View File
@@ -4,31 +4,33 @@
// operator-selector.go
package expr
//-------- export all term
//-------- selector term
func newSelectorTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 3),
position: posMultifix,
position: posInfix,
priority: priSelector,
evalFunc: evalSelector,
}
}
func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
} else {
filterList := caseData.filterList.children
match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
} else {
var caseValue any
for _, caseTerm := range filterList {
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
break
}
}
@@ -39,7 +41,7 @@ func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (sel
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
var exprValue any
// var caseList []*term
var match bool
if err = self.checkOperands(); err != nil {
return
@@ -49,14 +51,15 @@ func evalSelector(ctx ExprContext, self *term) (v any, err error) {
return
}
caseList := self.children[1:]
caseListTerm := self.children[1]
caseList, _ := caseListTerm.value().([]*term)
for i, caseTerm := range caseList {
caseSel := caseTerm.value()
if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match {
break
}
}
if err == nil && v == nil {
if err == nil && !match {
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
}
return
+2 -2
View File
@@ -35,14 +35,14 @@ func evalSign(ctx ExprContext, self *term) (v any, err error) {
return
}
if isFloat(rightValue) {
if IsFloat(rightValue) {
if self.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64)
v = -f
} else {
v = rightValue
}
} else if isInteger(rightValue) {
} else if IsInteger(rightValue) {
if self.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64)
v = -i
+30 -24
View File
@@ -28,33 +28,39 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
return
}
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
} else if IsNumber(leftValue) && IsNumber(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt + rightInt
}
} else if isList(leftValue) || isList(rightValue) {
var leftList, rightList []any
} else if IsList(leftValue) || IsList(rightValue) {
var leftList, rightList *ListType
var ok bool
if leftList, ok = leftValue.([]any); !ok {
leftList = []any{leftValue}
if leftList, ok = leftValue.(*ListType); !ok {
leftList = &ListType{leftValue}
}
if rightList, ok = rightValue.([]any); !ok {
rightList = []any{rightValue}
if rightList, ok = rightValue.(*ListType); !ok {
rightList = &ListType{rightValue}
}
sumList := make([]any, 0, len(leftList)+len(rightList))
for _, item := range leftList {
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
for _, item := range *leftList {
sumList = append(sumList, item)
}
for _, item := range rightList {
for _, item := range *rightList {
sumList = append(sumList, item)
}
v = sumList
v = &sumList
} else if isFraction(leftValue) || isFraction(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else {
v, err = sumAnyFract(leftValue, rightValue)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -66,8 +72,6 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
func newMinusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
@@ -82,24 +86,26 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue)
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt - rightInt
}
} else if isList(leftValue) && isList(rightValue) {
leftList, _ := leftValue.([]any)
rightList, _ := rightValue.([]any)
diffList := make([]any, 0, len(leftList)-len(rightList))
for _, item := range leftList {
if slices.Index(rightList, item) < 0 {
} else if IsList(leftValue) && IsList(rightValue) {
leftList, _ := leftValue.(*ListType)
rightList, _ := rightValue.(*ListType)
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
for _, item := range *leftList {
if slices.Index(*rightList, item) < 0 {
diffList = append(diffList, item)
}
}
v = diffList
v = &diffList
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
+211 -28
View File
@@ -58,34 +58,94 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
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) {
// Example: "add = func(x,y) {x+y}
var body *ast
args := make([]*term, 0)
tk := scanner.Next()
for tk.Sym != SymClosedRound && tk.Sym != SymEos {
if tk.Sym == SymIdentifier {
t := newTerm(tk, nil)
args = append(args, t)
} else {
err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
lastSym := SymUnknown
itemExpected := false
tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next()
if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk, nil)
args = append(args, param)
tk = scanner.Next()
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("function-param")
break
}
tk = scanner.Next()
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil && tk.Sym != SymClosedRound {
err = tk.Errorf("unterminate function params list")
if err == nil && lastSym != SymClosedRound {
err = tk.ErrorExpectedGot(")")
}
if err == nil {
tk = scanner.Next()
if tk.Sym == SymOpenBrace {
if tk.IsSymbol(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().Errorf("not properly terminated function body")
err = scanner.Previous().ErrorExpectedGot("}")
} else {
tk = scanner.makeValueToken(SymExpression, "", body)
tree = newFuncDefTerm(tk, args)
@@ -97,21 +157,27 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("list-item")
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedSquare {
err = scanner.Previous().Errorf("unterminate items list")
err = scanner.Previous().ErrorExpectedGot("]")
} else {
subtree = newListTerm(args)
}
@@ -119,6 +185,103 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
return
}
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
tk := scanner.Previous()
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("iterator-param")
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound {
err = scanner.Previous().ErrorExpectedGot(")")
} else {
subtree = newIteratorTerm(tk, args)
}
}
return
}
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
tk := scanner.Next()
if tk.Sym == SymError {
err = tk.Error()
return
}
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
return
}
if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next()
if tkSep.Sym != SymColon {
err = tkSep.ErrorExpectedGot(":")
} else {
key = tk.Value
}
} else {
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
err = tk.ErrorExpectedGot("dictionary-key or }")
}
return
}
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make(map[any]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast
var key any
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
break
} else if key == nil {
tk := scanner.Previous()
lastSym = tk.Sym
if itemExpected {
err = tk.ErrorExpectedGot("dictionary-key")
}
break
}
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
if subTree.root != nil {
args[key] = subTree.root
} else if key != nil {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("dictionary-value")
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}")
} else {
subtree = newDictTerm(args)
}
}
return
}
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term
var caseExpr *ast
@@ -145,7 +308,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
return
}
} else {
err = tk.Errorf("selector-case expected, got %q", tk.source)
err = tk.ErrorExpectedGot("{")
}
if err == nil {
@@ -154,6 +317,19 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
return
}
func addSelectorCase(selectorTerm, caseTerm *term) {
if len(selectorTerm.children) < 2 {
caseListTerm := newListTermA(caseTerm)
selectorTerm.children = append(selectorTerm.children, caseListTerm)
} else {
caseListTerm := selectorTerm.children[1]
caseList, _ := caseListTerm.value().([]*term)
caseList = append(caseList, caseTerm)
caseListTerm.tk.Value = caseList
}
caseTerm.parent = selectorTerm
}
func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
var caseTerm *term
tk := scanner.makeToken(SymSelector, '?')
@@ -162,8 +338,7 @@ func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool)
}
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil {
selectorTerm.children = append(selectorTerm.children, caseTerm)
caseTerm.parent = selectorTerm
addSelectorCase(selectorTerm, caseTerm)
}
return
}
@@ -179,14 +354,14 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
var tk *Token
tree = NewAst()
firstToken := true
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 {
continue
}
//resetSelector := true
if tk.Sym == SymSemiColon {
if allowForest {
@@ -229,6 +404,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(listTerm)
currentTerm = listTerm
}
case SymOpenBrace:
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk)
@@ -239,6 +420,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = tree.addTerm(funcDefTerm)
currentTerm = funcDefTerm
}
case SymDollarRound:
var iterDefTerm *term
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
err = tree.addTerm(iterDefTerm)
currentTerm = iterDefTerm
}
case SymIdentifier:
if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
@@ -246,9 +433,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
currentTerm, err = tree.addToken2(tk)
}
case SymQuestion:
/*if selectorTerm != nil {
err = tk.Errorf("nested selectors must be enclosed in parentheses")
} else*/if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
currentTerm = selectorTerm
}
case SymColon, SymDoubleColon:
@@ -256,27 +441,25 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
if selectorTerm == nil {
err = tk.Errorf("selector-case outside of a selector context")
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
selectorTerm.children = append(selectorTerm.children, caseTerm)
caseTerm.parent = selectorTerm
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
selectorTerm = nil
}
}
//resetSelector = tk.Sym == SymDoubleColon
default:
currentTerm, err = tree.addToken2(tk)
}
if currentTerm != nil && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
selectorTerm = nil
}
// if resetSelector {
// selectorTree = nil
// }
lastSym = tk.Sym
}
if err == nil {
err = tk.Error()
}
return
}
+129 -226
View File
@@ -6,18 +6,18 @@ package expr
import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
)
func TestParser(t *testing.T) {
type inputType struct {
type inputType struct {
source string
wantResult any
wantErr error
}
}
func TestGeneralParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil},
@@ -35,10 +35,10 @@ func TestParser(t *testing.T) {
/* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* *17 */ {`"uno" + "due"`, `unodue`, nil},
/* *18 */ {`"uno" + 2`, `uno2`, nil},
/* *19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* *20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil},
@@ -51,133 +51,133 @@ func TestParser(t *testing.T) {
/* 30 */ {"-(-2+1)", int64(1), nil},
/* 31 */ {"(1+1)*5", int64(10), nil},
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
/* 33 */ {`add(1,2,3)`, int64(6), nil},
/* 34 */ {`mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 37 */ {`add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 39 */ {`(((1)))`, int64(1), nil},
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 44 */ {`false // very simple expression`, false, nil},
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil operands, got 1`)},
/* 46 */ {"", nil, errors.New(`empty expression`)},
/* 47 */ {"4!", int64(24), nil},
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
/* 49 */ {"-4!", int64(-24), nil},
/* 50 */ {"1.5 < 7", true, nil},
/* 51 */ {"1.5 > 7", false, nil},
/* 52 */ {"1.5 <= 7", true, nil},
/* 53 */ {"1.5 >= 7", false, nil},
/* 54 */ {"1.5 != 7", true, nil},
/* 55 */ {"1.5 == 7", false, nil},
/* 56 */ {`"1.5" < "7"`, true, nil},
/* 57 */ {`"1.5" > "7"`, false, nil},
/* 58 */ {`"1.5" == "7"`, false, nil},
/* 59 */ {`"1.5" != "7"`, true, nil},
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)},
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)},
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)},
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)},
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)},
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)},
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)},
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)},
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)},
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)},
/* 70 */ {"+1.5", float64(1.5), nil},
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
/* 75 */ {`123`, int64(123), nil},
/* 76 */ {`1.`, float64(1.0), nil},
/* 77 */ {`1.E-2`, float64(0.01), nil},
/* 78 */ {`1E2`, float64(100), nil},
/* 79 */ {`1 / 2`, int64(0), nil},
/* 80 */ {`1.0 / 2`, float64(0.5), nil},
/* 81 */ {`1 ./ 2`, float64(0.5), nil},
/* 82 */ {`5 % 2`, int64(1), nil},
/* 83 */ {`5 % (-2)`, int64(1), nil},
/* 84 */ {`-5 % 2`, int64(-1), nil},
/* 85 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
/* 86 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
/* 87 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
/* 88 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
/* 89 */ {`~ 2 > 1`, false, nil},
/* 90 */ {`~ true && true`, false, nil},
/* 91 */ {`~ false || true`, true, nil},
/* 92 */ {`false but true`, true, nil},
/* 93 */ {`2+3 but 5*2`, int64(10), nil},
/* 94 */ {`add(1,2) but var2`, "abc", nil},
/* 95 */ {`x=2`, int64(2), nil},
/* 96 */ {`x=2 but x*10`, int64(20), nil},
/* 97 */ {`false and true`, false, nil},
/* 98 */ {`false and (x==2)`, false, nil},
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable "x"`)},
/* 100 */ {`false or true`, true, nil},
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
/* 102 */ {`a=5; a`, int64(5), nil},
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
/* 105 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
/* 106 */ {`2+(a=5)`, int64(7), nil},
/* 107 */ {`two=func(){2}; two()`, int64(2), nil},
/* 108 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
/* 109 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 114 */ {`x ?? "default"`, "default", nil},
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
/* 116 */ {`x ?? func(){}"`, nil, errors.New(`[1:15] the right operand of a coalescing operation cannot be a function definition`)},
/* 117 */ {`x ?= "default"; x`, "default", nil},
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)},
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
/* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
/* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
/* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
/* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
/* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
/* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
/* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
/* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 34 */ {`(((1)))`, int64(1), nil},
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
/* 36 */ {`0 || 0.0 && "hello"`, false, nil},
/* 37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
/* 38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 39 */ {`false // very simple expression`, false, nil},
/* 40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
/* 41 */ {"", nil, nil},
/* 42 */ {"4!", int64(24), nil},
/* 43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
/* 44 */ {"-4!", int64(-24), nil},
/* 45 */ {"1.5 < 7", true, nil},
/* 46 */ {"1.5 > 7", false, nil},
/* 47 */ {"1.5 <= 7", true, nil},
/* 48 */ {"1.5 >= 7", false, nil},
/* 49 */ {"1.5 != 7", true, nil},
/* 50 */ {"1.5 == 7", false, nil},
/* 51 */ {`"1.5" < "7"`, true, nil},
/* 52 */ {`"1.5" > "7"`, false, nil},
/* 53 */ {`"1.5" == "7"`, false, nil},
/* 54 */ {`"1.5" != "7"`, true, nil},
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
/* 56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
/* 57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
/* 58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
/* 59 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
/* 60 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
/* 61 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
/* 62 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
/* 63 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
/* 64 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
/* 65 */ {"+1.5", float64(1.5), nil},
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
/* 70 */ {`123`, int64(123), nil},
/* 71 */ {`1.`, float64(1.0), nil},
/* 72 */ {`1.E-2`, float64(0.01), nil},
/* 73 */ {`1E2`, float64(100), nil},
/* 74 */ {`1 / 2`, int64(0), nil},
/* 75 */ {`1.0 / 2`, float64(0.5), nil},
/* 76 */ {`1 ./ 2`, float64(0.5), nil},
/* 77 */ {`5 % 2`, int64(1), nil},
/* 78 */ {`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 "%"`)},
/* 81 */ {`"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},
/* 84 */ {`~ 2 > 1`, false, nil},
/* 85 */ {`~ true && true`, false, nil},
/* 86 */ {`~ false || true`, true, nil},
/* 87 */ {`false but true`, true, nil},
/* 88 */ {`2+3 but 5*2`, int64(10), nil},
/* 89 */ {`x=2`, int64(2), nil},
/* 90 */ {`x=2 but x*10`, int64(20), nil},
/* 91 */ {`false and true`, false, nil},
/* 92 */ {`false and (x==2)`, false, nil},
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
/* 94 */ {`false or true`, true, nil},
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
/* 96 */ {`a=5; a`, int64(5), nil},
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
/* 99 */ {`2+(a=5)`, int64(7), nil},
/* 100 */ {`x ?? "default"`, "default", nil},
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
/* 103 */ {`x ?= "default"; x`, "default", nil},
/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 115 */ {`nil`, nil, nil},
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
/* 119 */ {`{}`, map[any]any{}, nil},
/* 120 */ {`1|2`, newFraction(1, 2), nil},
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil},
/* 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},
}
check_env_expr_path := 113
// t.Setenv("EXPR_PATH", ".")
parserTest(t, "General", inputs)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0
failed := 0
// inputs1 := []inputType{
// {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
// }
for i, input := range inputs {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
importMathFuncs(ctx)
importImportFunc(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
@@ -187,7 +187,9 @@ func TestParser(t *testing.T) {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
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
}
@@ -203,114 +205,15 @@ func TestParser(t *testing.T) {
succeeded++
} else {
failed++
if i+1 == check_env_expr_path {
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH environment variable with value "."`, check_env_expr_path)
}
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func TestListParser(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
// inputs1 := []inputType{
// {`add(1,2,3)`, int64(6), nil},
// }
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), 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},
}
succeeded := 0
failed := 0
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
importMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, 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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
func logTest(t *testing.T, n int, source string, wantResult any, wantErr error) {
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %3d -- %q --> %v", n, source, wantResult))
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
} else {
t.Log(fmt.Sprintf("[-]Test nr %3d -- %q --> %v", n, source, wantErr))
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
}
+100 -18
View File
@@ -86,13 +86,14 @@ func (self *scanner) Next() (tk *Token) {
}
func (self *scanner) fetchNextToken() (tk *Token) {
var ch byte
if err := self.skipBlanks(); err != nil {
return self.makeErrorToken(err)
}
escape := false
for {
ch, _ := self.readChar()
ch, _ = self.readChar()
switch ch {
case '+':
if next, _ := self.peek(); next == '+' {
@@ -143,6 +144,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
}
case ',':
tk = self.makeToken(SymComma, ch)
case '^':
tk = self.makeToken(SymCaret, ch)
case ':':
if next, _ := self.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next)
@@ -152,10 +155,17 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case ';':
tk = self.makeToken(SymSemiColon, ch)
case '.':
if next, _ := self.peek(); next >= '0' && next <= '9' {
tk = self.parseNumber(ch)
} else if next == '/' {
//if next, _ := self.peek(); next >= '0' && next <= '9' {
// tk = self.parseNumber(ch)
//} else if next == '/' {
if next, _ := self.peek(); next == '/' {
tk = self.moveOn(SymDotSlash, ch, next)
} else if next == '.' {
if next1, _ := self.peek(); next1 == '.' {
tk = self.moveOn(SymTripleDot, ch, next, next1)
} else {
tk = self.moveOn(SymDoubleDot, ch, next)
}
} else {
tk = self.makeToken(SymDot, ch)
}
@@ -219,6 +229,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '<':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' {
tk = self.moveOn(SymAppend, ch, next)
} else if next == '>' {
tk = self.moveOn(SymLessGreater, ch, next)
} else {
@@ -227,13 +239,26 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '>':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' {
tk = self.moveOn(SymInsert, ch, next)
} else {
tk = self.makeToken(SymGreater, ch)
}
case '$':
if next, _ := self.peek(); next == '(' {
tk = self.moveOn(SymDollarRound, ch, next)
tk.source += ")"
} else if next == '$' {
tk = self.moveOn(SymDoubleDollar, ch, next)
} else {
tk = self.makeToken(SymDollar, ch)
}
case '(':
if next, _ := self.peek(); next == ')' {
tk = self.moveOn(SymOpenClosedRound, ch, next)
} else {
tk = self.makeToken(SymOpenRound, ch)
}
case ')':
tk = self.makeToken(SymClosedRound, ch)
case '[':
@@ -266,6 +291,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
break
}
}
if tk == nil {
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
}
return
}
@@ -276,17 +304,78 @@ func (self *scanner) sync(err error) error {
return err
}
func isBinaryDigit(ch byte) bool {
return ch == '0' || ch == '1'
}
func isOctalDigit(ch byte) bool {
return ch >= '0' && ch <= '7'
}
func isDecimalDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}
func isHexDigit(ch byte) bool {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
}
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
var ch byte
var digitType string
firstCh = currentFirstCh
digitFunc = isDecimalDigit
numBase = 10
if ch, err = self.peek(); err == nil {
if ch == 'b' || ch == 'B' {
numBase = 2
digitType = "binary"
self.readChar()
digitFunc = isBinaryDigit
firstCh, err = self.readChar()
} else if ch == 'o' || ch == 'O' {
numBase = 8
digitType = "octal"
self.readChar()
digitFunc = isOctalDigit
firstCh, err = self.readChar()
} else if ch == 'x' || ch == 'X' {
numBase = 16
digitType = "hex"
self.readChar()
digitFunc = isHexDigit
firstCh, err = self.readChar()
}
if err == nil && !digitFunc(firstCh) {
if len(digitType) == 0 {
digitType = "decimal"
}
err = fmt.Errorf("expected %s digit, got '%c'", digitType, firstCh)
}
} else if err == io.EOF {
err = nil
}
return
}
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
var err error
var ch byte
var sym Symbol = SymInteger
var value any
var sb strings.Builder
var isDigit func(byte) bool = isDecimalDigit
var numBase = 10
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
if firstCh == '0' {
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
}
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
sb.WriteByte(ch)
}
if ch == '.' {
if numBase == 10 {
if err == nil && ch == '.' {
sym = SymFloat
sb.WriteByte(ch)
ch, err = self.readChar()
@@ -296,7 +385,7 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
}
}
}
if ch == 'e' || ch == 'E' {
if err == nil && (ch == 'e' || ch == 'E') {
sym = SymFloat
sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil {
@@ -308,30 +397,23 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
//err = self.sync(err)
} else {
err = errors.New("expected integer exponent")
}
}
// } else {
// err = self.sync(err)
}
}
if err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
} else {
var value any
err = self.sync(err)
txt := sb.String()
if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64)
} else if strings.HasPrefix(txt, "0x") {
value, err = strconv.ParseInt(txt, 16, 64)
} else if strings.HasPrefix(txt, "0o") {
value, err = strconv.ParseInt(txt, 8, 64)
} else if strings.HasPrefix(txt, "0b") {
value, err = strconv.ParseInt(txt, 2, 64)
} else {
value, err = strconv.ParseInt(txt, 10, 64)
value, err = strconv.ParseInt(txt, numBase, 64)
}
if err == nil {
tk = self.makeValueToken(sym, txt, value)
+2 -2
View File
@@ -36,7 +36,7 @@ func TestScanner(t *testing.T) {
/* 14 */ {`:`, SymColon, nil, nil},
/* 15 */ {`;`, SymSemiColon, nil, nil},
/* 16 */ {`.`, SymDot, nil, nil},
/* 17 */ {`.5`, SymFloat, float64(0.5), nil},
/* 17 */ {`0.5`, SymFloat, float64(0.5), nil},
/* 18 */ {`\\`, SymBackSlash, nil, nil},
/* 19 */ {"`", SymBackTick, nil, nil},
/* 20 */ {"?", SymQuestion, nil, nil},
@@ -74,7 +74,7 @@ func TestScanner(t *testing.T) {
scanner := NewScanner(r, nil)
if tk := scanner.Next(); tk == nil {
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
if tk.Sym == SymError && input.wantSym == tk.Sym {
if tkErr, tkOk := tk.Value.(error); tkOk {
+69 -6
View File
@@ -1,7 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import "fmt"
import (
"fmt"
"strings"
)
type SimpleFuncStore struct {
SimpleVarStore
@@ -15,6 +21,29 @@ type funcInfo struct {
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
}
@@ -32,21 +61,55 @@ func (info *funcInfo) Functor() Functor {
}
func NewSimpleFuncStore() *SimpleFuncStore {
return &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)},
funcStore: CloneMap(ctx.funcStore),
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc) {
info, _ = ctx.funcStore[name]
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
}
+57 -3
View File
@@ -1,7 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-var-store.go
package expr
import "fmt"
import (
"fmt"
"strings"
)
type SimpleVarStore struct {
varStore map[string]any
@@ -13,9 +19,14 @@ func NewSimpleVarStore() *SimpleVarStore {
}
}
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: CloneMap(ctx.varStore),
varStore: ctx.cloneVars(),
}
return clone
}
@@ -26,10 +37,12 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
}
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 {
@@ -51,7 +64,7 @@ func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (v
return
}
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
return
}
@@ -65,3 +78,44 @@ func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, m
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()
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// strings_test.go
package expr
import (
"testing"
)
func TestStringsParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil},
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc".1`, `b`, nil},
/* 5 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
}
+17 -1
View File
@@ -33,7 +33,7 @@ const (
SymBackTick // 22: '`'
SymExclamation // 23: '!'
SymQuestion // 24: '?'
SymAmpersand // 25: '&&'
SymAmpersand // 25: '&'
SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%'
SymAt // 28: '@'
@@ -59,6 +59,14 @@ const (
SymQuestionEqual // 48: '?='
SymDoubleAt // 49: '@@'
SymDoubleColon // 50: '::'
SymInsert // 51: '>>'
SymAppend // 52: '<<'
SymCaret // 53: '^'
SymDollarRound // 54: '$('
SymOpenClosedRound // 55: '()'
SymDoubleDollar // 56: '$$'
SymDoubleDot // 57: '..'
SymTripleDot // 58: '...'
SymChangeSign
SymUnchangeSign
SymIdentifier
@@ -66,6 +74,7 @@ const (
SymInteger
SymFloat
SymString
SymIterator
SymOr
SymAnd
SymNot
@@ -73,6 +82,7 @@ const (
SymFuncCall
SymFuncDef
SymList
SymDict
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
@@ -87,6 +97,9 @@ const (
SymKwOr
SymKwBut
SymKwFunc
SymKwBuiltin
SymKwInclude
SymKwNil
)
var keywords map[string]Symbol
@@ -95,9 +108,12 @@ func init() {
//keywords = make(map[string]Symbol)
keywords = map[string]Symbol{
"AND": SymKwAnd,
"BUILTIN": SymKwBuiltin,
"BUT": SymKwBut,
"FUNC": SymKwFunc,
"INCLUDE": SymKwInclude,
"NOT": SymKwNot,
"OR": SymKwOr,
"NIL": SymKwNil,
}
}
+20 -3
View File
@@ -20,10 +20,14 @@ const (
priRelational
priSum
priProduct
priFraction
priSelector
priSign
priFact
priIterValue
priCoalesce
priIncDec
priDot
priValue
)
@@ -126,6 +130,10 @@ func (self *term) setParent(parent *term) {
}
}
func (self *term) symbol() Symbol {
return self.tk.Sym
}
func (self *term) source() string {
return self.tk.source
}
@@ -143,6 +151,15 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
return
}
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
if index64, ok := computedValue.(int64); ok {
i = int(index64)
} else {
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
}
return
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
return self.tk.Errorf(
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
@@ -166,7 +183,7 @@ func (self *term) checkOperands() (err error) {
switch self.position {
case posInfix:
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
err = self.tk.Errorf("infix operator %q requires two not nil operands, got %d", self.source(), self.getChildrenCount())
err = self.tk.Errorf("infix operator %q requires two non-nil operands, got %d", self.source(), self.getChildrenCount())
}
case posPrefix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
@@ -201,9 +218,9 @@ func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err err
return
}
func (self *term) evalPrefix(ctx ExprContext) (rightValue any, err error) {
func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
if err = self.checkOperands(); err == nil {
rightValue, err = self.children[0].compute(ctx)
childValue, err = self.children[0].compute(ctx)
}
return
}
+3 -4
View File
@@ -5,7 +5,6 @@
package expr
import (
"fmt"
"testing"
)
@@ -16,7 +15,7 @@ func TestString(t *testing.T) {
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr == nil {
fmt.Println("Tree:", tree)
t.Log("Tree:", tree)
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
@@ -27,7 +26,7 @@ func TestGetRoom(t *testing.T) {
tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root room:", tree.root.getRoom())
t.Log("Tree-root room:", tree.root.getRoom())
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
@@ -37,7 +36,7 @@ func TestGetChildrenCount(t *testing.T) {
tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root children count:", tree.root.getChildrenCount())
t.Log("Tree-root children count:", tree.root.getChildrenCount())
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
+2
View File
@@ -0,0 +1,2 @@
uno
due
+34 -2
View File
@@ -27,7 +27,11 @@ func (tk *Token) DevString() string {
func (tk *Token) String() string {
if tk.Value != nil {
return fmt.Sprintf("%#v", tk.Value)
if s, ok := tk.Value.(string); ok {
return fmt.Sprintf("%q", s)
} else {
return fmt.Sprintf("%v", tk.Value)
}
}
return fmt.Sprintf("%s", tk.source)
}
@@ -42,7 +46,7 @@ func NewValueToken(row, col int, sym Symbol, source string, value any) *Token {
func NewErrorToken(row, col int, err error) *Token {
if err == io.EOF {
return NewToken(row, col, SymEos, "")
return NewToken(row, col, SymEos, "<EOF>")
}
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
}
@@ -59,7 +63,35 @@ func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
}
func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym
}
func (self *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
return
}
func (self *Token) Error() (err error) {
if self.Sym == SymError {
if msg, ok := self.Value.(error); ok {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
}
}
return
}
func (self *Token) Errors(msg string) (err error) {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
return
}
func (self *Token) ErrorExpectedGot(symbol string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", self.row, self.col, symbol, self)
return
}
func (self *Token) ErrorExpectedGotString(symbol, got string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", self.row, self.col, symbol, got)
return
}
+25 -4
View File
@@ -10,9 +10,30 @@ import (
)
func TestDevString(t *testing.T) {
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
type inputType struct {
source string
sym Symbol
value any
wantResult string
}
fmt.Println("Token '100':", tk1.DevString())
fmt.Println("Token '+':", tk2.DevString())
inputs := []inputType{
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)},
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)},
}
for i, input := range inputs {
var tk *Token
if input.value == nil {
tk = NewToken(0, 0, input.sym, input.source)
} else {
tk = NewValueToken(0, 0, input.sym, input.source, input.value)
}
t.Logf("Test nr %2d: %q --> %q", i+1, input.source, input.wantResult)
if s := tk.DevString(); s != input.wantResult {
t.Errorf("wrong token from symbol '+': expected %q, got %q", input.wantResult, s)
}
}
}
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
if [ $# -lt 2 ]; then
echo >&2 "Usage: $(basename "${0}") <module-name> <func-name>..."
exit 1
fi
MODULE_NAME=${1,,}
GO_FILE="func-${MODULE_NAME//[-.]/_}.go"
shift
FUNC_LIST=
i=0
for name; do
if [ ${i} -gt 0 ]; then
if [ $((i+1)) -eq $# ]; then
FUNC_LIST+=" and "
else
FUNC_LIST+=", "
fi
fi
FUNC_LIST+="${name}()"
((i++))
done
cat > "${GO_FILE}" <<EOF
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-${MODULE_NAME}.go
package expr
import (
)
// --- Start of function definitions
$(
for name; do
cat <<IEOF
func ${name}Func(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
IEOF
done
)
// --- End of function definitions
// Import above functions in the context
func Import${MODULE_NAME^}Funcs(ctx ExprContext) {
$(
for name; do
cat <<IEOF
ctx.RegisterFunc("${name}", &simpleFunctor{f: ${name}Func}, 1, -1)
IEOF
done
)
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
registerImport("${MODULE_NAME}", Import${name}Funcs, "The \"${MODULE_NAME}\" module implements the ${FUNC_LIST} function(s)")
}
EOF
+74 -9
View File
@@ -4,42 +4,78 @@
// utils.go
package expr
import "reflect"
import (
"fmt"
"reflect"
)
func isString(v any) (ok bool) {
func IsString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
func isInteger(v any) (ok bool) {
func IsInteger(v any) (ok bool) {
_, ok = v.(int64)
return ok
}
func isFloat(v any) (ok bool) {
func IsFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func isList(v any) (ok bool) {
_, ok = v.([]any)
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v)
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
}
func IsDict(v any) (ok bool) {
_, ok = v.(map[any]any)
return ok
}
func IsFract(v any) (ok bool) {
_, ok = v.(*fraction)
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}
func isNumOrFract(v any) (ok bool) {
return IsFloat(v) || IsInteger(v) || isFraction(v)
}
func isNumberString(v any) (ok bool) {
return isString(v) || isNumber(v)
return IsString(v) || IsNumber(v)
}
func isFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
func isIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
func numAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
if fract, ok := v.(*fraction); ok {
f = fract.toFloat()
} else {
i, _ := v.(int64)
f = float64(i)
}
}
return
}
@@ -131,3 +167,32 @@ func CloneMap[K comparable, V any](source map[K]V) map[K]V {
dest := make(map[K]V, len(source))
return CopyMap(dest, source)
}
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
// fmt.Printf("--- Clone with filter %p\n", filter)
if filter == nil {
return CopyMap(dest, source)
} else {
for k, v := range source {
if filter(k) {
// fmt.Printf("\tClone var %q\n", k)
dest[k] = v
}
}
}
return dest
}
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
dest := make(map[K]V, len(source))
return CopyFilteredMap(dest, source, filter)
}
func toInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else {
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
}
return
}