Compare commits

..

596 Commits

Author SHA1 Message Date
camoroso 99c1adc434 copyright header updated to 2026 2026-05-06 09:27:42 +02:00
camoroso 5585b496fb iter-iter: changed item operation from function and args to a list of expressions 2026-05-06 04:04:08 +02:00
camoroso acd4f8487d new iter-iter iterator and kern.func-info module 2026-05-05 20:38:30 +02:00
camoroso 7f34ccf955 moved scanner sources to package 'scan' 2026-05-03 14:19:17 +02:00
camoroso f63ff5953e graph.go: conditioned compilation by 'graph' tag 2026-05-03 07:14:30 +02:00
camoroso b9d37a5b4c kern/compare.go: added copyright comment 2026-05-03 07:13:39 +02:00
camoroso 23b8eec74a builtin-base: removed useless function unset(). See UNSET operator 2026-05-03 06:46:51 +02:00
camoroso bb6b6d17ec operator-map.go: return nil on error 2026-05-03 06:30:56 +02:00
camoroso 53acacbadf kern/common-errors.go: little changes to ErrExpectedGot() and ErrInvalidParameterValue() 2026-05-03 06:30:00 +02:00
camoroso 2ebc52891c Iterator operator: automatic temporary variables _index and _count changed with '__' and '_#' respectively. Note that, sinc '#' is not an identifier allowed char, '_#' requires himBHsnotation: -cover 2026-05-02 15:06:12 +02:00
camoroso 3b2ef7927b new operator 'groupby' 2026-05-02 14:53:19 +02:00
camoroso d5ced343c4 kern/compare.go: new function Equal(a,b) that returns true if a and b have the same value 2026-05-02 14:45:05 +02:00
camoroso 3ac8cab275 enhanced ending operator detection 2026-05-02 09:54:24 +02:00
camoroso 6c5e9db34b int-itrator: new iterator over integer ranges 2026-05-01 17:23:06 +02:00
camoroso 78871641d0 t_common_test.go: Error messages also contains the name of the section introduced by special symbol >>> 2026-05-01 17:17:40 +02:00
camoroso dacbec677a iterator interface chenged index and count members from int to tint 64 2026-05-01 17:15:18 +02:00
camoroso 75ed88915d ast.go, pasrse.go: fixed the recover routine 2026-05-01 17:03:44 +02:00
camoroso f2d1f23774 ast.go: added recover from panic in ast.Eval() 2026-05-01 07:25:05 +02:00
camoroso edd90054d7 parser.go: added recover from panic in parser.Parse() 2026-05-01 07:24:41 +02:00
camoroso 610e2df5f5 operator-dot: added support to access dict value by exprssion; example: D.key, D."key", D.expr 2026-04-30 07:06:20 +02:00
camoroso 32c0b45255 kern/dict-type.go: added GetItem() 2026-04-30 07:04:03 +02:00
camoroso 75a3f220df parser: token '()' returns error 2026-04-30 07:03:28 +02:00
camoroso 116b54836f token.go: little change to ErrorExpectedGotStringWithPrefix() 2026-04-30 07:01:02 +02:00
camoroso 8787973de0 util files moved form kern to util package 2026-04-29 11:03:36 +02:00
camoroso 4d910dd069 moved a subset of source file to the kern package 2026-04-27 19:43:37 +02:00
camoroso f100adead3 increased test coverage and splitted some utility functions by OS 2026-04-26 06:16:43 +02:00
camoroso 7d2cf1e687 new operator 'join' 2026-04-25 07:00:30 +02:00
camoroso 49728307f3 t_operator_test.go: added a test for the digest operator 2026-04-25 06:33:35 +02:00
camoroso 20dc502438 new operator 'digest' 2026-04-25 06:25:39 +02:00
camoroso ce7bfc5e3f operator-map/filter.go: added deletion of temporary variables '_index' and '_count' 2026-04-25 06:24:28 +02:00
camoroso 6e98bdd16b new operator 'filter' 2026-04-24 22:42:46 +02:00
camoroso 6ee365bacc new operator 'map' ans general variable access by ${} 2026-04-24 20:11:25 +02:00
camoroso 20d8236325 Iterators: Removed NewAnyIterator(), added NewIterator() 2026-04-24 06:26:00 +02:00
camoroso c39ee7cec0 dict-iterator.go: fixed behaviour of NewDictIterator() with no args 2026-04-23 22:06:23 +02:00
camoroso 9e4252173b Iterator interface now embeds fmt.Stringer 2026-04-23 22:04:41 +02:00
camoroso 2b80ba6789 t_plugin_test.go: TestLoadPluginName() removed due to linkage mismatch 2026-04-23 22:02:41 +02:00
camoroso d7247f97c5 doc: added description of set() and unset() functions defined in the base buiiltin 2026-04-23 19:09:09 +02:00
camoroso 02df7f1c1f Plugin file name extension adapted to the host OS convention (.so on linux, .dll on windows, .dylib on Darwin/MacOS) 2026-04-23 19:06:11 +02:00
camoroso b6b09b2fb1 builtin-os-file: add function fileReadIterator() that creates an iterator over lines of a text file 2026-04-23 19:01:07 +02:00
camoroso 0677180456 builtin-iterator.go/run(): removed default operator (print item) 2026-04-23 18:55:03 +02:00
camoroso b6952444ab builtin-base: added functions set() and unset() 2026-04-21 17:14:51 +02:00
camoroso 6c3a071a02 doc: last change date-time updated 2026-04-21 06:49:58 +02:00
camoroso d7d03b4af8 README: added link to the main Expr documentation hosted on cdn.paas.portale-stac.it 2026-04-21 06:47:15 +02:00
camoroso 49c5cb6a22 doc: progress about builtin functions 2026-04-21 06:45:41 +02:00
camoroso 53d805a832 doc: test commit 2026-04-19 21:27:43 +02:00
camoroso b99ad5def1 Merge branch 'iterator/dict' 2026-04-19 15:18:07 +02:00
camoroso 0d44b8697b doc: more details about iterators over dicts 2026-04-19 15:17:21 +02:00
camoroso 1a7e537921 more tests on iterator over dicts 2026-04-19 15:16:25 +02:00
camoroso 807df0f3a8 Iterator: added support for iterators over dictionaries 2026-04-19 15:08:14 +02:00
camoroso d7dd628fc9 doc: further details about iterator definition 2026-04-19 08:20:40 +02:00
camoroso 1f57ba28dd Doc: progress about builtin modules 2026-04-18 12:21:42 +02:00
camoroso 92bd366380 builtin-base: bool() supports lists and dicts, int() supports fractions 2026-04-18 12:20:05 +02:00
camoroso 7b93c5b4ac doc: begin of iterators description 2026-04-16 10:13:41 +02:00
camoroso 3ba8194ddb Expr.doc, a lot of fixes 2026-04-15 18:17:27 +02:00
camoroso 037565c41e New var() function added to the builtin set 2026-04-15 16:04:12 +02:00
camoroso f8d12b1a93 added TestGoFunction() to test Go functions 2026-04-15 16:03:41 +02:00
camoroso 518d4d8d65 simple-store.go: added function Init() 2026-01-03 09:11:43 +01:00
camoroso d64602cb00 builtin-string.go: fix return type of strLower() and strUpper() 2025-11-15 06:18:38 +01:00
camoroso 4709248828 string builtin: strUpper() and strLower() added 2025-11-13 20:53:07 +01:00
camoroso 5ecf81412e Doc: pre & post incremente/decrement 2025-01-05 12:53:50 +01:00
camoroso ff4db34f7b t_operator_test.go: test on -- and ++ prefix operators 2025-01-05 12:49:36 +01:00
camoroso 0f848071c2 New prefix operators ++ and -- 2025-01-04 18:12:38 +01:00
camoroso 6b3351b324 new prefix operator "!" as logic NOT 2025-01-04 17:47:59 +01:00
camoroso 760c1ee6da New bitwise XOR operator specified by symbol ^ (caret). Iterator dereference is now done by prefixed * (star) 2025-01-03 07:33:17 +01:00
camoroso 5ab6876ea1 term.go: new priority priDereference 2025-01-03 07:28:30 +01:00
camoroso 6268abda8c token.go: new member function Token.SetSymbol() 2025-01-03 07:27:49 +01:00
camoroso d25bd325b7 symbol-map.go: improved detection of incomplete operations 2025-01-03 06:32:55 +01:00
camoroso 01c04feea5 Doc: bitwise operators in the main operator table and special assignment operators table 2025-01-03 05:43:50 +01:00
camoroso 6fc689c46c Added a test to the context type 2025-01-03 05:40:24 +01:00
camoroso eccb0c4dc9 Added new special operators like &= and <<=.
Also made a litle change to scanner function moveOn(): now it moves on
the last char passed and only if there are more than one chars.
2024-12-29 19:26:02 +01:00
camoroso e43823740f Doc: added string splitter operator '/' 2024-12-28 19:22:26 +01:00
camoroso 526982a564 new string operator '/' 2024-12-28 19:16:45 +01:00
camoroso 252514943e parser.go: fix currentItem after parse a subexpr 2024-12-28 19:16:03 +01:00
camoroso 32686fac62 term.go: new member function errDivisionByZero() 2024-12-28 19:14:34 +01:00
camoroso 52a627c747 Edited according to preious commit 2024-12-28 09:28:12 +01:00
camoroso d91e7eb979 The list operator '<<' (append) and '>>' (prepend) have been replaced
with the new operators '<+' and '+>' respectively.
'<<' and '>>' are now used only for left and right binary shit
respectively. Further, their priority has been increased moving them to
a higher priority than that of the assignment operator.
2024-12-28 09:17:27 +01:00
camoroso cca3b76baa solved a number of problems highlighted by the syntax analyzer 2024-12-27 07:46:11 +01:00
camoroso 24e31997fc context-helpers.go: export functions no longer export control flags 2024-12-27 07:22:28 +01:00
camoroso 646710e180 builtin-base.go: new eval() function 2024-12-27 07:14:26 +01:00
camoroso b38327b841 t_builtin-string_test.go: corrected an undefinite article 2024-12-27 07:14:01 +01:00
camoroso fd912b2eb1 common-errors.go: undefinte article selection in error messages 2024-12-27 07:13:03 +01:00
camoroso 0e55f83d56 Forced the exlamation mark as a postfix operator 2024-12-26 08:57:14 +01:00
camoroso 4725145d1c Doc: changed fraction symbol and introduced binary operators 2024-12-25 07:43:06 +01:00
camoroso edf8818f51 New dedicated priority for binary operators between relational and sum ones 2024-12-25 07:41:08 +01:00
camoroso 6211be8a8f Completed transition of the symbol '|' from fraction to operator binary or. New fraction symbol is ':'.
Also, fixed and improved some parsing sections concerning collection indeces and ranges
2024-12-23 06:59:39 +01:00
camoroso f50ddf48db operator-range.go: range-term registered with symbol SymRange 2024-12-23 06:55:57 +01:00
camoroso 76e01f12d2 term.go: two error messages corrected 2024-12-23 06:53:37 +01:00
camoroso 406bced450 operator-sum.go: sum of two fraction fixed 2024-12-23 06:52:10 +01:00
camoroso 409dc86a92 symbol.go: SymRange added 2024-12-23 06:50:02 +01:00
camoroso 4184221428 symbol-map.go: changed symbol classification of some symbols like quotes and post-op 2024-12-23 06:49:17 +01:00
camoroso 8cf8b36a26 t_parser_test.go: replaced ~ with NOT 2024-12-19 15:36:16 +01:00
camoroso de87050188 scanner.go: removed SymTilde from DefaultTranslatios() -> It is not an alias for the SymNot symbol any more 2024-12-19 15:30:29 +01:00
camoroso a1ec0cc611 All assignment operators set the firstToken flag 2024-12-19 15:27:38 +01:00
camoroso 8e5550bfa7 New operator %= 2024-12-19 15:14:30 +01:00
camoroso 6ee21e10af New, more flexible, parser context datatype that includes and extends
the previous flags allowForest and allowVarRef.
Added binary operator (work in progress).
Better implementation of +=,-=,*=, and /= (new) operators.
2024-12-19 14:48:27 +01:00
camoroso 5c44532790 << && >>: left and right shift with integer operands 2024-12-07 07:06:08 +01:00
camoroso cb66c1ab19 symbol-map.go: removed unsed definitons 2024-10-13 08:44:21 +02:00
camoroso a28d24ba68 parser.go: improved terminal symbols thanks to new symbol-map.go functions 2024-10-13 08:42:55 +02:00
camoroso 523349a204 symbol-map.go: new file that helps to identify symbols by source and class 2024-10-13 08:41:30 +02:00
camoroso b185f1df3a token.go: added a few error functions 2024-10-13 08:39:56 +02:00
camoroso 5da5a61a42 Expr.doc: notes about function context 2024-10-05 05:30:22 +02:00
camoroso 6e9205abc4 t_funcs_test.go: A test add on parameters check about two params with the same name 2024-10-05 05:25:29 +02:00
camoroso f61004fb5d A test added on new implicit boolean cases in selector operator 2024-10-05 05:23:55 +02:00
camoroso 321030c8d3 parser.go: function parameter list can't specify same parameter more than once 2024-10-01 06:39:51 +02:00
camoroso 98fc89e84f operator-selector.go: Simplified selector for Boolean expressions 2024-10-01 06:37:35 +02:00
camoroso 778d00677d Doc: closure example 2024-09-18 20:48:12 +02:00
camoroso ba3dbb7f02 Doc: continuation 2024-09-16 06:52:29 +02:00
camoroso 7285109115 parser.go: number sign is now allowed after the assign operator 2024-09-16 06:49:26 +02:00
camoroso 4755774edd Doc: Fixed a lot of typos 2024-09-12 06:57:43 +02:00
camoroso d215d837f6 Reset() and Clean() have new, simpler signature 2024-09-12 05:44:29 +02:00
camoroso ad3c1e5a60 enhanced and simplified Reset(), Clean() and Next() methods 2024-09-09 15:23:07 +02:00
camoroso d6bf5ee500 common-type-names.go: TypeNil and TyperDict added 2024-09-09 15:16:42 +02:00
camoroso 4b176eb868 Fix function.go: CallFunctionByParams() dit not pass correctly received actual params 2024-08-23 10:29:57 +02:00
camoroso dceb31f542 CallFunction() has been replaced by three new functions:
CallFunctionByTerm(), CallFunctionByArgs() and CallFunctionByParams()
2024-08-02 06:39:33 +02:00
camoroso 075b0b5691 RegisterFunc() also returns the funcInfo object 2024-08-01 00:09:49 +02:00
camoroso 3c51b8d2ee Changed some function names and param types 2024-07-31 09:11:57 +02:00
camoroso a46753f453 Function buildActualParams moved from data-cursor.go ro function.go 2024-07-31 09:08:58 +02:00
camoroso 9070b5c9cc The function parameter model has been modified to support the passing of named parameters 2024-07-28 18:49:08 +02:00
camoroso ab06702e5e operator-post-inc.go: new post int decrement operator '--' 2024-07-24 06:39:35 +02:00
camoroso ffe1fa3aac op-assign expansion now end at ']' and '}' too 2024-07-24 06:37:57 +02:00
camoroso 1a1a475dd8 Added support to op-assign operators like '+='.
This feature is implemented by expansion, not by dedicated operators, e.g. a*=2+x is exapanded as a=a*(2+x)
2024-07-23 15:35:57 +02:00
camoroso 463e3634ba scanner.go: New function UnreadToken().
Currently it only supports one staging level.
2024-07-23 15:32:25 +02:00
camoroso 5f8ca47ef0 term.go: New function Clone() 2024-07-23 15:27:50 +02:00
camoroso 837b887490 token.go: New functions Clone() and IsOneOfA() 2024-07-23 15:27:36 +02:00
camoroso c76e1d3c8e symbol.go: New symbol '*=' 2024-07-23 15:24:54 +02:00
camoroso 315f5b22d3 data-cursor.go: the inizialization of the current item is done in the Next() method.
This allows the application of the filter and map operator to the first item too.
2024-07-23 05:46:37 +02:00
camoroso dfae593e86 operand-iterator.go: removed commented code 2024-07-21 16:35:13 +02:00
camoroso d572f3a129 Iterator: new function Count() 2024-07-21 16:34:52 +02:00
camoroso c461fd138e Iterator defined by data-source now only requires one method: next() 2024-07-21 05:45:22 +02:00
camoroso 6ecbe2feb1 Fixed type (expcted -> expected) 2024-07-21 05:43:55 +02:00
camoroso 80d3c6ec7d parser.go: Fixed an old bug that did not allow the parser to skip comment tokens 2024-07-21 05:37:34 +02:00
camoroso e09806c716 %q replaced by %s in some error messages 2024-07-21 05:33:06 +02:00
camoroso 1a772597cb removed/commented unused code 2024-07-19 17:03:03 +02:00
camoroso 33b3e1fc29 New function for searching and importing plugin 2024-07-19 15:37:00 +02:00
camoroso 4e3f5cfbc6 import-utils.go: Paths are now expanded with respect to env-vars and shell ~ 2024-07-19 15:33:15 +02:00
camoroso e35d4e3f70 utils.go: added function ExpandPath 2024-07-19 15:30:26 +02:00
camoroso b4529499d6 iterator.go: exported some const identifier 2024-07-18 07:27:02 +02:00
camoroso 7745dc24e2 dict-type.go: exported NewDataCursor 2024-07-18 07:25:51 +02:00
camoroso be25385d02 builtin-iterator: changed status variable from 'it_status' to 'status' 2024-07-15 06:59:13 +02:00
camoroso 79889cd8e1 New builtin module 'iterator' 2024-07-14 16:53:32 +02:00
camoroso 234759158c scanner: disabled prefix operator '()' 2024-07-13 18:10:04 +02:00
camoroso 00fda29606 operator-iter-value.go: prefix operator () used to get the current value of an iterator replaced by ^ 2024-07-13 17:17:16 +02:00
camoroso 2a2840bdf2 ListIterator now implements next and current command (e.g it.next) 2024-07-13 17:15:53 +02:00
camoroso 06373f5126 Impemented Typer interface 2024-07-13 17:14:25 +02:00
camoroso e69dad5fb5 Rename priority label priCoalesce as priDefault 2024-07-13 17:13:16 +02:00
camoroso 7459057df9 Expr embeds Typer and ast implements it 2024-07-13 17:11:39 +02:00
camoroso 176969c956 New iterator tests 2024-07-13 16:19:04 +02:00
camoroso cde2efacf1 Test on iterator filter and map 2024-07-13 15:30:04 +02:00
camoroso d7a7b3218c Merge branch 'main' into enhance_iterators 2024-07-13 09:08:16 +02:00
camoroso be3bb12f28 operator-unset.go: fixed function removal 2024-07-13 09:07:33 +02:00
camoroso 032916d4fa New operator unset to delete variables and functions from current context 2024-07-13 09:01:59 +02:00
camoroso f3cc0cc7ad operator-include.go: Fixed inclusion of a list of files.
Now returns the value of the last evaluated expression.
2024-07-13 09:00:53 +02:00
camoroso 905337f963 ExprContext: new functions VarCount(), DeleteVar(), FuncCount(), DeleteFunc() 2024-07-13 08:59:15 +02:00
camoroso 9a95a837f6 data-cursor.go: add item mapping support 2024-07-13 06:44:00 +02:00
camoroso 8547248ea2 data-cursor.go: fixed filter 2024-07-13 00:12:08 +02:00
camoroso a02f998fc6 t_iterator_test.go: Fixed test numbering and add a commentend test that is not yet fulfilled 2024-07-11 07:23:35 +02:00
camoroso e904003e6e minor changes 2024-07-11 06:53:14 +02:00
camoroso 990e04f957 operator-assign.go -- Fix: Assigning a functor to a collection's item didn't work 2024-07-11 06:49:02 +02:00
camoroso d8aed9dd7a t_iterator_test.go: added a test to verify the reset command of the list iterator 2024-07-11 05:54:22 +02:00
camoroso a73d24b171 iter-list.go -> list-iterator.go
Fix: the reset command set the initial item to the second one in the list
2024-07-11 05:52:47 +02:00
camoroso 3ebba83bce term.go: replaced self receiver 2024-07-09 07:50:50 +02:00
camoroso 6b3bfa2a11 self param replaced as opTerm 2024-07-09 07:50:06 +02:00
camoroso 867806155e scanner.go: replaced self receiver 2024-07-08 07:30:58 +02:00
camoroso a711333a2e simple-store.go: commented out an unused function 2024-07-08 07:30:26 +02:00
camoroso af3e946bd4 operator-sum.go: replaced self receiver 2024-07-08 07:29:42 +02:00
camoroso 22a36fa630 context.go renamed as expr-context.go 2024-07-07 16:20:29 +02:00
camoroso 6d9a379c92 token.go: replaced self receiver 2024-07-07 16:19:58 +02:00
camoroso dd6404c786 t_scanner_test.go: replaced t.Log(fmtStringf()) with t.Logf() 2024-07-07 16:18:39 +02:00
camoroso 34874ef663 plugins.go: replaced stringsIndex()<0 with !strings.Contains() 2024-07-07 16:17:48 +02:00
camoroso 9bc8e8ca05 operator-sum.go: replaced for-loop with append() 2024-07-07 16:15:56 +02:00
camoroso 7f367cfc49 parser.go: renamed self receiver 2024-07-07 16:14:52 +02:00
camoroso fe999acf2c operator-index.go: removed unused parameter from the function verifyKey() 2024-07-07 16:14:04 +02:00
camoroso 2ed1a1842b operand-iterator.go: commented out an unused function and replaced self receiver 2024-07-07 16:10:43 +02:00
camoroso bb9493d0cc list-type.go: commented out an unused fuction 2024-07-07 16:08:45 +02:00
camoroso f279bf163e import-utils.go: commented out unused fuctions 2024-07-07 15:59:23 +02:00
camoroso 6834d9f47b function.go: removed useless param != nil check 2024-07-07 15:58:29 +02:00
camoroso 8051faa2bf fraction-type.go: use of strings.TrimSuffix() in place of check suffix and slice 2024-07-07 15:57:17 +02:00
camoroso f30e687a79 changed the file name comment 2024-07-07 15:55:51 +02:00
camoroso 2b6e46576b byte-slider.go: renamed function receiver from self to slider 2024-07-07 15:54:26 +02:00
camoroso dc06c03112 builtin-string.go: removed useless err == nil check 2024-07-07 15:53:29 +02:00
camoroso e8f5d3e445 builtin-base.go: unused function iteratorFunc() commented out 2024-07-07 15:52:16 +02:00
camoroso 76ce0945f7 context.go splitted in two files: expr-context.go and expr-function-go.
Expr interface moved from ast.go to the new file expr.go
2024-07-07 15:51:29 +02:00
camoroso 340b99bad7 list-type.go: use of copy() for copying lists 2024-07-07 07:34:58 +02:00
camoroso 93dac956fb t_parser_test.go: more tests on ??, ?= and ?! operators 2024-07-06 17:01:23 +02:00
camoroso 307027d23d t_parser_test.go: changed all error values to string values 2024-07-06 16:47:00 +02:00
camoroso 896844e772 the common test framework now supports error, string and nil as value of the wantErr field 2024-07-06 16:43:13 +02:00
camoroso 9fc20077a1 operator-include.go: the include operator did not count the number of files included when its argument was a single string 2024-07-06 16:08:58 +02:00
camoroso d2a4adebdd dataCursor implements Typer interface 2024-07-06 16:05:54 +02:00
camoroso fd8e32e12b new operator "?!" (alternate value) 2024-07-06 05:54:53 +02:00
camoroso 1e62a51c15 t_fractions_test.go: added a test on the string() function 2024-07-06 04:56:00 +02:00
camoroso 0170baa0f5 iter-list.go: implemented the Typer interface 2024-07-06 04:54:42 +02:00
camoroso fe5c8e9619 tests improved 2024-06-26 04:29:40 +02:00
camoroso 7164e8816c operator-bool.go: error messages improved 2024-06-26 04:28:53 +02:00
camoroso e0f3b028fc builtin-string.go: replaced ToInt() with ToGoInt() 2024-06-26 04:28:07 +02:00
camoroso 522b5983bd builtin-base.go: string() function added 2024-06-26 04:27:14 +02:00
camoroso f1e2163277 replaced ToInt() with ToGoInt() 2024-06-26 04:25:49 +02:00
camoroso bbdf498cf3 function.go: commented out some unused error functions 2024-06-26 04:23:48 +02:00
camoroso f75c991ed2 common-errors.go: commented out some unused error functions 2024-06-26 04:23:14 +02:00
camoroso a7836143cc utils.go: ToInt() renamed as ToGoInt() 2024-06-26 04:22:29 +02:00
camoroso ba9b9cb28f a lot oh changes to the test framework and new test files t_builtin-fmt_test.go and t_plugin_test.go added 2024-06-25 10:59:03 +02:00
camoroso ef1baa11a8 builtin-fmt.go: print() and println() can write data to a generic Writer. They fetch the writer from the control variable '_stdout'.
If _stdout is nil, they write to os.Stdout
2024-06-25 10:55:54 +02:00
camoroso cfddbd60b9 control.go: use of UnsafeSetVar() in place of SetVar(). SetCtrl() added 2024-06-25 10:53:05 +02:00
camoroso 0760141caf utils.go: Removed CloneMap(). fromGenericAny(): check v against nil 2024-06-25 10:48:31 +02:00
camoroso b9e780e659 ExprContext: removed Merge() method 2024-06-25 10:45:54 +02:00
camoroso e41ddc664c Improved closure context persistence. Now it is possibile to define counters like this func(base){func(){@base=base+1}} 2024-06-24 07:20:17 +02:00
camoroso 3b641ac793 Doc: little changes 2024-06-21 09:06:25 +02:00
camoroso a1a62b6794 Doc: little changes 2024-06-20 07:10:59 +02:00
camoroso a18d92534f Doc: little changes 2024-06-19 22:51:37 +02:00
camoroso ec0963e26f Doc: little changes 2024-06-19 22:46:35 +02:00
camoroso be992131b1 Doc: little changes 2024-06-19 22:25:38 +02:00
camoroso 0e3960321f Doc: little changes 2024-06-19 22:22:10 +02:00
camoroso 61d34fef7d Doc: little changes 2024-06-19 22:20:28 +02:00
camoroso 581f1585e6 Doc: embedded images 2024-06-19 22:04:13 +02:00
camoroso 531cb1c249 Doc: concepts 2024-06-19 09:38:02 +02:00
camoroso d1b468f35b t_list_test.go: new test cases added 2024-06-19 09:24:50 +02:00
camoroso ff9cf80c66 operator-index.go: ConstLastIndex is checked 2024-06-19 09:24:19 +02:00
camoroso d63b18fd76 parser.go: SymColon resets the firstToken flag; that is needed to specify range indeces that can define negative limits 2024-06-19 09:22:40 +02:00
camoroso 019470faf1 operator-range.go: Fixed priority bug; when range only has the left limit, right limit is set to ConstLastIndex constant 2024-06-19 09:20:02 +02:00
camoroso 302430d57d common-params.go: added the constant ConstLastIndex 2024-06-19 09:17:46 +02:00
camoroso 62ef0d699d token.go: new function IsOneOf() 2024-06-19 09:16:19 +02:00
camoroso 866de759dd file-reader.expr: simpler implementation 2024-06-17 14:07:39 +02:00
camoroso b1d6b6de44 refactored dict's item access 2024-06-17 14:06:33 +02:00
camoroso 7e357eea62 changed to comply new builtin-os-file.go's function names 2024-06-17 09:54:20 +02:00
camoroso d6b4c79736 operator-index.go: dict item access by generic keys implemented 2024-06-17 09:05:23 +02:00
camoroso d066344af8 builtin-os-file.go: changed read and write function names; added fileReadTextAll 2024-06-17 06:59:15 +02:00
camoroso f41dba069e plugin.go: support Liteide and VScode to debug executable file name 2024-06-17 06:57:47 +02:00
camoroso 703ecf6829 Added utility function GetLast() to context 2024-06-17 06:56:32 +02:00
camoroso ba479a1b99 Function call moved from operand-func.go to function.go 2024-06-17 06:54:50 +02:00
camoroso 24e6a293b0 common-params.go: ParamName added 2024-06-12 11:16:57 +02:00
camoroso 28f464c4dc plugins.go: If the main program's file name ends with '.debug', plugins will be loaded from file with name ending with '.debug' 2024-06-12 11:15:31 +02:00
camoroso 9fb611aa20 Formatter option is now composed of two data: flags (lower 16 bits) and indentation size (higher 16 bits).
DictType and ListType formatter use both indent and flag options.
Simple-store now makes a DictType from its data and its ToString() function returns dict.ToString()
2024-06-11 16:32:01 +02:00
camoroso 56d6d06d15 simple-store.go: newline removed after context last brace in ToString() 2024-06-10 20:58:38 +02:00
camoroso d9f7e5b1ad common-errors.go: exported all error functions.
builtin-string.go: renamed all functions from somthingStr() to strSomething()
2024-06-10 20:37:58 +02:00
camoroso 0f54e01ef3 t_scanner_test.go: Test nr 25 changed because now single-quotes can enclose strings 2024-06-10 20:36:03 +02:00
camoroso 63f5db00b3 all test file on builtin functions have been renamed from t_func-*_test.go to t_builtin-*_test.go 2024-06-10 20:34:11 +02:00
camoroso 9745a5d909 utils.go: toInt() -> ToInt(); toBool() -> ToBool() 2024-06-10 19:03:39 +02:00
camoroso 0bb4c96481 scanner.go: Strings can be enclosed between two single-quotes too 2024-06-10 18:52:13 +02:00
camoroso 1757298eb4 formatter.go: typeName() renamed as TypeName() 2024-06-10 09:37:27 +02:00
camoroso 54041552d4 dict-type.go: added MakeDict() constructor 2024-06-10 09:35:48 +02:00
camoroso 5302907dcf external plugins can now request for dependencies 2024-06-09 17:12:57 +02:00
camoroso eb4b17f078 moved all test expression files in the test-resources forlder 2024-06-09 16:02:07 +02:00
camoroso 33d70d6d1a splitted go and expr function bindings in dedicated source files 2024-06-09 10:41:06 +02:00
camoroso 9df9ad5dd1 func-*.go modules renamed as builtin-*.go.
Also changed and exported some identiefier relatet to the builtin feature
2024-06-09 10:28:51 +02:00
camoroso 34dc828ead exported some identifier 2024-06-09 10:13:37 +02:00
camoroso 29bc2c62a3 first plugin support.
Module organization requires a better structure to decouple definitions and implementations
2024-06-09 07:41:56 +02:00
camoroso 8eb2d77ea3 improved position of some common functions 2024-06-09 07:38:29 +02:00
camoroso 53bcf90d2a operator-builtin.go: some error messages improved 2024-06-09 07:36:12 +02:00
camoroso f347b15146 Control vars are now stored in the globalCtx only.
However, it is still allowed to store control var in a local context in special situation
2024-06-08 05:49:28 +02:00
camoroso 115ce26ce9 IsOptional() function of ExprFuncParam renamed as IdDefault(). A new IsOptional() function created to check if a param is optional without requiring a default value 2024-06-07 09:45:02 +02:00
camoroso 227944b3fb Removed eval() function from ast interface and implementation. Check on preset control variables is now done in initDefaultVars() 2024-06-07 09:41:10 +02:00
camoroso 0d01afcc9f member ctx removed from struct parser because it is unused 2024-06-07 09:03:42 +02:00
camoroso 00c76b41f1 list-type.go: two special constructors, MakeList() and ListFromStrigns(), added 2024-06-07 09:02:14 +02:00
camoroso 08e0979cdd import-utils.go: addPresetDirs() replaced by addSearchDirs() 2024-06-07 09:01:18 +02:00
camoroso f04f5822ec common-type-names.go: added type name list-of 2024-06-07 08:59:04 +02:00
camoroso 80d47879e9 t_list_test.go: removed commented code 2024-06-07 08:54:59 +02:00
camoroso 985eb3d19d Merge branch 'main' into feat_plugin 2024-06-06 05:33:56 +02:00
camoroso 45734ab393 new test file t_iter-list.go 2024-06-06 05:33:35 +02:00
camoroso c100cf349d some identier exported; new file import-utils.go 2024-06-06 05:31:35 +02:00
camoroso 8144122d2c fixed some defects in the iter-list implementation 2024-06-05 08:06:39 +02:00
camoroso 188ea354ee it-range.go renamed as it-range.go.unused 2024-06-05 08:05:42 +02:00
camoroso 2fc6bbfe10 removed old simple vars and funcs context implementations 2024-06-05 05:54:12 +02:00
camoroso 847d85605e removed unused commented code 2024-06-05 05:53:02 +02:00
camoroso 9c29392389 changes to adapt errors using typeName() functions 2024-06-05 05:52:27 +02:00
camoroso a7b6e6f8d2 t_list_test.go: some tests added 2024-06-05 05:50:37 +02:00
camoroso a16ac70e4a t_dict_test.go: some tests added 2024-06-05 05:49:07 +02:00
camoroso ab2e3f0528 use of typeName() in error messages 2024-06-05 05:48:02 +02:00
camoroso 974835a8ef utils.go/typeName() and formatter.go/getTipeName() have been merged in formatter.go/typeName() 2024-06-05 05:42:37 +02:00
camoroso 457a656073 tests on collection's item assignments and some other changes 2024-06-05 05:09:13 +02:00
camoroso 9e63e1402e t_parser_test.go: Expr's type names 2024-06-05 05:06:43 +02:00
camoroso e4ded4f746 operator-assign.go: some errors message changed to report the Expr's type names, not the Go ones 2024-06-05 05:05:40 +02:00
camoroso 4e3af837e6 list-type.go: constructor newListA(), called without arguments, now creates an empty list, not a nil object 2024-06-05 05:03:37 +02:00
camoroso ca12722c93 utils.go: new function typeName() 2024-06-05 05:01:34 +02:00
camoroso d96123ab02 The assign operator '=' can now set items in ListType and DictType 2024-06-04 11:07:35 +02:00
camoroso f2d6d63017 fixed an error message (test nr 97) 2024-06-04 11:04:59 +02:00
camoroso 905b2af7fa setItem() function 2024-06-04 11:04:00 +02:00
camoroso 9307473d08 Call() implementation can invoke varaibles holding functors 2024-06-04 11:03:24 +02:00
camoroso 10a596a4cd parser.go: commented out some useless code 2024-06-04 11:02:26 +02:00
camoroso 609fb21505 global-context.go: variables holding function values can be invoked as function now 2024-06-04 11:01:04 +02:00
camoroso 7650a4a441 DictType moved from operand-dict.go to the dict-type.go file 2024-06-04 10:59:25 +02:00
camoroso f51d6023ae Doc: start of function documentation 2024-06-03 06:27:14 +02:00
camoroso 99454227d5 little changes to test source files 2024-06-02 12:33:32 +02:00
camoroso 75358e5d35 func-fmt.go: print() and println() 2024-06-02 12:32:08 +02:00
camoroso 51b272dda8 func-string.go: ad least one extra prefix and suffix for startsWithStr() and endsWitchStr() 2024-06-02 12:30:17 +02:00
camoroso 7f282e5460 func-string.go: the second parameter joinStr() flags changed as pfRepeat 2024-06-02 11:56:17 +02:00
camoroso cff21b40f7 typeFilepath changed 2024-06-02 11:54:43 +02:00
camoroso 4f432da2b9 typeFilepath and typeDirpath renamed as paramFilepath and paramDirpath respectively 2024-06-02 11:32:47 +02:00
camoroso e4b4b4fb79 function.go: if the right most parameter is repeteable (pfRepeat), then it does not increment the min-arg count 2024-06-02 11:30:24 +02:00
camoroso c04678c426 simple-store.go: duplicated name removed from funcs list 2024-06-02 11:12:53 +02:00
camoroso 9bba40f155 Expr's functions now support parameters with default value 2024-06-01 19:56:40 +02:00
camoroso f66cd1fdb1 New test file for specific code section or data type 2024-06-01 16:31:50 +02:00
camoroso f41ea96d17 Expr functions now act as closures 2024-05-30 07:13:26 +02:00
camoroso d84e690ef3 deep list inclusion and item membership implemented 2024-05-29 13:03:58 +02:00
camoroso 4b25a07699 commented code removed 2024-05-28 07:28:33 +02:00
camoroso 3736214c5a A lot of changes. Main ones are:
- fraction type renamed as FractionType and moved from operator-fraction.go to fraction-type.go
- ListType moved from operator-list.go to list-type.go
- all test file were renamed adding the "t_" prefix
- defined a test template in file t_temple_test.go
- new test file t_relational_test.go where relational tests are collected
- lists can now compared as set using operators <, <=, >, and >= (IMPORTANT: here = menas same content, not same list)
2024-05-28 07:26:05 +02:00
camoroso 78cbb7b36f test index/5 moved to list/26 because reflection's deep-equal function returns false even though computed and wanted lists are equal 2024-05-26 06:30:42 +02:00
camoroso 2c87d6bf9e Eprx now supports range of indeces to extract parts of strings or lists 2024-05-26 06:19:08 +02:00
camoroso 691c213d17 operator-dot.go: the '.' (dot) operator can now only be used to call member functions of iterators 2024-05-25 03:35:17 +02:00
camoroso fa136cb70b parser.go: square brackets are also allowed after a variable 2024-05-25 03:32:13 +02:00
camoroso 76dd01afcd strings_test.go: test nr 5,6 fixed 2024-05-25 03:30:26 +02:00
camoroso 4283fab816 list_test.go: test nr 15,16,17 fixed 2024-05-25 03:28:01 +02:00
camoroso 03d4c192c2 new syntax to get items from collection: collection[index]. Supported collections are string, list and dict 2024-05-24 22:51:01 +02:00
camoroso e5f63c3d9d function definition and usage rationalized 2024-05-24 06:28:48 +02:00
camoroso d545a35acf local var renamed 2024-05-24 04:23:03 +02:00
camoroso e4275e2cb6 simple-var-store.go and simple-func-store.go merged in a single file named simple-store.go 2024-05-23 07:46:31 +02:00
camoroso 1ff5770264 New interface and implementation to model function parameters 2024-05-22 20:52:44 +02:00
camoroso ba32e2dccf Doc: more details on some operators 2024-05-20 15:50:45 +02:00
camoroso f22b5a6f0b Doc: more details on some operators 2024-05-20 06:59:49 +02:00
camoroso 7c8dbb0ac7 Added virtual symbol SymVariable translated from general real SymIdentifier symbol 2024-05-20 06:05:24 +02:00
camoroso e5c5920db0 parser_test.go: incompatible type error corrected 2024-05-20 05:32:28 +02:00
camoroso 61efdb4eef operator-fraction.go: Typer interface implementation 2024-05-20 05:31:20 +02:00
camoroso 82ec78719d operand-list.go: ToString() now can handle the Truncate option 2024-05-20 05:30:26 +02:00
camoroso 554ff1a9dd operator-sum.go: better type checking when adding fractions 2024-05-20 05:27:44 +02:00
camoroso 6bb891e09d term.go: Error messagge about incompatible types now truncates long values 2024-05-20 05:26:33 +02:00
camoroso 1c4ffd7d64 formatter.go: Truncate function and number type names 2024-05-20 05:25:04 +02:00
camoroso b92b19e1dd New interface to Typer: the function TypeName() returns a more readable type name 2024-05-19 02:23:28 +02:00
camoroso 9967918418 operator-sum.go: adding item to a list is no more allowed. The sum operator '+' now ca only join two list. 2024-05-19 02:20:36 +02:00
camoroso 6c14c07d66 operand-iterator.go: adapted to the new DictType 2024-05-19 01:47:06 +02:00
camoroso 9ea170e53b new operator 'in': it returns true if a item belongs to a list or if a key belongs to a dict 2024-05-19 01:44:50 +02:00
camoroso a543360151 when the list value involved in an insert or append operations (directly) comes from a variable, that variable receives the result list 2024-05-19 01:42:15 +02:00
camoroso 24a25bbf94 adapted and enhanced the dict operations to make them compatible with the new DictType 2024-05-19 01:38:07 +02:00
camoroso d6a1607041 The content of round bracket now returns an expressione term. This way the content is decoupled from external terms. 2024-05-19 01:34:07 +02:00
camoroso 4d43ab2c2f context.go: setVar() renamed as UnsafeSetVar() 2024-05-19 01:27:44 +02:00
camoroso 9bd4a0ba23 utils.go:fromGenericAny() now supports also ListType and DictType 2024-05-19 01:21:06 +02:00
camoroso 2b184cf3f2 operand-map.go replaced by operand-dict.go 2024-05-19 01:20:04 +02:00
camoroso 263e419d9a operand-map.go: to be removed 2024-05-18 08:54:18 +02:00
camoroso c39970fa7e new operator 'in' added. It check if an item is member of a list, or if a key is contained in a dictionary 2024-05-18 07:47:41 +02:00
camoroso 14bb9e942b operator-fraction.go: added link to a tutorial about fractions 2024-05-18 07:07:17 +02:00
camoroso 9451958218 utils_test.go: new test file 2024-05-18 07:06:06 +02:00
camoroso 91fdc1926e Doc: updated last change date and time 2024-05-17 15:48:17 +02:00
camoroso 9a3abdf1b6 Doc: more details on the syntax of the selector and variable default operators 2024-05-17 15:46:56 +02:00
camoroso ac3e690f87 Doc: more details on the syntax of the dictionaries, variables and multi-expressions 2024-05-17 07:31:13 +02:00
camoroso f0a152a17a Doc: more details on the syntax of the numbers, strings and boolean 2024-05-16 07:11:20 +02:00
camoroso ca89931ca9 Doc: more details on the syntax of the numbers 2024-05-15 22:06:26 +02:00
camoroso d2e8aed4f7 parser_test.go: test on divisopn by-zero 2024-05-15 22:05:49 +02:00
camoroso 8138cd2a80 operand-func.go: commented the errors moved to common-errors..go 2024-05-15 22:04:38 +02:00
camoroso 6786666cf4 funcs_test.go: added some tests 2024-05-15 22:03:41 +02:00
camoroso 35e794701a func-string.go: commented out the param count check 2024-05-15 22:03:03 +02:00
camoroso 52ef134be6 func-base.go: commented out the param count check 2024-05-15 22:02:07 +02:00
camoroso 624318d84e common-errors.go: moved here some errors 2024-05-15 22:01:13 +02:00
camoroso aa1338cd51 Fixed special convertion case from decimal 'x.y()' to fraction 2024-05-15 06:45:40 +02:00
camoroso c9db4b84e3 funcs_test.go: added some tests 2024-05-14 05:41:53 +02:00
camoroso e7e9330b71 scanner: Fixed decimal number parser; it didn't save parenthesis around period part 2024-05-14 05:41:10 +02:00
camoroso 8eb25bbc86 operand-const.go -> operand-literal.go 2024-05-14 04:59:55 +02:00
camoroso efc92d434b the sum operation now supports dicts too 2024-05-14 04:56:24 +02:00
camoroso 4151f3f5e2 literals of rational number, e.g. 1.2(3), are now supported and are evaluated as fractions 2024-05-14 04:55:16 +02:00
camoroso f028485caa added functions to get the fenerating fraction of a decimal number 2024-05-13 14:24:37 +02:00
camoroso ab07405cda common-errors.go: added errDivisionByZero() 2024-05-13 14:23:21 +02:00
camoroso 47be0c66cf Doc: some typo and better examples 2024-05-11 20:14:54 +02:00
camoroso 50e7168214 Since now builtin functions are registared in a new global context. This reduces the effort to copy the whole set of builtin functions in the context of a function call; only the called function will be copied, if it is global. 2024-05-11 10:45:38 +02:00
camoroso 0a9543543d Remove the unused 'parent' param from the function newTerm(). 2024-05-11 06:41:06 +02:00
camoroso 775751c67b Doc: string examples 2024-05-11 06:35:32 +02:00
camoroso 4aaffd6c44 dict: implemented length of dict 2024-05-10 09:25:18 +02:00
camoroso 924051fbcd extended funcs and list tests 2024-05-10 09:18:32 +02:00
camoroso 5a9b6525a2 new function isRational() that return true is the passed value is integere or fraction 2024-05-10 09:17:51 +02:00
camoroso 8c66d90532 operator-length.go: fixed length of list 2024-05-10 09:16:31 +02:00
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
camoroso 8f396a35de ExprContext.SetVar() no longer requires the explicit specification of the type of number 2024-04-09 07:12:22 +02:00
camoroso bd323efedf README.adoc: example programs updated 2024-04-09 07:09:23 +02:00
camoroso a9b143d012 README.adoc: example programs updated 2024-04-09 06:29:44 +02:00
camoroso 024ff42be0 Arg struct members are now exported 2024-04-09 06:28:57 +02:00
camoroso 8ab2c28343 EvalArg -> Arg 2024-04-09 06:13:12 +02:00
camoroso c4a2fcce3d README.adoc: proofreading 2024-04-09 05:42:50 +02:00
camoroso 4d94a7ad59 class and kind types removed 2024-04-09 05:32:50 +02:00
camoroso 9ac88bf446 made some interfaces exportable and fixed/enhaaced some selector operator versions 2024-04-08 23:17:56 +02:00
camoroso aa195b9a68 README.adoc: section on selector operator 2024-04-08 23:14:25 +02:00
camoroso d3f388f7e1 Selector operator, multi-operand, added 2024-04-08 22:16:07 +02:00
camoroso f74e523617 test on '@@' 2024-04-06 03:09:02 +02:00
camoroso 7612a59757 Operator '@@' (export-all) added. Experimental include() function also added 2024-04-06 03:06:07 +02:00
camoroso ce6b88ccdd present.go -> control.go 2024-04-06 03:01:57 +02:00
camoroso 43b7d3b15e constant prefix name 'preset_' changed to 'control_'. 'control_export_all' constant added. 2024-04-06 03:01:17 +02:00
camoroso 181a9210d5 parser.go -- Fix: reset to true the firstToken flag after getting a semicolon 2024-04-06 01:32:29 +02:00
camoroso 7f9d7690f9 SimpleFuncStore is now derived from SimpleVarStore 2024-04-06 01:07:06 +02:00
camoroso 0ba96e65a5 Variable reference 2024-04-06 01:00:29 +02:00
camoroso 574a6f5215 Expression process diagram added 2024-04-04 12:56:43 +02:00
camoroso d073d11ad3 Variable references belonging to the parent scope added ('@') 2024-04-04 12:54:26 +02:00
camoroso 8c3254a8f2 typo 2024-04-03 13:19:18 +02:00
camoroso fccfd2f971 Operators '??' and '?=' added 2024-04-03 13:15:25 +02:00
camoroso 088e347c95 Changed SetValue() as SetVar() and GetValue() as GetVar() 2024-04-03 06:29:57 +02:00
camoroso 6c02b02d4a New draft 2024-04-03 06:23:45 +02:00
camoroso 4fc8ac64e7 README.adoc: examples, relational operators table completed 2024-04-02 08:18:34 +02:00
camoroso 4683a08da2 import() function added 2024-04-02 06:49:16 +02:00
camoroso 6aada9f7e4 funcs-math.go -> func-math.go 2024-04-02 05:18:30 +02:00
camoroso 4e8ebbef04 the preset.go file 2024-04-02 05:02:15 +02:00
camoroso f29b1f13b1 preset definitions and functions were moved to the new preset.go file 2024-04-02 05:00:48 +02:00
camoroso 072dab4144 Expressions now support function definition 2024-04-02 04:36:03 +02:00
camoroso f58ec3ac32 parser: now checks whether the assign operator '=' is preceded by a variable 2024-03-31 06:38:46 +02:00
camoroso 28e3b2f741 parser: accepts espression forest (multi expressions) 2024-03-31 06:10:27 +02:00
camoroso 4e361f938e ast: now supports espression forest (multi expressions) 2024-03-31 05:57:02 +02:00
camoroso aa705e68bf 'and' and 'or' operators are now evaluated using logic shortcut (this behaviour can be changed setting system var '_bool_shortcut' to false 2024-03-31 05:09:24 +02:00
camoroso 94ad968d5e ast.go: presetting of system variables before starting evaluation process 2024-03-31 05:06:24 +02:00
camoroso e3e5ad7da8 Farmatting changes 2024-03-30 08:12:47 +01:00
camoroso d0572260c7 New operators 'but' and assignment ('=') 2024-03-30 08:09:41 +01:00
camoroso 43fd457383 term.go: added priority levels priBut and priAssign 2024-03-30 08:08:49 +01:00
camoroso e085502df3 symbol.go: New keyword 'but' 2024-03-30 08:08:11 +01:00
camoroso 85fb007a2b The plus and minus operators now support lists for join and difference respectively 2024-03-30 07:05:22 +01:00
camoroso f540ec28e8 funcs-math.go: Implementes add() and mul() function for both simple values and iterators 2024-03-30 07:01:00 +01:00
camoroso 36feed3168 parser.go: changedthe message error about unknown functiona 2024-03-30 06:58:29 +01:00
camoroso 836a9165a5 Added Iterator interface and two implementation: list itrator and range iterator 2024-03-30 06:56:12 +01:00
camoroso e012afa691 Added the RegisterFunc() interface to the expression context 2024-03-30 06:54:43 +01:00
camoroso 20007a5a81 utils.go: New function isList() that checks is a value is an array of any 2024-03-30 06:50:49 +01:00
camoroso 107484d13c parser_test.go: add test on the list data type 2024-03-28 08:52:54 +01:00
camoroso c36c88d0fd Added list '[]' data type. Fix: function with no arguments 2024-03-28 08:51:02 +01:00
camoroso 3ca415fc66 Merge branch 'main' of ssh://git.portale-stac.it:3022/go-pkg/expr 2024-03-28 06:34:53 +01:00
camoroso ae2f7c89bd helpers_test.go: changed some comments 2024-03-28 06:34:40 +01:00
camoroso 27f53db0f4 helpers_test.go: tests on the helper functions 2024-03-28 06:33:14 +01:00
camoroso 6d8c6f5154 helpers.go provides easy functions to parse and evaluate expressions 2024-03-28 06:32:37 +01:00
camoroso d20027296c New test on the tilde operator. Now it is based on simple-var-store 2024-03-28 06:30:45 +01:00
camoroso 37f0de5902 Added simple context objects 2024-03-28 06:29:11 +01:00
camoroso fdc2fd8dfd utils.go: added functions anyInteger() and anyFloat() 2024-03-28 06:26:20 +01:00
camoroso 9fa3d9fcb2 Added the tilde '~' operator acting as NOT 2024-03-28 06:25:29 +01:00
camoroso 45c0663ea1 Second incomplete draft 2024-03-27 08:44:47 +01:00
camoroso 998580772a added operator '%' (division remainder) and test 2024-03-26 09:27:14 +01:00
camoroso 594806c999 added operator './' (float division) and test 2024-03-26 09:12:44 +01:00
camoroso 800664c98c added go.mod 2024-03-26 08:56:20 +01:00
camoroso 4bb1e9abcd Merge branch 'main' of ssh://git.portale-stac.it:3022/go-pkg/expr 2024-03-26 08:45:37 +01:00
camoroso 33841c5861 Added copyrighr note to all sources 2024-03-26 08:45:18 +01:00
camoroso eaf5e29a0c byte-ring.go -> byte-slider.go 2024-03-26 08:44:56 +01:00
camoroso 3586aadbc1 First incomplete draft 2024-03-26 07:36:12 +01:00
camoroso 19ab171109 README.md -> README.adoc 2024-03-26 07:17:27 +01:00
168 changed files with 19538 additions and 1929 deletions
+147
View File
@@ -0,0 +1,147 @@
= README
README about the 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::[]
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
A few examples to get started.
.Examples taken from parser_test.go
[source,go]
----
`1.0 / 2` // 0.5
`435 + 105 * 2 - 1` // 644
`4 == (3-1)*(10/5)` // true
`"uno" * (2+1)` // `unounouno`
`2+3 but 5*2` // 10 <1>
`add(add(1+4),3+2,5*(3-2))` // 15 <2>
`a=5; b=2; add(a, b*3)` // 11 <3>
`two=func(){2}; two()` // 2 <4>
`double=func(x){2*x}; a=4+1; two=func() {2}; (double(3+a) + 1) * two()` // 34
`import("./test-funcs.expr"); (double(3+a) + 1) * two()` // 34 <5>
`[1,2,"hello"]` // Mixed types list
`[1,2]+[3]` // append list, result: [1,2,3]
`add([1,[2,2],3,2])` // Deep list sum, result: 10 <2>
`[a=1,b=2,c=3] but a+b+c` // 6
----
<1> [blue]`but` operator.
<2> The _add()_ function definition may be changed in the future.
<3> Multiple expression. Only the last expression value will returned.
<4> Simple function definition: _two()_ returns 2.
<5> _import()_ function imports expressions from the specified files. See file _test-funcs.expr_.
=== Usage
[source,go]
----
package main
import (
"fmt"
"strings"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
ctx := expr.NewSimpleVarStore()
ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var`
r := strings.NewReader(source)
scanner := expr.NewScanner(r, expr.DefaultTranslations())
parser := expr.NewParser(ctx)
if ast, err := parser.Parse(scanner); err == nil {
if result, err := ast.Eval(ctx); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else {
fmt.Println("Error calculating the expression:", err)
}
} else {
fmt.Println("Error parsing the expression:", err)
}
}
----
The above program is equivalent to the following one.
[source,go]
----
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
ctx := expr.NewSimpleVarStore()
ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var`
if result, err := expr.EvalString(ctx, source); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else {
fmt.Println("Error calculating the expression:", err)
}
}
----
Here is another equivalent version.
[source,go]
----
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
source := `(3-1)*(10/5) == var`
if result, err := expr.EvalStringA(source, expr.Arg{"var", 4}); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else {
fmt.Println("Error calculating the expression:", err)
}
}
----
== Context of evaluation
Unless helpers functions like _expr.EvalStringA()_ are used, a _context_ is required to compute an expession.
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 https://cdn.paas.portale-stac.it/howto/go-pkg/expr/doc/Expr.html[Expr documentation] for a complete reference of the expression syntax and available functions.
View File
-84
View File
@@ -1,84 +0,0 @@
// ast.go
package expr
import (
"errors"
"fmt"
"strings"
)
//-------- ast
type ast struct {
root *term
}
func NewAst() *ast {
return &ast{}
}
func (self *ast) String() string {
var sb strings.Builder
if self.root == nil {
sb.WriteString("(nil)")
} else {
self.root.toString(&sb)
}
return sb.String()
}
func (self *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens {
if err = self.addToken(tk); err != nil {
break
}
}
return
}
func (self *ast) addToken(tk *Token) (err error) {
if t := newTerm(tk, nil); t != nil {
err = self.addTerm(t)
} else {
err = fmt.Errorf("No term constructor for token %q", tk.String())
}
return
}
func (self *ast) addTerm(node *term) (err error) {
if self.root == nil {
self.root = node
} else {
self.root, err = self.insert(self.root, node)
}
return
}
func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.getPriority() < node.getPriority() {
root = tree
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
subRoot, err = self.insert(last, node)
subRoot.setParent(tree)
} else {
node.setParent(tree)
}
} else if !node.isLeaf() {
root = node
tree.setParent(node)
} else {
err = fmt.Errorf("two adjacent operators: %q and %q", tree, node)
}
return
}
func (self *ast) eval(ctx exprContext) (result any, err error) {
if self.root != nil {
result, err = self.root.compute(ctx)
} else {
err = errors.New("empty expression")
}
return
}
-50
View File
@@ -1,50 +0,0 @@
// ast_test.go
package expr
import (
"errors"
"testing"
)
func TestAstString(t *testing.T) {
tree := NewAst()
if gotResult := tree.String(); gotResult != "(nil)" {
t.Errorf(`result: got %q, want "(nil)"`, gotResult)
}
}
func TestAddTokensGood(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr != nil {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestAddTokensBad(t *testing.T) {
tk0 := NewValueToken(SymInteger, "200", 200)
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
wantErr := errors.New(`two adjacent operators: "200" and "100"`)
tree := NewAst()
if gotErr := tree.addTokens(tk0, tk1, tk2, tk3); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestAddUnknownTokens(t *testing.T) {
tk0 := NewToken(SymPercent, "%")
wantErr := errors.New(`No term constructor for token "%"`)
tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
}
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
)
// ---- Linking with Expr functions
type exprFunctor struct {
kern.BaseFunctor
params []kern.ExprFuncParam
expr kern.Expr
defCtx kern.ExprContext
}
func (functor *exprFunctor) GetParams() (params []kern.ExprFuncParam) {
return functor.params
}
func newExprFunctor(e kern.Expr, params []kern.ExprFuncParam, ctx kern.ExprContext) *exprFunctor {
var defCtx kern.ExprContext
if ctx != nil {
defCtx = ctx
}
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
func (functor *exprFunctor) TypeName() string {
return "ExprFunctor"
}
func (functor *exprFunctor) GetDefinitionContext() kern.ExprContext {
return functor.defCtx
}
func (functor *exprFunctor) InvokeNamed(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var missing []string
for _, p := range functor.params {
if arg, exists := args[p.Name()]; exists {
if funcArg, ok := arg.(kern.Functor); ok {
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, kern.TypeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
if missing == nil {
missing = make([]string, 0, 1)
}
missing = append(missing, p.Name())
// ctx.UnsafeSetVar(p.Name(), nil)
}
}
if missing != nil {
err = kern.ErrMissingParams(name, missing)
} else {
result, err = functor.expr.Eval(ctx)
}
return
}
+319
View File
@@ -0,0 +1,319 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-base.go
package expr
import (
"fmt"
"math"
"strconv"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
const (
ParamDenominator = "denominator"
)
func isNilFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = args[kern.ParamValue] == nil
return
}
func isIntFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsInteger(args[kern.ParamValue])
return
}
func isFloatFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFloat(args[kern.ParamValue])
return
}
func isBoolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsBool(args[kern.ParamValue])
return
}
func isStringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsString(args[kern.ParamValue])
return
}
func isFractionFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsFraction(args[kern.ParamValue])
return
}
func isRationalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsRational(args[kern.ParamValue])
return
}
func isListFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsList(args[kern.ParamValue])
return
}
func isDictionaryFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = kern.IsDict(args[kern.ParamValue])
return
}
func boolFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
result = (v != 0)
case *kern.FractionType:
result = v.N() != 0
case float64:
result = v != 0.0
case bool:
result = v
case string:
result = len(v) > 0
case *kern.ListType:
result = len(*v) > 0
case *kern.DictType:
result = len(*v) > 0
default:
err = kern.ErrCantConvert(name, v, "bool")
}
return
}
func intFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(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)
}
case *kern.FractionType:
result = int64(v.N() / v.D())
default:
err = kern.ErrCantConvert(name, v, "int")
}
return
}
func decFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
result = float64(v)
case float64:
result = v
case bool:
if v {
result = float64(1)
} else {
result = float64(0)
}
case string:
var f float64
if f, err = strconv.ParseFloat(v, 64); err == nil {
result = f
}
case *kern.FractionType:
result = v.ToFloat()
default:
err = kern.ErrCantConvert(name, v, "float")
}
return
}
func stringFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
result = strconv.FormatInt(v, 10)
case float64:
result = strconv.FormatFloat(v, 'g', -1, 64)
case bool:
if v {
result = "true"
} else {
result = "false"
}
case string:
result = v
case *kern.FractionType:
result = v.ToString(0)
case kern.Formatter:
result = v.ToString(0)
case fmt.Stringer:
result = v.String()
default:
err = kern.ErrCantConvert(name, v, "string")
}
return
}
func fractFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[kern.ParamValue].(type) {
case int64:
var den int64 = 1
var ok bool
if den, ok = args[ParamDenominator].(int64); !ok {
err = kern.ErrExpectedGot(name, "integer", args[ParamDenominator])
} else if den == 0 {
err = kern.ErrFuncDivisionByZero(name)
}
if err == nil {
result = kern.NewFraction(v, den)
}
case float64:
result, err = kern.Float64ToFraction(v)
case bool:
if v {
result = kern.NewFraction(1, 1)
} else {
result = kern.NewFraction(0, 1)
}
case string:
result, err = kern.MakeGeneratingFraction(v)
case *kern.FractionType:
result = v
default:
err = kern.ErrCantConvert(name, v, "float")
}
return
}
// func iteratorFunc(ctx expr.ExprContext, name string, args []any) (result any, err error) {
// return
// }
func evalFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[kern.ParamSource].(string); ok {
var ast kern.Expr
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStoreWithoutGlobalContext()
}
r := strings.NewReader(source)
scanner := scan.NewScanner(r, scan.DefaultTranslations())
if ast, err = parser.Parse(scanner); err == nil {
CtrlEnable(ctx, kern.ControlExportAll)
result, err = ast.Eval(ctx)
}
} else {
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
return
}
func varFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
}
if result, ok = args[kern.ParamValue]; ok && result != nil {
ctx.GetParent().UnsafeSetVar(varName, result)
// } else {
// err = expr.ErrWrongParamType(name, expr.ParamSource, expr.TypeString, args[expr.ParamSource])
// }
} else if result, ok = ctx.GetVar(varName); !ok {
err = kern.ErrUnknownVar(name, varName)
}
return
}
func setFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
}
if result, ok = args[kern.ParamValue]; ok {
ctx.GetParent().UnsafeSetVar(varName, result)
} else {
err = kern.ErrWrongParamType(name, kern.ParamValue, kern.TypeAny, args[kern.ParamValue])
}
return
}
// func unsetFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
// var varName string
// var ok bool
// if varName, ok = args[kern.ParamName].(string); !ok {
// return nil, kern.ErrWrongParamType(name, kern.ParamName, kern.TypeString, args[kern.ParamName])
// } else {
// ctx.GetParent().DeleteVar(varName)
// result = nil
// }
// return
// }
//// import
func ImportBuiltinsFuncs(ctx kern.ExprContext) {
anyParams := []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
}
ctx.RegisterFunc("isNil", kern.NewGolangFunctor(isNilFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isInt", kern.NewGolangFunctor(isIntFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isFloat", kern.NewGolangFunctor(isFloatFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isBool", kern.NewGolangFunctor(isBoolFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isString", kern.NewGolangFunctor(isStringFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isFract", kern.NewGolangFunctor(isFractionFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isRational", kern.NewGolangFunctor(isRationalFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isList", kern.NewGolangFunctor(isListFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("isDict", kern.NewGolangFunctor(isDictionaryFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("bool", kern.NewGolangFunctor(boolFunc), kern.TypeBoolean, anyParams)
ctx.RegisterFunc("int", kern.NewGolangFunctor(intFunc), kern.TypeInt, anyParams)
ctx.RegisterFunc("dec", kern.NewGolangFunctor(decFunc), kern.TypeFloat, anyParams)
ctx.RegisterFunc("string", kern.NewGolangFunctor(stringFunc), kern.TypeString, anyParams)
ctx.RegisterFunc("fract", kern.NewGolangFunctor(fractFunc), kern.TypeFraction, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
kern.NewFuncParamFlagDef(ParamDenominator, kern.PfDefault, int64(1)),
})
ctx.RegisterFunc("eval", kern.NewGolangFunctor(evalFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
ctx.RegisterFunc("var", kern.NewGolangFunctor(varFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault, nil),
})
ctx.RegisterFunc("set", kern.NewGolangFunctor(setFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
kern.NewFuncParam(kern.ParamValue),
})
// ctx.RegisterFunc("unset", kern.NewGolangFunctor(unsetFunc), kern.TypeAny, []kern.ExprFuncParam{
// kern.NewFuncParam(kern.ParamName),
// kern.NewFuncParam(kern.ParamValue),
// })
}
func init() {
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-fmt.go
package expr
import (
"fmt"
"io"
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
func getStdout(ctx kern.ExprContext) io.Writer {
var w io.Writer
if wany, exists := ctx.GetVar(kern.ControlStdout); exists && wany != nil {
w, _ = wany.(io.Writer)
}
if w == nil {
w = os.Stdout
}
return w
}
func printFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var n int = 0
if v, exists := args[kern.ParamItem]; exists && v != nil {
argv := v.([]any)
n, err = fmt.Fprint(getStdout(ctx), argv...)
}
result = int64(n)
return
}
func printLnFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var n int = 0
if v, exists := args[kern.ParamItem]; exists && v != nil {
argv := v.([]any)
n, err = fmt.Fprintln(getStdout(ctx), argv...)
} else {
n, err = fmt.Fprintln(getStdout(ctx))
}
result = int64(n)
return
}
func ImportFmtFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("print", kern.NewGolangFunctor(printFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
})
ctx.RegisterFunc("println", kern.NewGolangFunctor(printLnFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
})
}
func init() {
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-import.go
package expr
import (
"io"
"os"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
func importFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func importAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
CtrlEnable(ctx, kern.ControlExportAll)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
dirList := buildSearchDirList(ctx, "sources", ENV_EXPR_SOURCE_PATH)
if v, exists := args[kern.ParamFilepath]; exists && v != nil {
argv := v.([]any)
result, err = doImport(ctx, name, dirList, NewArrayIterator(argv))
}
return
}
func doImport(ctx kern.ExprContext, name string, dirList []string, it kern.Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, int(it.Index())); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *scan.Ast
scanner := scan.NewScanner(file, scan.DefaultTranslations())
parser := NewParser()
if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, scan.SymEos); err == nil {
result, err = expr.Eval(ctx)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("import", kern.NewGolangFunctor(importFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
})
ctx.RegisterFunc("importAll", kern.NewGolangFunctor(importAllFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamFilepath, kern.PfRepeat),
})
}
func init() {
RegisterBuiltinModule("import", ImportImportFuncs, "Functions import() and include()")
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-iterator.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
)
const (
iterParamOperator = "operator"
iterParamVars = "vars"
iterVarStatus = "status"
)
func parseRunArgs(localCtx kern.ExprContext, args map[string]any) (it kern.Iterator, op kern.Functor, err error) {
var ok bool
if it, ok = args[kern.ParamIterator].(kern.Iterator); !ok {
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", kern.ParamIterator, args[kern.ParamIterator], kern.TypeName(args[kern.ParamIterator]))
return
}
if args[iterParamOperator] != nil {
if op, ok = args[iterParamOperator].(kern.Functor); !ok || op == nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], kern.TypeName(args[iterParamOperator]))
return
}
}
var vars *kern.DictType
if vars, ok = args[iterParamVars].(*kern.DictType); !ok && args[iterParamVars] != nil {
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], kern.TypeName(args[iterParamVars]))
return
}
if vars != nil {
for key, value := range *vars {
var varName string
if varName, ok = key.(string); ok {
localCtx.UnsafeSetVar(varName, value)
}
}
}
return
}
func runFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var it kern.Iterator
var ok bool
var op kern.Functor
var v any
// var usingDefaultOp = false
var params map[string]any
var item any
localCtx := ctx.Clone()
localCtx.UnsafeSetVar(iterVarStatus, nil)
if it, op, err = parseRunArgs(localCtx, args); err != nil {
return
}
for item, err = it.Next(); err == nil; item, err = it.Next() {
if op != nil {
params = map[string]any{kern.ParamIndex: it.Index(), kern.ParamItem: item}
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = kern.ToBool(v); !success || !ok {
break
}
}
}
}
if err == io.EOF {
err = nil
}
if err == nil {
if op == nil {
ctx.UnsafeSetVar(iterVarStatus, it.Count())
}
result, _ = localCtx.GetVar(iterVarStatus)
}
return
}
func ImportIterFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("run", kern.NewGolangFunctor(runFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamIterator),
kern.NewFuncParamFlag(iterParamOperator, kern.PfOptional),
kern.NewFuncParamFlag(iterParamVars, kern.PfOptional),
})
}
func init() {
RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
}
+185
View File
@@ -0,0 +1,185 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-math-arith.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
if !(kern.IsNumber(paramValue) || kern.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 kern.ExprContext, name string, it kern.Iterator, count, level int) (result any, err error) {
var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0
var intSum int64 = 0
var fractSum *kern.FractionType
var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*kern.ListType); ok {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(kern.Iterator); ok {
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(kern.ExtIterator); ok && extIter.HasOperation(kern.CleanName) {
if _, err = extIter.CallOperation(kern.CleanName, nil); err != nil {
return
}
}
} else if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
break
}
count++
if !sumAsFloat {
if kern.IsFloat(v) {
sumAsFloat = true
if sumAsFract {
floatSum = fractSum.ToFloat()
} else {
floatSum = float64(intSum)
}
} else if !sumAsFract && kern.IsFraction(v) {
fractSum = kern.NewFraction(intSum, 1)
sumAsFract = true
}
}
if sumAsFloat {
floatSum += kern.NumAsFloat(v)
} else if sumAsFract {
var item *kern.FractionType
var ok bool
if item, ok = v.(*kern.FractionType); !ok {
iv, _ := v.(int64)
item = kern.NewFraction(iv, 1)
}
fractSum = kern.SumFract(fractSum, item)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil || err == io.EOF {
err = nil
if sumAsFloat {
result = floatSum
} else if sumAsFract {
result = fractSum
} else {
result = intSum
}
}
return
}
func addFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
argv := args[kern.ParamValue].([]any)
result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1)
return
}
func doMul(ctx kern.ExprContext, name string, it kern.Iterator, count, level int) (result any, err error) {
var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0
var intProd int64 = 1
var fractProd *kern.FractionType
var v any
level++
for v, err = it.Next(); err == nil; v, err = it.Next() {
if list, ok := v.(*kern.ListType); ok {
v = NewListIterator(list, nil)
}
if subIter, ok := v.(kern.Iterator); ok {
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
break
}
if extIter, ok := v.(kern.ExtIterator); ok && extIter.HasOperation(kern.CleanName) {
if _, err = extIter.CallOperation(kern.CleanName, nil); err != nil {
return
}
}
} else {
if err = checkNumberParamExpected(name, v, count, level, int(it.Index())); err != nil {
break
}
}
count++
if !mulAsFloat {
if kern.IsFloat(v) {
mulAsFloat = true
if mulAsFract {
floatProd = fractProd.ToFloat()
} else {
floatProd = float64(intProd)
}
} else if !mulAsFract && kern.IsFraction(v) {
fractProd = kern.NewFraction(intProd, 1)
mulAsFract = true
}
}
if mulAsFloat {
floatProd *= kern.NumAsFloat(v)
} else if mulAsFract {
var item *kern.FractionType
var ok bool
if item, ok = v.(*kern.FractionType); !ok {
iv, _ := v.(int64)
item = kern.NewFraction(iv, 1)
}
fractProd = kern.MulFract(fractProd, item)
} else {
iv, _ := v.(int64)
intProd *= iv
}
}
if err == nil || err == io.EOF {
err = nil
if mulAsFloat {
result = floatProd
} else if mulAsFract {
result = fractProd
} else {
result = intProd
}
}
return
}
func mulFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
argv := args[kern.ParamValue].([]any)
result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1)
return
}
func ImportMathFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("add", kern.NewGolangFunctor(addFunc), kern.TypeNumber, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(0)),
})
ctx.RegisterFunc("mul", kern.NewGolangFunctor(mulFunc), kern.TypeNumber, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, int64(1)),
})
}
func init() {
RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
}
+151
View File
@@ -0,0 +1,151 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
const paramHandleOrPath = "handle-or-path"
const fileReadTextIteratorType = "fileReadTextIterator"
type fileReadTextIterator struct {
osReader *osReader
index int64
count int64
line string
autoClose bool
}
func newReadTextIterator(r *osReader, autoClose bool) *fileReadTextIterator {
return &fileReadTextIterator{osReader: r, index: -1, autoClose: autoClose}
}
func (it *fileReadTextIterator) TypeName() string {
return fileReadTextIteratorType
}
func (it *fileReadTextIterator) String() string {
if it.osReader != nil && it.osReader.fh != nil {
return fmt.Sprintf("$(%s@%q)", fileReadTextIteratorType, it.osReader.fh.Name())
}
return fmt.Sprintf("$(%s@<nil>)", fileReadTextIteratorType)
}
func (it *fileReadTextIterator) Count() int64 {
return it.count
}
func (it *fileReadTextIterator) Next() (item any, err error) { // must return io.EOF after the last item
if it.osReader.fh != nil {
if it.line, err = it.osReader.reader.ReadString('\n'); err == nil {
it.index++
it.count++
item = it.line[0 : len(it.line)-1]
} else if it.autoClose {
it.Clean()
}
}
return
}
func (it *fileReadTextIterator) Current() (item any, err error) {
if len(it.line) > 0 {
item = it.line[0 : len(it.line)-1]
}
return
}
func (it *fileReadTextIterator) Index() int64 {
return it.index
}
func (it *fileReadTextIterator) Reset() (err error) {
if _, err = it.osReader.fh.Seek(0, io.SeekStart); err == nil {
it.index = -1
it.count = 0
it.line = ""
}
return
}
func (it *fileReadTextIterator) HasOperation(name string) bool {
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
}
func (it *fileReadTextIterator) Clean() (err error) {
if it.osReader.fh != nil {
if err = it.osReader.fh.Close(); err == nil {
it.osReader = nil
}
}
return nil
}
func (it *fileReadTextIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func fileReadIteratorFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle *osReader
var invalidFileHandle any
var ok, autoClose bool
result = nil
if handle, ok = args[paramHandleOrPath].(*osReader); !ok {
if fileName, ok := args[paramHandleOrPath].(string); ok && len(fileName) > 0 {
var handleAny any
if handleAny, err = openFileFunc(ctx, name, map[string]any{kern.ParamFilepath: fileName}); err != nil {
return
}
if handleAny != nil {
handle = handleAny.(*osReader)
autoClose = true
}
} else {
invalidFileHandle = args[paramHandleOrPath]
}
}
if handle != nil {
result = newReadTextIterator(handle, autoClose)
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
// func ImportOsIterFuncs(ctx ExprContext) {
// ctx.RegisterFunc("fileReadIterator", NewGolangFunctor(fileReadIteratorFunc), TypeIterator, []ExprFuncParam{
// NewFuncParam(paramHandleOrPath),
// })
// }
// func init() {
// RegisterBuiltinModule("os.file", ImportOsIterFuncs, "Operating system file iterator functions")
// }
+263
View File
@@ -0,0 +1,263 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-os-file.go
package expr
import (
"bufio"
"fmt"
"io"
"os"
"git.portale-stac.it/go-pkg/expr/kern"
)
const (
osLimitCh = "limitCh"
)
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) TypeName() string {
return "osWriter"
}
func (h *osWriter) String() string {
return "writer"
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) TypeName() string {
return "osReader"
}
func (h *osReader) String() string {
return "reader"
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func errMissingFilePath(funcName string) error {
return fmt.Errorf("%s(): missing or invalid file path", funcName)
}
func errInvalidFileHandle(funcName string, v any) error {
if v != nil {
return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v)
} else {
return fmt.Errorf("%s(): invalid file handle", funcName)
}
}
func createFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && 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 = errMissingFilePath(name)
}
return
}
func openFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && 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 = errMissingFilePath(name)
}
return
}
func appendFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[kern.ParamFilepath].(string); ok && 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 = errMissingFilePath(name)
}
return
}
func closeFileFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
if handle, ok = args[kern.ParamHandle].(osHandle); !ok {
invalidFileHandle = args[kern.ParamHandle]
}
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()
}
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, handle)
}
result = err == nil
return
}
func fileWriteTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
if handle, ok = args[kern.ParamHandle].(osHandle); !ok {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if w, ok := handle.(*osWriter); ok {
if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any)
result, err = fmt.Fprint(w.writer, argv...)
}
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
var limit byte = '\n'
var v string
if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
limit = s[0]
}
v, err = r.reader.ReadString(limit)
if err == io.EOF {
err = nil
}
if len(v) > 0 {
if v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextAllFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[kern.ParamHandle].(osHandle); !ok || args[kern.ParamHandle] == nil {
invalidFileHandle = args[kern.ParamHandle]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
var b []byte
b, err = io.ReadAll(r.reader)
result = string(b)
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func ImportOsFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("fileOpen", kern.NewGolangFunctor(openFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamFilepath),
})
ctx.RegisterFunc("fileAppend", kern.NewGolangFunctor(appendFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamFilepath),
})
ctx.RegisterFunc("fileCreate", kern.NewGolangFunctor(createFileFunc), kern.TypeFileHandle, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamFilepath),
})
ctx.RegisterFunc("fileClose", kern.NewGolangFunctor(closeFileFunc), kern.TypeBoolean, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
})
ctx.RegisterFunc("fileWriteText", kern.NewGolangFunctor(fileWriteTextFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
kern.NewFuncParamFlagDef(kern.ParamItem, kern.PfDefault|kern.PfRepeat, ""),
})
ctx.RegisterFunc("fileReadText", kern.NewGolangFunctor(fileReadTextFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
kern.NewFuncParamFlagDef(osLimitCh, kern.PfDefault, "\n"),
})
ctx.RegisterFunc("fileReadTextAll", kern.NewGolangFunctor(fileReadTextAllFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamHandle),
})
ctx.RegisterFunc("fileReadIterator", kern.NewGolangFunctor(fileReadIteratorFunc), kern.TypeIterator, []kern.ExprFuncParam{
kern.NewFuncParam(paramHandleOrPath),
})
}
func init() {
RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
}
+273
View File
@@ -0,0 +1,273 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-string.go
package expr
import (
"fmt"
"io"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
)
const (
strParamOther = "other"
)
// --- Start of function definitions
func doJoinStr(funcName string, sep string, it kern.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 = kern.ErrExpectedGot(funcName, kern.TypeString, v)
return
}
}
if err == io.EOF {
err = nil
result = sb.String()
}
return
}
func joinStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if sep, ok := args[kern.ParamSeparator].(string); ok {
if v, exists := args[kern.ParamItem]; exists {
argv := v.([]any)
if len(argv) == 1 {
if ls, ok := argv[0].(*kern.ListType); ok {
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
} else if it, ok := argv[0].(kern.Iterator); ok {
result, err = doJoinStr(name, sep, it)
} else if s, ok := argv[0].(string); ok {
result = s
} else {
err = kern.ErrInvalidParameterValue(name, kern.ParamItem, v)
}
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(argv))
}
}
} else {
err = kern.ErrWrongParamType(name, kern.ParamSeparator, kern.TypeString, args[kern.ParamSeparator])
}
return
}
func subStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var start = 0
var count = -1
var source string
var ok bool
if source, ok = args[kern.ParamSource].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if start, err = kern.ToGoInt(args[kern.ParamStart], name+"()"); err != nil {
return
}
if count, err = kern.ToGoInt(args[kern.ParamCount], 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 kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source string
var ok bool
if source, ok = args[kern.ParamSource].(string); !ok {
return nil, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
result = strings.TrimSpace(source)
return
}
func startsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source, prefix string
var ok bool
result = false
if source, ok = args[kern.ParamSource].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if prefix, ok = args[kern.ParamPrefix].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamPrefix, kern.TypeString, args[kern.ParamPrefix])
}
if strings.HasPrefix(source, prefix) {
result = true
} else if v, exists := args[strParamOther]; exists {
argv := v.([]any)
for i, targetSpec := range argv {
if target, ok := targetSpec.(string); ok {
if strings.HasPrefix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
break
}
}
}
return
}
func endsWithStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source, suffix string
var ok bool
result = false
if source, ok = args[kern.ParamSource].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if suffix, ok = args[kern.ParamSuffix].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSuffix, kern.TypeString, args[kern.ParamSuffix])
}
if strings.HasPrefix(source, suffix) {
result = true
} else if v, exists := args[strParamOther]; exists {
argv := v.([]any)
for i, targetSpec := range argv {
if target, ok := targetSpec.(string); ok {
if strings.HasSuffix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, kern.TypeName(targetSpec))
break
}
}
}
return
}
func splitStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var source, sep string
var count int = -1
var parts []string
var ok bool
if source, ok = args[kern.ParamSource].(string); !ok {
return result, kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
if sep, ok = args[kern.ParamSeparator].(string); !ok {
return nil, fmt.Errorf("separator param must be string, got %s (%v)", kern.TypeName(args[kern.ParamSeparator]), args[kern.ParamSeparator])
}
if count64, ok := args[kern.ParamCount].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %s (%v)", kern.TypeName(args[kern.ParamCount]), args[kern.ParamCount])
}
if count > 0 {
parts = strings.SplitN(source, sep, count)
} else if count < 0 {
parts = strings.Split(source, sep)
} else {
parts = []string{}
}
list := make(kern.ListType, len(parts))
for i, part := range parts {
list[i] = part
}
result = &list
return
}
func upperStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[kern.ParamSource].(string); ok {
result = strings.ToUpper(source)
} else {
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
return
}
func lowerStrFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if source, ok := args[kern.ParamSource].(string); ok {
result = strings.ToLower(source)
} else {
err = kern.ErrWrongParamType(name, kern.ParamSource, kern.TypeString, args[kern.ParamSource])
}
return
}
// --- End of function definitions
// Import above functions in the context
func ImportStringFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("strJoin", kern.NewGolangFunctor(joinStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSeparator),
kern.NewFuncParamFlag(kern.ParamItem, kern.PfRepeat),
})
ctx.RegisterFunc("strSub", kern.NewGolangFunctor(subStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParamFlagDef(kern.ParamStart, kern.PfDefault, int64(0)),
kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
})
ctx.RegisterFunc("strSplit", kern.NewGolangFunctor(splitStrFunc), "list of "+kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParamFlagDef(kern.ParamSeparator, kern.PfDefault, ""),
kern.NewFuncParamFlagDef(kern.ParamCount, kern.PfDefault, int64(-1)),
})
ctx.RegisterFunc("strTrim", kern.NewGolangFunctor(trimStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
ctx.RegisterFunc("strStartsWith", kern.NewGolangFunctor(startsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParam(kern.ParamPrefix),
kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
})
ctx.RegisterFunc("strEndsWith", kern.NewGolangFunctor(endsWithStrFunc), kern.TypeBoolean, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
kern.NewFuncParam(kern.ParamSuffix),
kern.NewFuncParamFlag(strParamOther, kern.PfRepeat),
})
ctx.RegisterFunc("strUpper", kern.NewGolangFunctor(upperStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
ctx.RegisterFunc("strLower", kern.NewGolangFunctor(lowerStrFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamSource),
})
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
RegisterBuiltinModule("string", ImportStringFuncs, "string utilities")
}
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtins-register.go
package expr
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
)
type builtinModule struct {
importFunc func(kern.ExprContext)
description string
imported bool
}
func newBuiltinModule(importFunc func(kern.ExprContext), description string) *builtinModule {
return &builtinModule{importFunc, description, false}
}
var builtinModuleRegister map[string]*builtinModule
func RegisterBuiltinModule(name string, importFunc func(kern.ExprContext), description string) {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
if _, exists := builtinModuleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
builtinModuleRegister[name] = newBuiltinModule(importFunc, description)
}
func IterateBuiltinModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range builtinModuleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
}
-31
View File
@@ -1,31 +0,0 @@
// byte-slider.go
package expr
import "bytes"
type ByteSlider struct {
buf []byte
length int
}
func NewByteSlider(size int) *ByteSlider {
return &ByteSlider{
buf: make([]byte, max(1, size)),
length: 0,
}
}
func (self *ByteSlider) PushEnd(b byte) {
if self.length == cap(self.buf) {
self.length--
for i := 0; i < self.length; i++ {
self.buf[i] = self.buf[i+1]
}
}
self.buf[self.length] = b
self.length++
}
func (self *ByteSlider) Equal(target []byte) bool {
return target != nil && bytes.Equal(self.buf, target)
}
-15
View File
@@ -1,15 +0,0 @@
// context.go
package expr
type exprFunc interface {
Name() string
MinArgs() int
MaxArgs() int
}
type exprContext interface {
GetValue(varName string) (value any, exists bool)
SetValue(varName string, value any)
GetFuncInfo(name string) exprFunc
Call(name string, args []any) (result any, err error)
}
+307
View File
@@ -0,0 +1,307 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// data-cursors.go
package expr
import (
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type dataCursor struct {
ds map[string]kern.Functor
ctx kern.ExprContext
initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
// cursorValid bool // true if resource is nil or if clean has not yet been called
index int64
count int64
current any
lastErr error
resource any
nextFunc kern.Functor
cleanFunc kern.Functor
resetFunc kern.Functor
}
func NewDataCursor(ctx kern.ExprContext, ds map[string]kern.Functor, resource any) (dc *dataCursor) {
dc = &dataCursor{
ds: ds,
initState: true,
// cursorValid: true,
index: -1,
count: 0,
current: nil,
lastErr: nil,
resource: resource,
ctx: ctx.Clone(),
nextFunc: ds[kern.NextName],
cleanFunc: ds[kern.CleanName],
resetFunc: ds[kern.ResetName],
}
return
}
func (dc *dataCursor) Context() kern.ExprContext {
return dc.ctx
}
func (dc *dataCursor) TypeName() string {
return "DataCursor"
}
// 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 = slices.Contains([]string{kern.CleanName, kern.ResetName, kern.CurrentName, kern.IndexName}, name)
if !exists {
f, ok := dc.ds[name]
exists = ok && kern.IsFunctor(f)
}
return
}
func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
if name == kern.IndexName {
value = int64(dc.Index())
} else if name == kern.CleanName {
err = dc.Clean()
} else if name == kern.ResetName {
err = dc.Reset()
} else if functor, ok := dc.ds[name]; ok && kern.IsFunctor(functor) {
ctx := kern.CloneContext(dc.ctx)
value, err = functor.InvokeNamed(ctx, name, args)
kern.ExportObjects(dc.ctx, ctx)
} else {
err = kern.ErrNoOperation(name)
}
return
}
// func (dc *dataCursor) Reset() (err error) {
// if dc.resetFunc != nil {
// if dc.resource != nil {
// ctx := cloneContext(dc.ctx)
// actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
// _, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
// exportObjects(dc.ctx, ctx)
// dc.index = -1
// dc.count = 0
// dc.initState = true
// dc.current = nil
// dc.lastErr = nil
// } else {
// err = errInvalidDataSource()
// }
// } else {
// err = errNoOperation(ResetName)
// }
// return
// }
func (dc *dataCursor) Reset() (err error) {
if dc.resetFunc != nil {
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(dc.resetFunc, []any{dc.resource})
_, err = dc.resetFunc.InvokeNamed(ctx, kern.ResetName, actualParams)
kern.ExportObjects(dc.ctx, ctx)
}
dc.index = -1
dc.count = 0
dc.initState = true
dc.current = nil
dc.lastErr = nil
return
}
func (dc *dataCursor) Clean() (err error) {
if dc.cleanFunc != nil {
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(dc.cleanFunc, []any{dc.resource})
_, err = dc.cleanFunc.InvokeNamed(ctx, kern.CleanName, actualParams)
kern.ExportObjects(dc.ctx, ctx)
}
dc.lastErr = io.EOF
return
}
// func (dc *dataCursor) Clean() (err error) {
// if dc.cleanFunc != nil {
// if dc.resource != nil {
// ctx := cloneContext(dc.ctx)
// actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
// _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
// exportObjects(dc.ctx, ctx)
// } else {
// err = errInvalidDataSource()
// }
// } else {
// err = errNoOperation(CleanName)
// }
// return
// }
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
dc.init()
if dc.current != nil {
item = dc.current
} else {
err = io.EOF
}
return
}
func (dc *dataCursor) checkFilter(filter kern.Functor, item any) (accepted bool, err error) {
var v any
var ok bool
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(filter, []any{item, dc.index})
if v, err = filter.InvokeNamed(ctx, kern.FilterName, actualParams); err == nil && v != nil {
if accepted, ok = v.(bool); !ok {
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
}
return
}
func (dc *dataCursor) mapItem(mapper kern.Functor, item any) (mappedItem any, err error) {
ctx := kern.CloneContext(dc.ctx)
actualParams := kern.BindActualParams(mapper, []any{item, dc.index})
mappedItem, err = mapper.InvokeNamed(ctx, kern.MapName, actualParams)
return
}
func (dc *dataCursor) init() {
if dc.initState {
dc.initState = false
dc.Next()
}
}
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
if dc.initState {
dc.init()
} else if err = dc.lastErr; err != nil {
return
}
current = dc.current
filter := dc.ds[kern.FilterName]
mapper := dc.ds[kern.MapName]
var item any
for item == nil && dc.lastErr == nil {
ctx := kern.CloneContext(dc.ctx)
dc.index++
actualParams := kern.BindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, kern.NextName, actualParams); dc.lastErr == nil {
if item == nil {
dc.lastErr = io.EOF
} else {
accepted := true
if filter != nil {
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
item = nil
}
}
if accepted {
dc.count++
}
if item != nil && mapper != nil {
item, dc.lastErr = dc.mapItem(mapper, item)
}
}
}
kern.ExportObjects(dc.ctx, ctx)
}
dc.current = item
if dc.lastErr != nil {
dc.index--
dc.Clean()
}
return
}
// func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
// if dc.initState {
// dc.init()
// } else if err = dc.lastErr; err != nil {
// return
// }
// current = dc.current
// if dc.resource != nil {
// filter := dc.ds[FilterName]
// mapper := dc.ds[MapName]
// var item any
// for item == nil && dc.lastErr == nil {
// ctx := cloneContext(dc.ctx)
// dc.index++
// actualParams := bindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
// if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
// if item == nil {
// dc.lastErr = io.EOF
// } else {
// accepted := true
// if filter != nil {
// if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
// item = nil
// }
// }
// if accepted {
// dc.count++
// }
// if item != nil && mapper != nil {
// item, dc.lastErr = dc.mapItem(mapper, item)
// }
// }
// }
// exportObjects(dc.ctx, ctx)
// }
// dc.current = item
// if dc.lastErr != nil {
// dc.index--
// dc.Clean()
// }
// } else {
// dc.lastErr = errInvalidDataSource()
// }
// return
// }
func (dc *dataCursor) Index() int64 {
return dc.index - 1
}
func (dc *dataCursor) Count() int64 {
return dc.count
}
+225
View File
@@ -0,0 +1,225 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
)
type dictIterMode int
const (
dictIterModeKeys dictIterMode = iota
dictIterModeValues
dictIterModeItems
)
type DictIterator struct {
a *kern.DictType
count int64
index int64
keys []any
iterMode dictIterMode
}
type sortType int
const (
sortTypeNone sortType = iota
sortTypeAsc
sortTypeDesc
sortTypeDefault = sortTypeAsc
)
func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
it.keys = make([]any, 0, len(m))
if sort == sortTypeNone {
for keyAny := range m {
it.keys = append(it.keys, keyAny)
}
} else {
scalarMap := make(map[string]any, len(m))
scalerKeys := make([]string, 0, len(m))
for keyAny := range m {
keyStr := fmt.Sprint(keyAny)
scalarMap[keyStr] = keyAny
scalerKeys = append(scalerKeys, keyStr)
}
switch sort {
case sortTypeAsc:
slices.Sort(scalerKeys)
case sortTypeDesc:
slices.Sort(scalerKeys)
slices.Reverse(scalerKeys)
}
for _, keyStr := range scalerKeys {
it.keys = append(it.keys, scalarMap[keyStr])
}
}
}
func NewDictIterator(dict *kern.DictType, args []any) (it *DictIterator, err error) {
var sortType = sortTypeNone
var s string
var argAny any
dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
if len(args) > 0 {
argAny = args[0]
} else {
argAny = "default"
}
if s, err = kern.ToGoString(argAny, "sort type"); err == nil {
switch strings.ToLower(s) {
case "a", "asc":
sortType = sortTypeAsc
case "d", "desc":
sortType = sortTypeDesc
case "n", "none", "nosort", "no-sort":
sortType = sortTypeNone
case "", "default":
sortType = sortTypeDefault
default:
err = fmt.Errorf("invalid sort type %q", s)
}
if err == nil {
if len(args) > 1 {
argAny = args[1]
} else {
argAny = "default"
}
if s, err = kern.ToGoString(argAny, "iteration mode"); err == nil {
switch strings.ToLower(s) {
case "k", "key", "keys":
dictIt.iterMode = dictIterModeKeys
case "v", "value", "values":
dictIt.iterMode = dictIterModeValues
case "i", "item", "items":
dictIt.iterMode = dictIterModeItems
case "", "default":
dictIt.iterMode = dictIterModeKeys
default:
err = fmt.Errorf("invalid iteration mode %q", s)
}
}
}
}
if err == nil {
dictIt.makeKeys(*dict, sortType)
it = dictIt
}
return
}
func NewMapIterator(m map[any]any) (it *DictIterator) {
it = &DictIterator{a: (*kern.DictType)(&m), count: 0, index: -1, keys: nil}
it.makeKeys(m, sortTypeNone)
return
}
func (it *DictIterator) String() string {
var l = int64(0)
if it.a != nil {
l = int64(len(*it.a))
}
return fmt.Sprintf("$({#%d})", l)
}
func (it *DictIterator) TypeName() string {
return "DictIterator"
}
func (it *DictIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName, kern.KeyName, kern.ValueName}, name)
return yes
}
func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
case kern.KeyName:
if it.index >= 0 && it.index < int64(len(it.keys)) {
v = it.keys[it.index]
} else {
err = io.EOF
}
case kern.ValueName:
if it.index >= 0 && it.index < int64(len(it.keys)) {
a := *(it.a)
v = a[it.keys[it.index]]
} else {
err = io.EOF
}
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *DictIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < int64(len(it.keys)) {
switch it.iterMode {
case dictIterModeKeys:
item = it.keys[it.index]
case dictIterModeValues:
a := *(it.a)
item = a[it.keys[it.index]]
case dictIterModeItems:
a := *(it.a)
pair := []any{it.keys[it.index], a[it.keys[it.index]]}
item = kern.NewList(pair)
}
} else {
err = io.EOF
}
return
}
func (it *DictIterator) Next() (item any, err error) {
it.index++
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *DictIterator) Index() int64 {
return it.index
}
func (it *DictIterator) Count() int64 {
return it.count
}
func (it *DictIterator) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *DictIterator) Clean() error {
return nil
}
+1739
View File
File diff suppressed because it is too large Load Diff
+3617
View File
File diff suppressed because one or more lines are too long
+94
View File
@@ -0,0 +1,94 @@
<mxfile host="app.diagrams.net" modified="2024-04-03T13:08:06.112Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0" etag="aiwjz2Mg7uovMJtfXvHm" version="24.2.1" type="device">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="989" dy="570" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="q_7kt0Q1lEUOPZf56y0L-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="WIyWlLk6GJQsqaUBKTNV-7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="scanner" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="180" y="39" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.13;entryY=0.475;entryDx=0;entryDy=0;endArrow=classic;endFill=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-7" target="q_7kt0Q1lEUOPZf56y0L-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-7" value="" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="320" y="14" width="286" height="90" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-0" target="WIyWlLk6GJQsqaUBKTNV-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-0" value="input stream" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="30" y="39" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-2" target="q_7kt0Q1lEUOPZf56y0L-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-2" value="&lt;div&gt;syntax&lt;/div&gt;&lt;div&gt;analyzer&lt;br&gt;&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="356" y="39" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-3" value="&lt;div&gt;static semanthic&lt;/div&gt;&lt;div&gt;analyzer&lt;br&gt;&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="477" y="39" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-4" value="Parser" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="433" y="10" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-7" target="q_7kt0Q1lEUOPZf56y0L-2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="356" y="80" as="sourcePoint" />
<mxPoint x="406" y="30" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-3" target="WIyWlLk6GJQsqaUBKTNV-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="356" y="80" as="sourcePoint" />
<mxPoint x="406" y="30" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-13" target="q_7kt0Q1lEUOPZf56y0L-29">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-13" value="" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="785" y="12" width="255" height="95" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-15" target="q_7kt0Q1lEUOPZf56y0L-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-15" value="AST" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="634" y="40" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-18" value="&lt;div&gt;semanthic&lt;/div&gt;&lt;div&gt;analyzer&lt;br&gt;&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="814" y="40.5" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-19" value="Eval" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="946" y="40.5" width="60" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" target="q_7kt0Q1lEUOPZf56y0L-18">
<mxGeometry relative="1" as="geometry">
<mxPoint x="781" y="60" as="sourcePoint" />
<mxPoint x="790" y="70" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-18" target="q_7kt0Q1lEUOPZf56y0L-19">
<mxGeometry relative="1" as="geometry">
<mxPoint x="750" y="70" as="sourcePoint" />
<mxPoint x="790" y="70" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-19" target="q_7kt0Q1lEUOPZf56y0L-13">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1020" y="62" as="sourcePoint" />
<mxPoint x="1070" y="12" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-29" value="&lt;div&gt;Computed&lt;/div&gt;&lt;div&gt;Value&lt;/div&gt;" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="1060" y="39.5" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-31" value="Excecuter" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="872.5" y="9" width="80" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+126
View File
@@ -0,0 +1,126 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// global-context.go
package expr
import (
"path/filepath"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
)
//var globalCtx *SimpleStore
func ImportInContext(ctx kern.ExprContext, name string) (exists bool) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
var mod *builtinModule
if mod, exists = builtinModuleRegister[name]; exists {
mod.importFunc(globalCtx)
mod.imported = true
}
}
return
}
func ImportInContextByGlobPattern(ctx kern.ExprContext, pattern string) (count int, err error) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
var matched bool
for name, mod := range builtinModuleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
mod.importFunc(globalCtx)
mod.imported = true
}
} else {
break
}
}
}
return
}
func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name)
globalCtx.SetVar(name, newValue)
}
return currentValue
}
func GlobalCtrlGet(ctx kern.ExprContext, name string) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name)
}
return currentValue
}
func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, true)
return currentStatus
}
func CtrlDisable(ctx kern.ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, false)
return currentStatus
}
// func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
// var v any
// var exists bool
// if !strings.HasPrefix(name, "_") {
// name = "_" + name
// }
// if v, exists = ctx.GetVar(name); !exists {
// v, exists = globalCtx.GetVar(name)
// }
// if exists {
// if b, ok := v.(bool); ok {
// status = b
// }
// }
// return
// }
func getControlString(ctx kern.ExprContext, name string) (s string, exists bool) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
var v any
if v, exists = globalCtx.GetVar(name); exists {
s, exists = v.(string)
}
}
return
}
func InitGlobal() (ctx kern.ExprContext) {
ctx = NewSimpleStoreWithoutGlobalContext()
kern.InitDefaultVars(ctx)
ImportBuiltinsFuncs(ctx)
return
}
+7
View File
@@ -0,0 +1,7 @@
module git.portale-stac.it/go-pkg/expr
go 1.22.0
toolchain go1.23.3
require golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
+4
View File
@@ -0,0 +1,4 @@
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+124
View File
@@ -0,0 +1,124 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
//go:build graph
// 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()
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers.go
package expr
import (
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/util"
)
func EvalString(ctx kern.ExprContext, source string) (result any, err error) {
var tree *scan.Ast
r := strings.NewReader(source)
scanner := scan.NewScanner(r, scan.DefaultTranslations())
parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx)
}
return
}
type Arg struct {
Name string
Value any
}
func EvalStringA(source string, args ...Arg) (result any, err error) {
return EvalStringV(source, args)
}
func EvalStringV(source string, args []Arg) (result any, err error) {
ctx := NewSimpleStoreWithoutGlobalContext()
for _, arg := range args {
if util.IsFunc(arg.Value) {
if f, ok := arg.Value.(kern.FuncTemplate); ok {
functor := kern.NewGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlagDef(kern.ParamValue, kern.PfDefault|kern.PfRepeat, 0),
})
} else {
err = fmt.Errorf("invalid function specification: %q", arg.Name)
}
} else if integer, ok := kern.AnyInteger(arg.Value); ok {
ctx.SetVar(arg.Name, integer)
} else if float, ok := kern.AnyFloat(arg.Value); ok {
ctx.SetVar(arg.Name, float)
} else if _, ok := arg.Value.(string); ok {
ctx.SetVar(arg.Name, arg.Value)
} else if _, ok := arg.Value.(bool); ok {
ctx.SetVar(arg.Name, arg.Value)
} else {
err = fmt.Errorf("unsupported type %T specified for item %q", arg.Value, arg.Name)
}
}
if err == nil {
result, err = EvalString(ctx, source)
}
return
}
func EvalStream(ctx kern.ExprContext, r io.Reader) (result any, err error) {
var tree *scan.Ast
scanner := scan.NewScanner(r, scan.DefaultTranslations())
parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx)
}
return
}
func EvalFile(ctx kern.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
}
+127
View File
@@ -0,0 +1,127 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// import-utils.go
package expr
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
)
const (
ENV_EXPR_SOURCE_PATH = "EXPR_PATH"
ENV_EXPR_PLUGIN_PATH = "EXPR_PLUGIN_PATH"
)
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(kern.IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, kern.TypeName(paramValue))
}
return
}
// func addSourceEnvImportDirs(varName string, dirList []string) []string {
// return addEnvImportDirs(ENV_EXPR_SOURCE_PATH, dirList)
// }
// func addPluginEnvImportDirs(varName string, dirList []string) []string {
// return addEnvImportDirs(ENV_EXPR_PLUGIN_PATH, dirList)
// }
func addEnvImportDirs(envVarName string, dirList []string) []string {
if dirSpec, exists := os.LookupEnv(envVarName); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addSearchDirs(ctx kern.ExprContext, endingPath string, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, kern.ControlSearchPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
if len(endingPath) > 0 {
for _, d := range dirs {
dirList = append(dirList, path.Join(d, endingPath))
}
} else {
dirList = append(dirList, dirs...)
}
}
}
return dirList
}
func buildSearchDirList(ctx kern.ExprContext, endingPath, envVarName string) (dirList []string) {
dirList = addEnvImportDirs(envVarName, dirList)
dirList = addSearchDirs(ctx, endingPath, dirList)
return
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
suffix := SHAREDLIBRARY_EXTENSION
if strings.HasSuffix(filename, ".debug") {
suffix += ".debug"
}
for _, dir := range dirList {
if dir, err = util.ExpandPath(dir); err != nil {
continue
}
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
subdir := strings.TrimSuffix(filename, suffix)
if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if filename, err = util.ExpandPath(filename); err != nil {
return
}
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("file %q not found", filename)
}
return
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// int-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type IntIterator struct {
count int64
index int64
start int64
stop int64
step int64
}
func NewIntIterator(args []any) (it *IntIterator, err error) {
var argc int = 0
if args != nil {
argc = len(args)
}
it = &IntIterator{count: 0, index: -1, start: 0, stop: 0, step: 1}
if argc >= 1 {
if it.stop, err = kern.ToGoInt64(args[0], "start index"); err != nil {
return
}
if argc >= 2 {
it.start = it.stop
if it.stop, err = kern.ToGoInt64(args[1], "stop index"); err != nil {
return
}
if argc >= 3 {
if it.step, err = kern.ToGoInt64(args[2], "step"); err != nil {
return
}
} else if it.start > it.stop {
it.step = -1
}
}
}
if it.step == 0 {
err = fmt.Errorf("step cannot be zero")
return
}
if it.start < it.stop && it.step < 0 {
err = fmt.Errorf("step cannot be negative when start < stop")
return
}
if it.start > it.stop && it.step > 0 {
err = fmt.Errorf("step cannot be positive when start > stop")
return
}
it.Reset()
return
}
func (it *IntIterator) String() string {
return fmt.Sprintf("$(%d..%d..%d)", it.start, it.stop, it.step)
}
func (it *IntIterator) TypeName() string {
return "IntIterator"
}
func (it *IntIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *IntIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *IntIterator) Current() (item any, err error) {
if it.start <= it.stop {
if it.index >= it.start && it.index < it.stop {
item = it.index
} else {
err = io.EOF
}
} else {
if it.index > it.stop && it.index <= it.start {
item = it.index
} else {
err = io.EOF
}
}
return
}
func (it *IntIterator) Next() (item any, err error) {
it.index += it.step
if item, err = it.Current(); err != io.EOF {
it.count++
}
return
}
func (it *IntIterator) Index() int64 {
return it.index
}
func (it *IntIterator) Count() int64 {
return it.count
}
func (it *IntIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *IntIterator) Clean() error {
return nil
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// it-range.go
package expr
import "io"
type RangeIterator struct {
start int64
end int64
step int64
current int64
index int
}
func NewRangeIterator(start, end, step int64) *RangeIterator {
if step == 0 {
panic("Range step must be not zero")
}
if step < 0 && start < end {
panic("When the range's step is less than zero, start must be greater than end")
}
if step > 0 && start > end {
panic("When the range's step is greater than zero, start must be less than end")
}
return &RangeIterator{start: start, end: end, step: step, current: start, index: 0}
}
func (it *RangeIterator) Reset() {
it.index = 0
it.current = it.start
}
func (it *RangeIterator) Next() (item any, err error) {
if it.step > 0 {
if it.current < it.end {
item = it.current
it.current += it.step
it.index++
} else {
err = io.EOF
}
} else {
if it.current > it.end {
item = it.current
it.current += it.step
it.index++
} else {
err = io.EOF
}
}
return
}
func (it *RangeIterator) Index() int {
return it.index - 1
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-factory.go
package expr
import (
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
func NewIterator(ctx kern.ExprContext, value any, ops []*scan.Term) (it kern.Iterator, err error) {
if value == nil {
return NewArrayIterator([]any{}), nil
}
switch v := value.(type) {
case *kern.ListType:
it = NewListIterator(v, nil)
case *kern.DictType:
it, err = NewDictIterator(v, nil)
case []any:
it = NewArrayIterator(v)
case kern.Iterator:
// var exprs []*scan.Term
it, err = NewIterIter(v, ctx, ops)
default:
it = NewArrayIterator([]any{value})
}
return
}
func HasIterStandardOperations(name string) bool {
return slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName}, name)
}
func HasIterOperations(name string, ops ...string) bool {
return slices.Contains([]string{
kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName,
}, name) ||
slices.Contains(ops, name)
}
+127
View File
@@ -0,0 +1,127 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-iter.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
type IterIter struct {
it kern.Iterator
count int64
index int64
ctx kern.ExprContext
exprList []*scan.Term
current any
}
func NewIterIter(it kern.Iterator, ctx kern.ExprContext, exprs []*scan.Term) (iter kern.Iterator, err error) {
iter = &IterIter{it: it, count: 0, index: -1, ctx: ctx, exprList: exprs, current: nil}
return
}
func (it *IterIter) String() string {
return fmt.Sprintf("$(%s)", it.it)
}
func (it *IterIter) TypeName() string {
return "IterIter"
}
func (it *IterIter) HasOperation(name string) bool {
return HasIterStandardOperations(name)
}
func (it *IterIter) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *IterIter) Current() (item any, err error) {
if it.current != nil {
item = it.current
} else if len(it.exprList) > 0 {
// Evaluate the expression list and use the result as the current item
var exprValue any
for _, expr := range it.exprList {
if exprValue, err = expr.Compute(it.ctx); err != nil {
break
}
it.ctx.UnsafeSetVar(kern.ControlLastResult, exprValue)
}
if err == nil {
item = exprValue
}
} else {
var exists bool
if it.current, exists = it.ctx.GetVar("_"); !exists {
err = fmt.Errorf("current item not available")
} else {
item = it.current
}
}
return
}
func (it *IterIter) Next() (item any, err error) {
var src any
it.current = nil
ctx := it.ctx
for src, err = it.it.Next(); src == nil && err == nil; src, err = it.it.Next() {
}
if err == nil {
if src == nil {
err = io.EOF
} else {
ctx.UnsafeSetVar("_", src)
ctx.UnsafeSetVar("__", it.it.Index())
ctx.UnsafeSetVar("_#", it.it.Count())
item, err = it.Current()
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
}
}
return
}
func (it *IterIter) Index() int64 {
return it.index
}
func (it *IterIter) Count() int64 {
return it.count
}
func (it *IterIter) Reset() error {
it.index = -1
it.count = 0
return nil
}
func (it *IterIter) Clean() error {
return nil
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// bind-go-function.go
package kern
// ---- Linking with Go functions
type GolangFunctor struct {
BaseFunctor
f FuncTemplate
}
func NewGolangFunctor(f FuncTemplate) *GolangFunctor {
return &GolangFunctor{f: f}
}
func (functor *GolangFunctor) TypeName() string {
return "GoFunctor"
}
func (functor *GolangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return functor.f(ctx, name, args)
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// string.go
package kern
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func ToBool(v any) (b bool, ok bool) {
ok = true
switch x := v.(type) {
case string:
b = len(x) > 0
case float64:
b = x != 0.0
case int64:
b = x != 0
case bool:
b = x
default:
ok = false
}
return
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-errors.go
package kern
import (
"fmt"
"strings"
)
func ErrMissingParams(funcName string, missing []string) (err error) {
return fmt.Errorf("%s(): missing params -- %s", funcName, strings.Join(missing, ", "))
}
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 {
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
} else {
err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
}
return
}
func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount)
return
}
// --- General errors
func ErrCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
}
func ErrExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s(): expected %s, got %s (%#v)", funcName, kind, TypeName(value), value)
}
func ErrFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s(): division by zero", funcName)
}
// func ErrDivisionByZero() error {
// return fmt.Errorf("division by zero")
// }
// --- Parameter errors
// 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 %s (%#v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
}
func undefArticle(s string) (article string) {
if len(s) > 0 && strings.Contains("aeiou", s[0:1]) {
article = "an"
} else {
article = "a"
}
return
}
func prependUndefArticle(s string) (result string) {
return undefArticle(s) + " " + s
}
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
var artWantType, artGotType string
gotType := TypeName(paramValue)
artGotType = prependUndefArticle(gotType)
artWantType = prependUndefArticle(paramType)
return fmt.Errorf("%s(): the %q parameter must be %s, got %s (%v)", funcName, paramName, artWantType, artGotType, paramValue)
}
func ErrUnknownParam(funcName, paramName string) error {
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
}
func ErrUnknownVar(funcName, varName string) error {
return fmt.Errorf("%s(): unknown variable %q", funcName, varName)
}
// --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm Term) error {
return leftTerm.Errorf("left operand of %q must be a variable", opTerm.Source())
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-params.go
package kern
const (
ParamArgs = "args"
ParamCount = "count"
ParamItem = "item"
ParamIndex = "index"
ParamParts = "parts"
ParamSeparator = "separator"
ParamSource = "source"
ParamSuffix = "suffix"
ParamPrefix = "prefix"
ParamStart = "start"
ParamEnd = "end"
ParamValue = "value"
ParamName = "name"
ParamEllipsis = "..."
ParamFilepath = "filepath"
ParamDirpath = "dirpath"
ParamHandle = "handle"
ParamResource = "resource"
ParamIterator = "iterator"
)
// to be moved in its own source file
const (
ConstLastIndex = 0xFFFF_FFFF
)
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-type-names.go
package kern
const (
TypeAny = "any"
TypeNil = "nil"
TypeBoolean = "boolean"
TypeFloat = "float"
TypeFraction = "fraction"
TypeFileHandle = "file-handle"
TypeInt = "integer"
TypeItem = "item"
TypeIterator = "iterator"
TypeNumber = "number"
TypePair = "pair"
TypeString = "string"
TypeDict = "dict"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
)
+37
View File
@@ -0,0 +1,37 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
package kern
import "reflect"
func Equal(value1, value2 any) (equal bool) {
if value1 == nil && value2 == nil {
equal = true
} else if value1 == nil || value2 == nil {
equal = false
} else if IsBool(value1) && IsBool(value2) {
equal = value1.(bool) == value2.(bool)
} else if IsList(value1) && IsList(value2) {
ls1 := value1.(*ListType)
ls2 := value2.(*ListType)
equal = ls1.Equals(*ls2)
} else if IsDict(value1) && IsDict(value2) {
d1 := value1.(*DictType)
d2 := value2.(*DictType)
equal = d1.Equals(*d2)
} else if IsInteger(value1) && IsInteger(value2) {
equal = value1.(int64) == value2.(int64)
} else if IsString(value1) && IsString(value2) {
equal = value1.(string) == value2.(string)
} else if IsFloat(value1) && IsFloat(value2) {
equal = value1.(float64) == value2.(float64)
} else if IsNumOrFract(value1) && IsNumOrFract(value2) {
if eq, err := CmpAnyFract(value1, value2); err == nil {
equal = eq == 0
}
} else if !reflect.DeepEqual(value1, value2) {
equal = false
}
return
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context-helpers.go
package kern
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.UnsafeSetVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
}
func ExportObjects(destCtx, sourceCtx ExprContext) {
exportAll := CtrlIsEnabled(sourceCtx, ControlExportAll)
// 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] == '@') && !(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)
}
}
}
func exportObjectsToParent(sourceCtx ExprContext) {
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
ExportObjects(parentCtx, sourceCtx)
}
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// control.go
package kern
import "strings"
// Preset control variables
const (
ControlPreset = "_preset"
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
ControlStdout = "_stdout"
)
// Other control variables
const (
ControlExportAll = "_export_all"
)
// Initial values
const (
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
)
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
var v any
var exists bool
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists = ctx.GetVar(name); !exists {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
v, exists = globalCtx.GetVar(name)
}
}
if exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
current, _ = ctx.GetVar(name)
ctx.UnsafeSetVar(name, value)
return
}
func InitDefaultVars(ctx ExprContext) {
if _, exists := ctx.GetVar(ControlPreset); exists {
return
}
ctx.UnsafeSetVar(ControlPreset, true)
ctx.UnsafeSetVar(ControlBoolShortcut, true)
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
}
+193
View File
@@ -0,0 +1,193 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-type.go
package kern
import (
"fmt"
"reflect"
"strings"
)
type DictType map[any]any
func IsDict(v any) (ok bool) {
_, ok = v.(*DictType)
return ok
}
func MakeDict() (dict *DictType) {
d := make(DictType)
dict = &d
return
}
func NewDict(dictAny map[any]any) (dict *DictType) {
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func newDict(dictAny map[any]Term) (dict *DictType) {
// TODO Change with a call to NewDict()
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
//sb.WriteString(strings.Repeat(" ", indent))
sb.WriteByte('{')
if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
sb.WriteByte('\n')
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(nest)
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(innerOpt))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
sb.WriteString("}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
flags := GetFormatFlags(opt)
if flags&MultiLine != 0 {
dict.toMultiLine(&sb, opt)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", key))
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else if t, ok := value.(Term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return "dict"
}
func (dict *DictType) HasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) SetItem(key any, value any) {
(*dict)[key] = value
}
func (dict *DictType) GetItem(key any) (value any, exists bool) {
value, exists = (*dict)[key]
return
}
func (dict *DictType) Clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
}
return
}
func (dict *DictType) Merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
func (dict *DictType) Equals(dict2 DictType) (answer bool) {
if dict2 != nil && len(*dict) == len(dict2) {
answer = true
for key, value1 := range *dict {
if value2, exists := dict2.GetItem(key); exists {
if !Equal(value1, value2) {
answer = false
break
}
} else {
answer = false
break
}
}
}
return
}
////////////////
type DictFormat interface {
ToDict() *DictType
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr-context.go
package kern
// ----Expression Context
type ExprContext interface {
Clone() ExprContext
SetParent(ctx ExprContext)
GetParent() (ctx ExprContext)
GetGlobal() (ctx ExprContext)
GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any)
UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
VarCount() int
DeleteVar(varName string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
FuncCount() int
DeleteFunc(funcName string)
GetLocalFuncInfo(name string) (info ExprFunc, exists bool)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args map[string]any) (result any, err error)
RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error)
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr-function.go
package kern
// ---- Functor interface
type Functor interface {
Typer
InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
GetParams() []ExprFuncParam
GetDefinitionContext() ExprContext
}
// ---- Function Param Info
type ExprFuncParam interface {
Name() string
Type() string
IsDefault() bool
IsOptional() bool
IsRepeat() bool
DefaultValue() any
}
// ---- Function Info
type ExprFunc interface {
Formatter
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
Params() []ExprFuncParam
ParamSpec(paramName string) ExprFuncParam
ReturnType() string
PrepareCall(name string, actualParams map[string]any) (err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext)
}
func IsFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
+12
View File
@@ -0,0 +1,12 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr.go
package kern
// ----Expression interface
type Expr interface {
Typer
Eval(ctx ExprContext) (result any, err error)
String() string
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// float.go
package kern
func IsFloat(v any) (ok bool) {
_, ok = v.(float64)
return ok
}
func AnyFloat(v any) (float float64, ok bool) {
ok = true
switch floatval := v.(type) {
case float32:
float = float64(floatval)
case float64:
float = floatval
default:
ok = false
}
return
}
+79
View File
@@ -0,0 +1,79 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// formatter.go
package kern
import "fmt"
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
const (
TTY FmtOpt = 1 << iota
MultiLine
Truncate
Base2
Base8
Base10
Base16
)
const (
TruncateEllipsis = "(...)"
MinTruncateSize = 10
TruncateSize = MinTruncateSize + 15
)
func TruncateString(s string) (trunc string) {
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
return
}
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
return FmtOpt(indent<<16) | flags
}
func GetFormatFlags(opt FmtOpt) FmtOpt {
return opt & 0xFFFF
}
func GetFormatIndent(opt FmtOpt) int {
return int(opt >> 16)
}
type Formatter interface {
ToString(options FmtOpt) string
}
func GetFormatted(v any, opt FmtOpt) (text string) {
if v == nil {
text = "(nil)"
} else if s, ok := v.(string); ok {
text = s
} else if formatter, ok := v.(Formatter); ok {
text = formatter.ToString(opt)
} else {
text = fmt.Sprintf("%v", v)
}
return
}
type Typer interface {
TypeName() string
}
func TypeName(v any) (name string) {
if v == nil {
name = "nil"
} else if typer, ok := v.(Typer); ok {
name = typer.TypeName()
} else if IsInteger(v) {
name = "integer"
} else if IsFloat(v) {
name = "float"
} else {
name = fmt.Sprintf("%T", v)
}
return
}
+384
View File
@@ -0,0 +1,384 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// fraction-type.go
package kern
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
type FractionType struct {
num, den int64
}
func NewFraction(num, den int64) *FractionType {
num, den = simplifyIntegers(num, den)
return &FractionType{num, den}
}
func Float64ToFraction(f float64) (fract *FractionType, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign = "-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
return MakeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
func MakeGeneratingFraction(s string) (f *FractionType, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign = int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
// if strings.HasSuffix(s, "()") {
// s = s[0 : len(s)-2]
// }
s = strings.TrimSuffix(s, "()")
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = NewFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = NewFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = NewFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *FractionType) N() int64 {
return f.num
}
func (f *FractionType) D() int64 {
return f.den
}
func (f *FractionType) ToFloat() float64 {
return float64(f.num) / float64(f.den)
}
func (f *FractionType) String() string {
return f.ToString(0)
}
func (f *FractionType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine == 0 {
sb.WriteString(fmt.Sprintf("%d:%d", f.num, f.den))
} else {
var sign, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
sign = "-"
} else {
num = strconv.FormatInt(f.num, 10)
}
den := strconv.FormatInt(f.den, 10)
size := max(len(num), len(den))
if opt&TTY != 0 {
sNum := fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, sign+num))
sb.WriteString(sNum)
} else {
if len(sign) > 0 {
sb.WriteString(" ")
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
sb.WriteByte('\n')
if len(sign) > 0 {
sb.WriteString(sign)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(sign) > 0 {
sb.WriteString(" ")
}
}
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
sb.WriteString(sDen)
}
return sb.String()
}
func (f *FractionType) TypeName() string {
return "fraction"
}
// -------- fraction utility functions
// greatest common divider
func Gcd(a, b int64) (g int64) {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
a, b = b, a
}
r := a % b
for r > 0 {
a, b = b, r
r = a % b
}
g = b
return
}
// lower common multiple
func lcm(a, b int64) (l int64) {
g := Gcd(a, b)
l = a * b / g
return
}
// Sum two fractions
func SumFract(f1, f2 *FractionType) (sum *FractionType) {
m := lcm(f1.den, f2.den)
sum = NewFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
// Multiply two fractions
func MulFract(f1, f2 *FractionType) (prod *FractionType) {
prod = NewFraction(f1.num*f2.num, f1.den*f2.den)
return
}
func anyToFract(v any) (f *FractionType, err error) {
var ok bool
if f, ok = v.(*FractionType); !ok {
if n, ok := v.(int64); ok {
f = intToFraction(n)
} else if dec, ok := v.(float64); ok {
f, err = Float64ToFraction(dec)
} else {
err = ErrExpectedGot("fract", TypeFraction, v)
}
}
return
}
func anyPairToFract(v1, v2 any) (f1, f2 *FractionType, err error) {
if f1, err = anyToFract(v1); err != nil {
return
}
if f2, err = anyToFract(v2); err != nil {
return
}
return
}
func SumAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f := SumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
// err if af1 or af2 is not convertible to fraction
func CmpAnyFract(af1, af2 any) (result int, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
result = cmpFract(f1, f2)
return
}
// Returns
//
// <0 if af1 < af2
// =0 if af1 == af2
// >0 if af1 > af2
func cmpFract(f1, f2 *FractionType) (result int) {
if f1 != nil && f2 != nil {
f2.num = -f2.num
f := SumFract(f1, f2)
if f.num < 0 {
result = -1
} else if f.num > 0 {
result = 1
} else {
result = 0
}
}
return
}
func SubAnyFract(af1, af2 any) (sum any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
f2.num = -f2.num
f := SumFract(f1, f2)
if f.num == 0 {
sum = 0
} else {
sum = simplifyFraction(f)
}
return
}
func MulAnyFract(af1, af2 any) (prod any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f1.num == 0 || f2.num == 0 {
prod = 0
} else {
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
prod = simplifyFraction(f)
}
return
}
func DivAnyFract(af1, af2 any) (quot any, err error) {
var f1, f2 *FractionType
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
return
}
if f2.num == 0 {
err = errors.New("division by zero")
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
} else {
f := &FractionType{f1.num * f2.den, f1.den * f2.num}
quot = simplifyFraction(f)
}
return
}
func simplifyFraction(f *FractionType) (v any) {
f.num, f.den = simplifyIntegers(f.num, f.den)
if f.den == 1 {
v = f.num
} else {
v = &FractionType{f.num, f.den}
}
return v
}
func simplifyIntegers(num, den int64) (a, b int64) {
if num == 0 {
return 0, 1
}
if den == 0 {
panic("fraction with denominator == 0")
}
if den < 0 {
den = -den
num = -num
}
g := Gcd(num, den)
a = num / g
b = den / g
return
}
func intToFraction(n int64) *FractionType {
return &FractionType{n, 1}
}
func IsFraction(v any) (ok bool) {
_, ok = v.(*FractionType)
return ok
}
// func IsFract(v any) (ok bool) {
// _, ok = v.(*FractionType)
// return ok
// }
func IsRational(v any) (ok bool) {
if _, ok = v.(*FractionType); !ok {
_, ok = v.(int64)
}
return ok
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-info.go
package kern
import (
"fmt"
"strings"
)
// --- Functions
// FuncInfo implements expr.ExprFunc
type FuncInfo struct {
name string
minArgs int
maxArgs int
functor Functor
formalParams []ExprFuncParam
returnType string
}
func NewFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *FuncInfo, err error) {
var minArgs = 0
var maxArgs = 0
for _, p := range params {
if maxArgs == -1 {
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
}
if p.IsDefault() || p.IsOptional() {
maxArgs++
} else if maxArgs == minArgs {
minArgs++
maxArgs++
} else {
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
}
if p.IsRepeat() {
minArgs--
maxArgs = -1
}
}
info = &FuncInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
}
functor.SetFunc(info)
return info, nil
}
func (info *FuncInfo) Params() []ExprFuncParam {
return info.formalParams
}
func (info *FuncInfo) ReturnType() string {
return info.returnType
}
func (info *FuncInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
if len(info.Name()) == 0 {
sb.WriteString("func")
} else {
sb.WriteString(info.Name())
}
sb.WriteByte('(')
if info.formalParams != nil {
for i, p := range info.formalParams {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.Name())
if p.IsDefault() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
}
}
}
}
if info.maxArgs < 0 {
sb.WriteString(" ...")
}
sb.WriteString("):")
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(TypeAny)
}
sb.WriteString("{}")
return sb.String()
}
func (info *FuncInfo) Name() string {
return info.name
}
func (info *FuncInfo) MinArgs() int {
return info.minArgs
}
func (info *FuncInfo) MaxArgs() int {
return info.maxArgs
}
func (info *FuncInfo) Functor() Functor {
return info.functor
}
func (info *FuncInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
ctx = defCtx.Clone()
ctx.SetParent(defCtx)
} else {
ctx = parentCtx.Clone()
ctx.SetParent(parentCtx)
}
return
}
func (info *FuncInfo) ParamSpec(paramName string) ExprFuncParam {
for _, spec := range info.formalParams {
if spec.Name() == paramName {
return spec
}
}
return nil
}
func (info *FuncInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
passedCount := len(actualParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
return
}
if passedCount < len(info.formalParams) {
for _, p := range info.formalParams {
if _, exists := actualParams[p.Name()]; !exists {
if !p.IsDefault() {
break
}
if p.IsRepeat() {
varArgs := make([]any, 1)
varArgs[0] = p.DefaultValue()
actualParams[p.Name()] = varArgs
} else {
actualParams[p.Name()] = p.DefaultValue()
}
}
}
}
if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
}
return
}
+273
View File
@@ -0,0 +1,273 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package kern
import (
"fmt"
"strconv"
)
// ---- Function templates
type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (result any, err error)
type DeepFuncTemplate func(a, b any) (eq bool, err error)
// ---- Common functor definition
type BaseFunctor struct {
info ExprFunc
}
func (functor *BaseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil {
s = functor.info.ToString(opt)
} else {
s = "func(){}"
}
return s
}
func (functor *BaseFunctor) GetParams() (params []ExprFuncParam) {
if functor.info != nil {
return functor.info.Params()
} else {
return []ExprFuncParam{}
}
}
func (functor *BaseFunctor) SetFunc(info ExprFunc) {
functor.info = info
}
func (functor *BaseFunctor) GetFunc() ExprFunc {
return functor.info
}
func (functor *BaseFunctor) GetDefinitionContext() ExprContext {
return nil
}
// ---- Function Parameters
type FuncParamFlags uint16
const (
PfDefault FuncParamFlags = 1 << iota
PfOptional
PfRepeat
)
type funcParamInfo struct {
name string
flags FuncParamFlags
defaultValue any
}
func NewFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name}
}
func NewFuncParamFlag(name string, flags FuncParamFlags) ExprFuncParam {
return &funcParamInfo{name: name, flags: flags}
}
func NewFuncParamFlagDef(name string, flags FuncParamFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return TypeAny
}
func (param *funcParamInfo) IsDefault() bool {
return (param.flags & PfDefault) != 0
}
func (param *funcParamInfo) IsOptional() bool {
return (param.flags & PfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & PfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
return param.defaultValue
}
func initActualParams(ctx ExprContext, info ExprFunc, callTerm Term) (actualParams map[string]any, err error) {
var varArgs []any
var varName string
namedParamsStarted := false
formalParams := info.Params()
actualParams = make(map[string]any, len(formalParams))
if callTerm == nil {
return
}
childCount := callTerm.GetChildCount()
for i := range childCount {
tree := callTerm.GetChild(i)
// for i, tree := range callTerm.Children() {
var paramValue any
paramCtx := ctx.Clone()
if paramValue, err = tree.Compute(paramCtx); err != nil {
break
}
if paramName, namedParam := GetAssignVarName(tree); namedParam {
if info.ParamSpec(paramName) == nil {
err = fmt.Errorf("%s(): unknown param %q", info.Name(), paramName)
break
}
actualParams[paramName] = paramValue
namedParamsStarted = true
} else if !namedParamsStarted {
if varArgs != nil {
varArgs = append(varArgs, paramValue)
} else if i < len(formalParams) {
spec := formalParams[i]
if spec.IsRepeat() {
varArgs = make([]any, 0, childCount-i)
varArgs = append(varArgs, paramValue)
varName = spec.Name()
} else {
actualParams[spec.Name()] = paramValue
}
} else {
err = ErrTooManyParams(info.Name(), len(formalParams), childCount)
break
}
} else {
err = fmt.Errorf("%s(): positional param nr %d not allowed after named params", info.Name(), i+1)
break
}
}
if err == nil {
if varArgs != nil {
actualParams[varName] = varArgs
}
}
return
}
// func (info *funcInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
// passedCount := len(actualParams)
// if info.MinArgs() > passedCount {
// err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
// return
// }
// if passedCount < len(info.formalParams) {
// for _, p := range info.formalParams {
// if _, exists := actualParams[p.Name()]; !exists {
// if !p.IsDefault() {
// break
// }
// if p.IsRepeat() {
// varArgs := make([]any, 1)
// varArgs[0] = p.DefaultValue()
// actualParams[p.Name()] = varArgs
// } else {
// actualParams[p.Name()] = p.DefaultValue()
// }
// }
// }
// }
// if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
// err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
// }
// return
// }
// ----- Call a function ---
// func getAssignVarName(t *term) (name string, ok bool) {
// if ok = t.symbol() == SymEqual; ok {
// name = t.children[0].source()
// }
// return
// }
func GetAssignVarName(t Term) (name string, ok bool) {
if ok = t.IsAssign(); ok {
name = t.GetChildSource(0)
}
return
}
func CallFunctionByTerm(parentCtx ExprContext, name string, callTerm Term) (result any, err error) {
var actualParams map[string]any
if info, exists := parentCtx.GetFuncInfo(name); exists {
if actualParams, err = initActualParams(parentCtx, info, callTerm); err == nil {
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
functor := info.Functor()
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunctionByArgs(parentCtx ExprContext, name string, args []any) (result any, err error) {
var actualParams map[string]any
if info, exists := parentCtx.GetFuncInfo(name); exists {
functor := info.Functor()
actualParams = BindActualParams(functor, args)
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunctionByParams(parentCtx ExprContext, name string, actualParams map[string]any) (result any, err error) {
//var actualParams map[string]any
if info, exists := parentCtx.GetFuncInfo(name); exists {
functor := info.Functor()
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func GetParam(args map[string]any, paramName string, paramNum int) (value any, exists bool) {
if value, exists = args[paramName]; !exists {
if paramNum > 0 && paramNum <= len(args) {
value, exists = args["arg"+strconv.Itoa(paramNum)]
}
}
return
}
func BindActualParams(functor Functor, args []any) (actualParams map[string]any) {
formalParams := functor.GetParams()
actualParams = make(map[string]any, len(args))
for i, arg := range args {
if i < len(formalParams) {
actualParams[formalParams[i].Name()] = arg
} else {
actualParams["arg"+strconv.Itoa(i+1)] = arg
}
}
return
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator.go
package kern
import (
// "errors"
"fmt"
)
// Operator names
const (
InitName = "init"
CleanName = "clean"
ResetName = "reset"
NextName = "next"
CurrentName = "current"
IndexName = "index"
CountName = "count"
FilterName = "filter"
MapName = "map"
KeyName = "key"
ValueName = "value"
)
type Iterator interface {
Typer
fmt.Stringer
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int64
Count() int64
HasOperation(name string) bool
CallOperation(name string, args map[string]any) (value any, err error)
}
type ExtIterator interface {
Iterator
Reset() error
Clean() error
}
func ErrNoOperation(name string) error {
return fmt.Errorf("no %s() function defined in the data-source", name)
}
func IsIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
+202
View File
@@ -0,0 +1,202 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-type.go
package kern
import (
"fmt"
"reflect"
"strings"
)
type ListType []any
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
}
func NewListA(listAny ...any) (list *ListType) {
if listAny == nil {
listAny = []any{}
}
return NewList(listAny)
}
func NewList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
copy(ls, listAny)
list = &ls
}
return
}
func MakeList(length, capacity int) (list *ListType) {
if capacity < length {
capacity = length
}
ls := make(ListType, length, capacity)
list = &ls
return
}
func ListFromStrings(stringList []string) (list *ListType) {
list = MakeList(len(stringList), 0)
for i, s := range stringList {
(*list)[i] = s
}
return
}
func (dict *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteByte('[')
if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
}
for i, item := range []any(*dict) {
if i > 0 {
if flags&MultiLine != 0 {
sb.WriteString(",\n")
sb.WriteString(nest)
} else {
sb.WriteString(", ")
}
}
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(innerOpt))
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
}
sb.WriteByte(']')
s = sb.String()
if flags&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
return
}
func (dict *ListType) String() string {
return dict.ToString(0)
}
func (dict *ListType) TypeName() string {
return "list"
}
func (dict *ListType) Contains(t *ListType) (answer bool) {
if len(*dict) >= len(*t) {
answer = true
for _, item := range *t {
if answer = dict.IndexDeepSameCmp(item) >= 0; !answer {
break
}
}
}
return
}
func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls1) == len(ls2) {
answer = true
for index, i1 := range *ls1 {
// if !reflect.DeepEqual(i1, ls2[index]) {
// answer = false
// break
// }
if !Equal(i1, ls2[index]) {
answer = false
break
}
}
}
return
}
func (dict *ListType) IndexDeepSameCmp(target any) (index int) {
var eq bool
var err error
index = -1
for i, item := range *dict {
if eq, err = deepSame(item, target, SameContent); err != nil {
break
} else if eq {
index = i
break
}
}
return
}
func SameContent(a, b any) (same bool, err error) {
la, _ := a.(*ListType)
lb, _ := b.(*ListType)
if len(*la) == len(*lb) {
same = true
for _, item := range *la {
if pos := lb.IndexDeepSameCmp(item); pos < 0 {
same = false
break
}
}
}
return
}
func deepSame(a, b any, deepCmp DeepFuncTemplate) (eq bool, err error) {
if IsNumOrFract(a) && IsNumOrFract(b) {
if IsNumber(a) && IsNumber(b) {
if IsInteger(a) && IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
eq = li == ri
} else {
eq = NumAsFloat(a) == NumAsFloat(b)
}
} else {
var cmp int
if cmp, err = CmpAnyFract(a, b); err == nil {
eq = cmp == 0
}
}
} else if deepCmp != nil && IsList(a) && IsList(b) {
eq, err = deepCmp(a, b)
} else {
eq = reflect.DeepEqual(a, b)
}
return
}
func (dict *ListType) SetItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*dict)) {
(*dict)[index] = value
} else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*dict)-1)
}
return
}
func (dict *ListType) AppendItem(value any) {
*dict = append(*dict, value)
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// number.go
package kern
import (
"fmt"
)
func IsInteger(v any) (ok bool) {
_, ok = v.(int64)
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)
}
func NumAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
if fract, ok := v.(*FractionType); ok {
f = fract.ToFloat()
} else {
i, _ := v.(int64)
f = float64(i)
}
}
return
}
func AnyInteger(v any) (i int64, ok bool) {
ok = true
switch intval := v.(type) {
case int:
i = int64(intval)
case uint8:
i = int64(intval)
case uint16:
i = int64(intval)
case uint64:
i = int64(intval)
case uint32:
i = int64(intval)
case int8:
i = int64(intval)
case int16:
i = int64(intval)
case int32:
i = int64(intval)
case int64:
i = intval
default:
ok = false
}
return
}
func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}
func ToGoInt64(value any, description string) (i int64, err error) {
if valueInt64, ok := value.(int64); ok {
i = valueInt64
} else if valueInt, ok := value.(int); ok {
i = int64(valueInt)
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// string.go
package kern
import (
"fmt"
)
func IsString(v any) (ok bool) {
_, ok = v.(string)
return ok
}
func ToGoString(value any, description string) (s string, err error) {
if s, ok := value.(string); ok {
return s, nil
} else {
err = fmt.Errorf("%s expected string, got %s (%v)", description, TypeName(value), value)
}
return
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term.go
package kern
import (
"fmt"
)
type Term interface {
fmt.Stringer
// Children() []Term
Source() string
GetChildCount() (count int)
GetChild(index int) Term
GetChildSource(index int) string
Compute(ctx ExprContext) (result any, err error)
IsAssign() bool
Errorf(template string, args ...any) (err error)
}
+9
View File
@@ -0,0 +1,9 @@
//go:build darwin
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-darwin.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dylib"
+9
View File
@@ -0,0 +1,9 @@
//go:build linux
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-linux.go
package expr
const SHAREDLIBRARY_EXTENSION = ".so"
+9
View File
@@ -0,0 +1,9 @@
//go:build windows
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// lib-ext-windows.go
package expr
const SHAREDLIBRARY_EXTENSION = ".dll"
+149
View File
@@ -0,0 +1,149 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// list-iterator.go
package expr
import (
"fmt"
"io"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
)
type ListIterator struct {
a *kern.ListType
count int64
index int64
start int64
stop int64
step int64
}
func NewListIterator(list *kern.ListType, args []any) (it *ListIterator) {
var argc int = 0
listLen := int64(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 := kern.ToGoInt64(args[0], "start index"); err == nil {
if i < 0 {
i = listLen + i
}
it.start = i
}
if argc >= 2 {
if i, err := kern.ToGoInt64(args[1], "stop index"); err == nil {
if i < 0 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := kern.ToGoInt64(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: (*kern.ListType)(&array), count: 0, index: -1, start: 0, stop: int64(len(array)) - 1, step: 1}
return
}
func (it *ListIterator) String() string {
var l = int64(0)
if it.a != nil {
l = int64(len(*it.a))
}
return fmt.Sprintf("$([#%d])", l)
}
func (it *ListIterator) TypeName() string {
return "ListIterator"
}
func (it *ListIterator) HasOperation(name string) bool {
//yes := name == expr.NextName || name == expr.ResetName || name == expr.IndexName || name == expr.CountName || name == expr.CurrentName
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *ListIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name {
case kern.NextName:
v, err = it.Next()
case kern.ResetName:
err = it.Reset()
case kern.CleanName:
err = it.Clean()
case kern.IndexName:
v = int64(it.Index())
case kern.CurrentName:
v, err = it.Current()
case kern.CountName:
v = it.count
default:
err = kern.ErrNoOperation(name)
}
return
}
func (it *ListIterator) Current() (item any, err error) {
a := *(it.a)
if it.start <= it.stop {
if it.stop < int64(len(a)) && it.index >= it.start && it.index <= it.stop {
item = a[it.index]
} else {
err = io.EOF
}
} else {
if it.start < int64(len(a)) && it.index >= it.stop && it.index <= it.start {
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() int64 {
return it.index
}
func (it *ListIterator) Count() int64 {
return it.count
}
func (it *ListIterator) Reset() error {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *ListIterator) Clean() error {
return nil
}
-72
View File
@@ -1,72 +0,0 @@
// operand-const.go
package expr
// -------- bool const term
func newBoolTerm(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,
class: classConst,
kind: kindInteger,
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,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- eval func
func evalConst(ctx exprContext, self *term) (v any, err error) {
v = self.tk.Value
return
}
// init
func init() {
registerTermConstructor(SymString, newStringTerm)
registerTermConstructor(SymInteger, newIntegerTerm)
registerTermConstructor(SymFloat, newFloatTerm)
registerTermConstructor(SymBool, newBoolTerm)
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-dict.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- dict term
func newDictTerm(args map[any]*scan.Term) *scan.Term {
return &scan.Term{
Tk: *scan.NewValueToken(0, 0, scan.SymDict, "{}", args),
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalDict,
}
}
// -------- dict func
func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
dict, _ := opTerm.Value().(map[any]*scan.Term)
items := make(kern.DictType, 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
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-expr.go
package expr
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- expr term
func newExprTerm(root *scan.Term) *scan.Term {
tk := scan.NewValueToken(root.Tk.Row(), root.Tk.Col(), scan.SymExpression, root.Source(), root)
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalExpr,
}
}
// -------- eval expr
func evalExpr(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if ast, ok := opTerm.Value().(*scan.Term); ok {
v, err = ast.Compute(ctx)
} else {
err = fmt.Errorf("expression expected, got %T", opTerm.Value())
}
return
}
+77 -22
View File
@@ -1,33 +1,88 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-func.go
package expr
// -------- function term
func newFuncTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
parent: nil,
children: args,
position: posLeaf,
priority: priValue,
evalFunc: evalFunc,
import (
"errors"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- function call term
func newFuncCallTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
var pos scan.TermPosition = scan.PosLeaf
if len(args) > 0 {
pos = scan.PosMultifix
}
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: args,
Position: pos,
Priority: scan.PriValue,
EvalFunc: evalFuncCall,
}
}
// -------- eval func
func evalFunc(ctx exprContext, self *term) (v any, err error) {
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children))
for i, tree := range self.children {
var param any
if param, err = tree.compute(ctx); err != nil {
break
// -------- eval func call
// func _evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
// name, _ := opTerm.Tk.Value.(string)
// params := make([]any, len(opTerm.Children), len(opTerm.Children)+5)
// for i, tree := range opTerm.Children {
// var param any
// if param, err = tree.Compute(ctx); err != nil {
// break
// }
// params[i] = param
// }
// if err == nil {
// v, err = CallFunction(ctx, name, params)
// }
// return
// }
func evalFuncCall(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
name, _ := opTerm.Tk.Value.(string)
v, err = kern.CallFunctionByTerm(ctx, name, opTerm)
return
}
// -------- function definition term
func newFuncDefTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
return &scan.Term{
Tk: *tk, // value is the expression body
Parent: nil,
Children: args, // function params
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalFuncDef,
}
params[i] = param
}
// -------- eval func def
func evalFuncDef(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
bodySpec := opTerm.Value()
if ast, ok := bodySpec.(*scan.Ast); ok {
paramList := make([]kern.ExprFuncParam, 0, len(opTerm.Children))
for _, param := range opTerm.Children {
var defValue any
flags := kern.FuncParamFlags(0)
if len(param.Children) > 0 {
flags |= kern.PfDefault
if defValue, err = param.Children[0].Compute(ctx); err != nil {
return
}
if err == nil {
v, err = ctx.Call(name, params)
}
info := kern.NewFuncParamFlagDef(param.Source(), flags, defValue)
paramList = append(paramList, info)
}
v = newExprFunctor(ast, paramList, ctx)
} else {
err = errors.New("invalid function definition: the body specification must be an expression")
}
return
}
+203
View File
@@ -0,0 +1,203 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-iterator.go
package expr
import (
"slices"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- iterator term
func newIteratorTerm(tk *scan.Token, args []*scan.Term) *scan.Term {
tk.Sym = scan.SymIterator
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: args,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalIterator,
}
}
// -------- eval iterator
func evalTermArray(ctx kern.ExprContext, terms []*scan.Term) (values []any, err error) {
values = make([]any, len(terms))
for i, t := range terms {
var value any
if value, err = t.Compute(ctx); err == nil {
values[i] = value
} else {
break
}
}
return
}
func evalFirstChild(ctx kern.ExprContext, iteratorTerm *scan.Term) (value any, err error) {
if len(iteratorTerm.Children) < 1 || iteratorTerm.Children[0] == nil {
err = iteratorTerm.Errorf("missing the data-source parameter")
return
}
value, err = iteratorTerm.Children[0].Compute(ctx)
return
}
func getDataSourceDict(iteratorTerm *scan.Term, firstChildValue any) (ds map[string]kern.Functor, err error) {
if dictAny, ok := firstChildValue.(*kern.DictType); ok {
requiredFields := []string{kern.NextName}
fieldsMask := 0b1
foundFields := 0
ds = make(map[string]kern.Functor)
for keyAny, item := range *dictAny {
if key, ok := keyAny.(string); ok {
if functor, ok := item.(kern.Functor); 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 = iteratorTerm.Children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
}
}
return
}
func evalIterator(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var firstChildValue any
var ds map[string]kern.Functor
if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
return
}
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil && ds == nil {
return
}
err = nil
if ds != nil {
if len(ds) > 0 {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[kern.InitName]; exists && initFunc != nil {
var args []any
var resource any
if len(opTerm.Children) > 1 {
if args, err = evalTermArray(ctx, opTerm.Children[1:]); err != nil {
return
}
} else {
args = []any{}
}
actualParams := kern.BindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, kern.InitName, actualParams); err != nil {
return
}
kern.ExportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
} else {
dc = NewDataCursor(dcCtx, ds, nil)
}
v = dc
} else {
if dictIt, ok := firstChildValue.(*kern.DictType); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v, err = NewDictIterator(dictIt, args)
}
} else {
err = opTerm.Children[0].Errorf("the data-source must be a dictionary")
}
}
} else if list, ok := firstChildValue.(*kern.ListType); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v = NewListIterator(list, args)
}
} else if intVal, ok := firstChildValue.(int64); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, intVal); err == nil {
v, err = NewIntIterator(args)
}
} else if it, ok := firstChildValue.(kern.Iterator); ok {
v, err = NewIterIter(it, ctx, opTerm.Children[1:])
} else {
var siblings []any
if siblings, err = evalSiblings(ctx, opTerm.Children, firstChildValue); err == nil {
v = NewArrayIterator(siblings)
}
}
return
}
// func evalIterIter(ctx kern.ExprContext, firstChildValue any, siblings []any) (v any, err error) {
// var op kern.Functor
// var args map[string]any
// if it, ok := firstChildValue.(kern.Iterator); ok {
// if len(siblings) > 1 {
// if op, ok = siblings[1].(kern.Functor); ok {
// args = make(map[string]any, len(siblings)-2)
// for i, arg := range siblings[2:] {
// switch a := arg.(type) {
// case *kern.DictType:
// for keyAny, item := range *a {
// if key, ok := keyAny.(string); ok {
// args[key] = item
// }
// }
// default:
// args["arg"+strconv.Itoa(i+1)] = arg
// }
// }
// } else if op == nil {
// return nil, fmt.Errorf("the first sibling parameter must be a functor to be used as operation for the iterator")
// }
// }
// v, err = NewIterIter(it, ctx, op, args)
// }
// return
// }
func evalSiblings(ctx kern.ExprContext, terms []*scan.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
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-list.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- list term
func newListTermA(args ...*scan.Term) *scan.Term {
return newListTerm(0, 0, args)
}
func newListTerm(row, col int, args []*scan.Term) *scan.Term {
return &scan.Term{
Tk: *scan.NewValueToken(row, col, scan.SymList, "[]", args),
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalList,
}
}
// -------- list func
func evalList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
list, _ := opTerm.Value().([]*scan.Term)
items := make(kern.ListType, len(list))
for i, tree := range list {
var param any
if param, err = tree.Compute(ctx); err != nil {
break
}
items[i] = param
}
if err == nil {
v = &items
}
return
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-literal.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- literal term
func newLiteralTerm(tk *scan.Token) *scan.Term {
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalLiteral,
}
}
// -------- eval func
func evalLiteral(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v = opTerm.Tk.Value
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymString, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymInteger, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymFloat, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymFraction, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymBool, newLiteralTerm)
scan.RegisterTermConstructor(scan.SymKwNil, newLiteralTerm)
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-selector-case.go
package expr
import (
"fmt"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- selector case term
type selectorCase struct {
filterList *scan.Term
caseExpr kern.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 *scan.Term, caseExpr kern.Expr) *scan.Term {
tk := scan.NewValueToken(row, col, scan.SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalSelectorCase,
}
}
// -------- eval selector case
func evalSelectorCase(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var ok bool
if v, ok = opTerm.Value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", opTerm.Value())
}
return
}
+28 -15
View File
@@ -1,32 +1,45 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-var.go
package expr
import "fmt"
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- variable term
func newVarTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalVar,
func newVarTerm(tk *scan.Token) *scan.Term {
t := &scan.Term{
Tk: *tk,
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalVar,
}
t.Tk.Sym = scan.SymVariable
return t
}
// -------- eval func
func evalVar(ctx exprContext, self *term) (v any, err error) {
func evalVar(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var exists bool
if v, exists = ctx.GetValue(self.tk.source); !exists {
err = fmt.Errorf("undefined variable %q", self.tk.source)
name := opTerm.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
}
// init
func init() {
registerTermConstructor(SymIdentifier, newVarTerm)
scan.RegisterTermConstructor(scan.SymIdentifier, newVarTerm)
}
+226
View File
@@ -0,0 +1,226 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserightChilded.
// operator-assign.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/util"
)
//-------- assign term
func newAssignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriAssign,
EvalFunc: evalAssign,
}
}
func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term, value any) (err error) {
var collectionValue, keyListValue, keyValue any
var keyList *kern.ListType
var ok bool
if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
return
}
if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
return
} else if keyList, ok = keyListValue.(*kern.ListType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
return
}
if keyValue = (*keyList)[0]; keyValue == nil {
err = keyListTerm.Errorf("index/key is nil")
return
}
switch collection := collectionValue.(type) {
case *kern.ListType:
if index, ok := keyValue.(int64); ok {
err = collection.SetItem(index, value)
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
}
case *kern.DictType:
collection.SetItem(keyValue, value)
default:
err = collectionTerm.Errorf("collection expected")
}
return
}
func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any) (err error) {
if leftTerm.Symbol() == scan.SymIndex {
err = assignCollectionItem(ctx, leftTerm.Children[0], leftTerm.Children[1], v)
} else {
ctx.UnsafeSetVar(leftTerm.Source(), v)
}
return
}
func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if err = opTerm.CheckOperands(); err != nil {
return
}
leftTerm := opTerm.Children[0]
leftSym := leftTerm.Symbol()
if leftSym != scan.SymVariable && leftSym != scan.SymIndex {
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
return
}
rightChild := opTerm.Children[1]
if v, err = rightChild.Compute(ctx); err == nil {
if functor, ok := v.(kern.Functor); ok {
if leftSym == scan.SymVariable {
if info := functor.GetFunc(); info != nil {
ctx.RegisterFunc(leftTerm.Source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok {
paramSpecs := util.ForAll(funcDef.params, func(p kern.ExprFuncParam) kern.ExprFuncParam { return p })
ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, paramSpecs)
} else {
err = opTerm.Errorf("unknown function %s()", rightChild.Source())
}
} else {
err = assignValue(ctx, leftTerm, v)
}
} else {
err = assignValue(ctx, leftTerm, v)
}
}
if err != nil {
v = nil
}
return
}
//-------- assign term
func newOpAssignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriAssign,
EvalFunc: evalOpAssign,
}
}
func getCollectionItemValue(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term) (value any, err error) {
var collectionValue, keyListValue, keyValue any
var keyList *kern.ListType
var ok bool
if collectionValue, err = collectionTerm.Compute(ctx); err != nil {
return
}
if keyListValue, err = keyListTerm.Compute(ctx); err != nil {
return
} else if keyList, ok = keyListValue.(*kern.ListType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, kern.TypeName(keyListValue))
return
}
if keyValue = (*keyList)[0]; keyValue == nil {
err = keyListTerm.Errorf("index/key is nil")
return
}
switch collection := collectionValue.(type) {
case *kern.ListType:
if index, ok := keyValue.(int64); ok {
value = (*collection)[index]
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, kern.TypeName(keyValue))
}
case *kern.DictType:
value = (*collection)[keyValue]
default:
err = collectionTerm.Errorf("collection expected")
}
return
}
func getAssignValue(ctx kern.ExprContext, leftTerm *scan.Term) (value any, err error) {
if leftTerm.Symbol() == scan.SymIndex {
value, err = getCollectionItemValue(ctx, leftTerm.Children[0], leftTerm.Children[1])
} else {
value, _ = ctx.GetVar(leftTerm.Source())
}
return
}
func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var rightValue, leftValue any
if err = opTerm.CheckOperands(); err != nil {
return
}
leftTerm := opTerm.Children[0]
leftSym := leftTerm.Symbol()
if leftSym != scan.SymVariable && leftSym != scan.SymIndex {
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
return
}
rightChild := opTerm.Children[1]
if rightValue, err = rightChild.Compute(ctx); err == nil {
if leftValue, err = getAssignValue(ctx, leftTerm); err == nil {
switch opTerm.Symbol() {
case scan.SymPlusEqual:
v, err = sumValues(opTerm, leftValue, rightValue)
case scan.SymMinusEqual:
v, err = diffValues(opTerm, leftValue, rightValue)
case scan.SymStarEqual:
v, err = mulValues(opTerm, leftValue, rightValue)
case scan.SymSlashEqual:
v, err = divValues(opTerm, leftValue, rightValue)
case scan.SymPercEqual:
v, err = remainderValues(opTerm, leftValue, rightValue)
case scan.SymAmpersandEqual:
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
case scan.SymVertBarEqual:
v, err = bitwiseOr(opTerm, leftValue, rightValue)
case scan.SymCaretEqual:
v, err = bitwiseXor(opTerm, leftValue, rightValue)
case scan.SymDoubleLessEqual:
v, err = bitLeftShift(opTerm, leftValue, rightValue)
case scan.SymDoubleGreaterEqual:
v, err = bitRightShift(opTerm, leftValue, rightValue)
default:
err = opTerm.Errorf("unsupported assign operator %q", opTerm.Source())
}
if err == nil {
err = assignValue(ctx, leftTerm, v)
}
}
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymEqual, newAssignTerm)
scan.RegisterTermConstructor(scan.SymPlusEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymMinusEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymStarEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymSlashEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymPercEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymDoubleLessEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymDoubleGreaterEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymAmpersandEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymVertBarEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymCaretEqual, newOpAssignTerm)
}
+159
View File
@@ -0,0 +1,159 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-bitwise.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- Bitwise NOT term
func newBitwiseNotTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriBitwiseNot,
EvalFunc: evalBitwiseNot,
}
}
func evalBitwiseNot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var value any
if value, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if kern.IsInteger(value) {
i, _ := value.(int64)
v = ^i
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(value)
}
return
}
//-------- Bitwise AND term
func newBitwiseAndTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriBitwiseAnd,
EvalFunc: evalBitwiseAnd,
}
}
func bitwiseAnd(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt & rightInt
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseAnd(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
return
}
//-------- Bitwise OR term
func newBitwiseOrTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriBitwiseOr,
EvalFunc: evalBitwiseOr,
}
}
func bitwiseOr(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt | rightInt
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseOr(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = bitwiseOr(opTerm, leftValue, rightValue)
return
}
//-------- Bitwise XOR term
func newBitwiseXorTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriBitwiseXor,
EvalFunc: evalBitwiseXor,
}
}
func bitwiseXor(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
var leftInt, rightInt int64
var lok, rok bool
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt ^ rightInt
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalBitwiseXor(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = bitwiseXor(opTerm, leftValue, rightValue)
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymTilde, newBitwiseNotTerm)
scan.RegisterTermConstructor(scan.SymAmpersand, newBitwiseAndTerm)
scan.RegisterTermConstructor(scan.SymVertBar, newBitwiseOrTerm)
scan.RegisterTermConstructor(scan.SymCaret, newBitwiseXorTerm)
}
+119 -44
View File
@@ -1,106 +1,181 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-bool.go
package expr
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- NOT term
func newNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priNot,
evalFunc: evalNot,
func newNotTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriNot,
EvalFunc: evalNot,
}
}
func evalNot(ctx exprContext, self *term) (v any, err error) {
func evalNot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
if rightValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if b, ok := toBool(rightValue); ok {
if b, ok := kern.ToBool(rightValue); ok {
v = !b
} else {
err = self.errIncompatibleType(rightValue)
err = opTerm.ErrIncompatiblePrefixPostfixType(rightValue)
}
return
}
//-------- AND term
func newAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAnd,
evalFunc: evalAnd,
func newAndTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriAnd,
EvalFunc: evalAnd,
}
}
func evalAnd(ctx exprContext, self *term) (v any, err error) {
func evalAnd(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
if kern.CtrlIsEnabled(ctx, kern.ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self)
} else {
v, err = evalAndWithoutShortcut(ctx, self)
}
return
}
func evalAndWithoutShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = self.EvalInfix(ctx); err != nil {
return
}
leftBool, lok = toBool(leftValue)
rightBool, rok = toBool(rightValue)
leftBool, lok = kern.ToBool(leftValue)
rightBool, rok = kern.ToBool(rightValue)
if lok && rok {
v = leftBool && rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = self.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalAndWithShortcut(ctx kern.ExprContext, self *scan.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
}
if leftBool, lok := kern.ToBool(leftValue); !lok {
// err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", expr.TypeName(leftValue))
// return
err = self.ErrIncompatibleType(leftValue, "left")
} else if !leftBool {
v = false
} else if rightValue, err = self.Children[1].Compute(ctx); err == nil {
if rightBool, rok := kern.ToBool(rightValue); rok {
v = rightBool
} else {
err = self.ErrIncompatibleTypes(leftValue, rightValue)
}
}
return
}
//-------- OR term
func newOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priOr,
evalFunc: evalOr,
func newOrTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriOr,
EvalFunc: evalOr,
}
}
func evalOr(ctx exprContext, self *term) (v any, err error) {
func evalOr(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
if kern.CtrlIsEnabled(ctx, kern.ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self)
} else {
v, err = evalOrWithoutShortcut(ctx, self)
}
return
}
func evalOrWithoutShortcut(ctx kern.ExprContext, self *scan.Term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = self.EvalInfix(ctx); err != nil {
return
}
leftBool, lok = toBool(leftValue)
rightBool, rok = toBool(rightValue)
leftBool, lok = kern.ToBool(leftValue)
rightBool, rok = kern.ToBool(rightValue)
if lok && rok {
v = leftBool || rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = self.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalOrWithShortcut(ctx kern.ExprContext, self *scan.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
}
if leftBool, lok := kern.ToBool(leftValue); !lok {
err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", kern.TypeName(leftValue))
return
} else if leftBool {
v = true
} else if rightValue, err = self.Children[1].Compute(ctx); err == nil {
if rightBool, rok := kern.ToBool(rightValue); rok {
v = rightBool
} else {
err = self.ErrIncompatibleTypes(leftValue, rightValue)
}
}
return
}
// init
func init() {
registerTermConstructor(SymNot, newNotTerm)
registerTermConstructor(SymAnd, newAndTerm)
registerTermConstructor(SymOr, newOrTerm)
scan.RegisterTermConstructor(scan.SymNot, newNotTerm)
scan.RegisterTermConstructor(scan.SymAnd, newAndTerm)
scan.RegisterTermConstructor(scan.SymOr, newOrTerm)
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-builtin.go
package expr
import (
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- builtin term
func newBuiltinTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalBuiltin,
}
}
func evalBuiltin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
count := 0
if kern.IsString(childValue) {
module, _ := childValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module)
} else {
var moduleSpec any
var it kern.Iterator
if it, err = NewIterator(ctx, childValue, nil); err != nil {
return
}
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) {
count++
} else {
err = opTerm.Errorf("unknown builtin module %q", module)
break
}
} else {
err = opTerm.Errorf("expected string at item nr %d, got %s", it.Index()+1, kern.TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
}
if err == nil {
v = int64(count)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwBuiltin, newBuiltinTerm)
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-but.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- but term
func newButTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriBut,
EvalFunc: evalBut,
}
}
func evalBut(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
_, v, err = opTerm.EvalInfix(ctx)
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwBut, newButTerm)
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-context-value.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- context term
func newContextTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriIncDec,
EvalFunc: evalContextValue,
}
}
func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
var sourceCtx kern.ExprContext
if len(opTerm.Children) == 0 {
sourceCtx = ctx
} else if opTerm.Children[0].Symbol() == scan.SymVariable && opTerm.Children[0].Source() == "global" {
sourceCtx = ctx.GetGlobal()
} else if childValue, err = opTerm.EvalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
}
if sourceCtx != nil {
if formatter, ok := sourceCtx.(kern.DictFormat); ok {
v = formatter.ToDict()
} else if formatter, ok := sourceCtx.(kern.Formatter); ok {
v = formatter.ToString(0)
} else {
// keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
keys := sourceCtx.EnumVars(nil)
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 = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDoubleDollar, newContextTerm)
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-ctrl.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- export all term
func newExportAllTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalExportAll,
}
}
func evalExportAll(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
CtrlEnable(ctx, kern.ControlExportAll)
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDoubleAt, newExportAllTerm)
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-default.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- default term
func newDefaultTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriDefault,
EvalFunc: evalDefault,
}
}
func evalDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var rightValue any
if err = opTerm.CheckOperands(); err != nil {
return
}
leftTerm := opTerm.Children[0]
if leftTerm.Tk.Sym != scan.SymVariable {
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.Source()); exists {
v = leftValue
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
v = rightValue
}
return
}
//-------- alternate term
func newAlternateTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriDefault,
EvalFunc: evalAlternate,
}
}
func evalAlternate(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var rightValue any
if err = opTerm.CheckOperands(); err != nil {
return
}
leftTerm := opTerm.Children[0]
if leftTerm.Tk.Sym != scan.SymVariable {
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.Source()); exists && leftValue != nil {
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
v = rightValue
}
} else {
v = leftValue
}
return
}
//-------- default assign term
func newDefaultAssignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriDefault,
EvalFunc: evalAssignDefault,
}
}
func evalAssignDefault(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var rightValue any
if err = opTerm.CheckOperands(); err != nil {
return
}
leftTerm := opTerm.Children[0]
if leftTerm.Tk.Sym != scan.SymVariable {
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
err = kern.ErrLeftOperandMustBeVariable(leftTerm, opTerm)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.Source()); exists {
v = leftValue
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if functor, ok := rightValue.(kern.Functor); ok {
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
ctx.RegisterFunc(leftTerm.Source(), functor, kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamValue, kern.PfDefault|kern.PfRepeat),
})
} else {
v = rightValue
ctx.UnsafeSetVar(leftTerm.Source(), rightValue)
}
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDoubleQuestion, newDefaultTerm)
scan.RegisterTermConstructor(scan.SymQuestionEqual, newDefaultAssignTerm)
scan.RegisterTermConstructor(scan.SymQuestionExclam, newAlternateTerm)
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-digest.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- digest term
func newDigestTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalDigest,
}
}
func evalDigest(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var it kern.Iterator
var item, lastValue any
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of DIGEST must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
lastValue = nil
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if rightValue == nil {
break
} else {
lastValue = rightValue
}
}
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
v = lastValue
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwDigest, newDigestTerm)
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-dot.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- dot term
func newDotTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriDot,
EvalFunc: evalDot,
}
}
func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
indexTerm := opTerm.Children[1]
switch unboxedValue := leftValue.(type) {
case kern.ExtIterator:
if indexTerm.Tk.Sym == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ {
opName := indexTerm.Source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, map[string]any{})
} else {
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false
}
} else {
err = indexTerm.Tk.ErrorExpectedGot("identifier")
}
case *kern.DictType:
var ok bool
s := opTerm.Children[1].Tk.Sym
if s == scan.SymVariable || s == scan.SymString {
src := opTerm.Children[1].Source()
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1]
}
if v, ok = unboxedValue.GetItem(src); !ok {
err = opTerm.Errorf("key %q not found", src)
}
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if v, ok = unboxedValue.GetItem(rightValue); !ok {
err = opTerm.Errorf("key %q not found", rightValue)
}
}
default:
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
err = opTerm.Errorf("incompatible types: %s and %s", kern.TypeName(leftValue), kern.TypeName(rightValue))
}
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDot, newDotTerm)
}
+21 -15
View File
@@ -1,30 +1,36 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-fact.go
package expr
import "fmt"
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- fact term
func newFactTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindInteger,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priFact,
evalFunc: evalFact,
func newFactTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPostfix,
Priority: scan.PriFact,
EvalFunc: evalFact,
}
}
func evalFact(ctx exprContext, self *term) (v any, err error) {
func evalFact(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if isInteger(leftValue) {
if kern.IsInteger(leftValue) {
if i, _ := leftValue.(int64); i >= 0 {
f := int64(1)
for k := int64(1); k <= i; k++ {
@@ -35,12 +41,12 @@ func evalFact(ctx exprContext, self *term) (v any, err error) {
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
}
} else {
err = self.errIncompatibleType(leftValue)
err = opTerm.Errorf("incompatible type for factorial: %s", kern.TypeName(leftValue))
}
return
}
// init
func init() {
registerTermConstructor(SymExclamation, newFactTerm)
scan.RegisterTermConstructor(scan.SymExclamation, newFactTerm)
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-filter.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- map term
func newFilterTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalFilter,
}
}
func evalFilter(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var it kern.Iterator
var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if it, ok = leftValue.(kern.Iterator); !ok {
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of FILTER must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if success, valid := kern.ToBool(rightValue); valid {
if success {
values.AppendItem(item)
}
} else {
err = fmt.Errorf("filter expression must return a boolean or a castable to boolean, got %v [%T]", rightValue, rightValue)
}
}
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwFilter, newFilterTerm)
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-fraction.go
package expr
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- fraction term
func newFractionTerm(tk *scan.Token) *scan.Term {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriFraction,
EvalFunc: evalFraction,
}
}
// -------- eval func
func evalFraction(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var numValue, denValue any
var num, den int64
var ok bool
if numValue, denValue, err = opTerm.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 = opTerm.Errorf("division by zero")
return
}
if den < 0 {
den = -den
num = -num
}
if num != 0 {
if g := kern.Gcd(num, den); g != 1 {
num = num / g
den = den / g
}
if den == 1 {
v = num
} else {
// v = &expr.FractionType{num, den}
v = kern.NewFraction(num, den)
}
} else {
// v = &FractionType{0, den}
v = kern.NewFraction(0, den)
}
return
}
// init
func init() {
// registerTermConstructor(SymVertBar, newFractionTerm)
scan.RegisterTermConstructor(scan.SymColon, newFractionTerm)
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-groupby.go
package expr
import (
"fmt"
"io"
"strconv"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- group by term
func newGroupByTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalGroupBy,
}
}
func evalGroupBy(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var it kern.Iterator
var item any
var sKey string
var keyByIndex bool
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if it, ok = leftValue.(kern.Iterator); !ok {
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
rightTk := opTerm.Children[1].Tk
if rightTk.IsSymbol(scan.SymVariable) && rightTk.Source() == "__" {
keyByIndex = true
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
return
} else if kern.IsString(rightValue) {
sKey = rightValue.(string)
} else {
return nil, fmt.Errorf("right operand of GROUPBY must be a string or identifier '__'; got %s", kern.TypeName(rightValue))
}
values := kern.MakeDict()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
var sItemKey string
if d, ok := item.(*kern.DictType); ok {
if keyByIndex || len(sKey) == 0 {
sItemKey = strconv.Itoa(int(it.Index()))
} else if d.HasKey(sKey) {
if keyValue, exists := d.GetItem(sKey); exists {
sItemKey = fmt.Sprintf("%v", keyValue)
} else {
sItemKey = "_"
}
} else {
sItemKey = "_"
}
} else {
sItemKey = strconv.Itoa(int(it.Index()))
}
var ls *kern.ListType
if lsAny, exists := values.GetItem(sItemKey); exists && lsAny != nil {
ls = lsAny.(*kern.ListType)
}
if ls == nil {
ls = kern.NewListA()
}
ls.AppendItem(item)
values.SetItem(sItemKey, ls)
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwGroupBy, newGroupByTerm)
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-in.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- in term
func newInTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalIn,
}
}
// func hasKey(d map[any]any, target any) (ok bool) {
// _, ok = d[target]
// return
// }
func evalIn(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if kern.IsList(rightValue) {
list, _ := rightValue.(*kern.ListType)
v = list.IndexDeepSameCmp(leftValue) >= 0
} else if kern.IsDict(rightValue) {
dict, _ := rightValue.(*kern.DictType)
v = dict.HasKey(leftValue)
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwIn, newInTerm)
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-include.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- include term
func newIncludeTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalInclude,
}
}
func evalInclude(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
count := 0
if kern.IsList(childValue) {
list, _ := childValue.(*kern.ListType)
for i, filePathSpec := range *list {
if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil {
count++
} else {
err = opTerm.Errorf("can't load file %q", filePath)
break
}
} else {
err = opTerm.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
break
}
}
} else if kern.IsString(childValue) {
filePath, _ := childValue.(string)
if v, err = EvalFile(ctx, filePath); err == nil {
count++
}
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
if err != nil {
//v = count
v = nil
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwInclude, newIncludeTerm)
}
+143
View File
@@ -0,0 +1,143 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-index.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- index term
func newIndexTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriDot,
EvalFunc: evalIndex,
}
}
func verifyKey(indexList *kern.ListType) (index any, err error) {
index = (*indexList)[0]
return
}
func verifyIndex(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (index int, err error) {
var v int
if v, err = kern.ToGoInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue {
index = v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
}
return
}
func verifyRange(indexTerm *scan.Term, indexList *kern.ListType, maxValue int) (startIndex, endIndex int, err error) {
v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a
endIndex = v.b
if endIndex == kern.ConstLastIndex {
endIndex = maxValue
}
if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex
}
if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex
}
if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
} else if endIndex < 0 || endIndex > maxValue {
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
} else if startIndex > endIndex {
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
}
return
}
func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var indexList *kern.ListType
var ok bool
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
indexTerm := opTerm.Children[1]
if indexList, ok = rightValue.(*kern.ListType); !ok {
err = opTerm.Errorf("invalid index expression")
return
} else if len(*indexList) != 1 {
err = indexTerm.Errorf("one index only is allowed")
return
}
if kern.IsInteger((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *kern.ListType:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
v = (*unboxedValue)[index]
}
case string:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
v = string(unboxedValue[index])
}
case *kern.DictType:
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
} else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
case *kern.ListType:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
sublist := kern.ListType((*unboxedValue)[start:end])
v = &sublist
}
case string:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
v = unboxedValue[start:end]
}
default:
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
} else if kern.IsDict(leftValue) {
d := leftValue.(*kern.DictType)
v, err = getDictItem(d, indexTerm, indexList, rightValue)
} else {
rightChild := opTerm.Children[1]
err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
}
return
}
func getDictItem(d *kern.DictType, indexTerm *scan.Term, indexList *kern.ListType, rightValue any) (v any, err error) {
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymIndex, newIndexTerm)
}
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-insert.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- prepend term
func newPrependTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriInsert,
EvalFunc: evalPrepend,
}
}
func newAppendTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriInsert,
EvalFunc: evalAppend,
}
}
func evalPrepend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if kern.IsList(rightValue) {
list, _ := rightValue.(*kern.ListType)
newList := append(kern.ListType{leftValue}, *list...)
v = &newList
if opTerm.Children[1].Symbol() == scan.SymVariable {
ctx.UnsafeSetVar(opTerm.Children[1].Source(), v)
}
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalAppend(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if kern.IsList(leftValue) {
list, _ := leftValue.(*kern.ListType)
newList := append(*list, rightValue)
v = &newList
if opTerm.Children[0].Symbol() == scan.SymVariable {
ctx.UnsafeSetVar(opTerm.Children[0].Source(), v)
}
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
// if IsList(leftValue) {
// list, _ := leftValue.(*ListType)
// newList := append(*list, rightValue)
// v = &newList
// if
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
// init
func init() {
scan.RegisterTermConstructor(scan.SymPlusGreater, newPrependTerm)
scan.RegisterTermConstructor(scan.SymLessPlus, newAppendTerm)
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-iter-value.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- iter value term
func newIterValueTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriDereference,
EvalFunc: evalIterValue,
}
}
func evalIterValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(kern.Iterator); ok {
v, err = it.Current()
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// init
func init() {
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
scan.RegisterTermConstructor(scan.SymDereference, newIterValueTerm)
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-join.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- join term
func newJoinTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalJoin,
}
}
func evalJoin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var itLeft, itRight kern.Iterator
var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if rightValue, err = opTerm.Children[1].Compute(ctx); err != nil {
return
}
if itLeft, ok = leftValue.(kern.Iterator); !ok {
if itLeft, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of JOIN must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
if itRight, ok = rightValue.(kern.Iterator); !ok {
if itRight, err = NewIterator(ctx, rightValue, nil); err != nil {
return nil, fmt.Errorf("right operand of JOIN must be an iterable data-source; got %s", kern.TypeName(rightValue))
}
}
values := kern.NewListA()
for _, it := range []kern.Iterator{itLeft, itRight} {
for item, err = it.Next(); err == nil; item, err = it.Next() {
values.AppendItem(item)
}
}
if err == io.EOF {
err = nil
}
v = values
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwJoin, newJoinTerm)
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-length.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- length term
func newLengthTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalLength,
}
}
func evalLength(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if kern.IsList(childValue) {
ls, _ := childValue.(*kern.ListType)
v = int64(len(*ls))
} else if kern.IsString(childValue) {
s, _ := childValue.(string)
v = int64(len(s))
} else if kern.IsDict(childValue) {
m, _ := childValue.(*kern.DictType)
v = int64(len(*m))
} else if it, ok := childValue.(kern.Iterator); ok {
v = int64(it.Count())
// if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
// count, _ := extIt.CallOperation(CountName, nil)
// v, _ = ToGoInt(count, "")
// } else {
// v = int64(it.Index() + 1)
// }
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymHash, newLengthTerm)
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-map.go
package expr
import (
"fmt"
"io"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- map term
func newMapTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriIterOp,
EvalFunc: evalMap,
}
}
func evalMap(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
var it kern.Iterator
var item any
var ok bool
if err = opTerm.CheckOperands(); err != nil {
return
}
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
if it, ok = leftValue.(kern.Iterator); !ok {
if it, err = NewIterator(ctx, leftValue, nil); err != nil {
return nil, fmt.Errorf("left operand of MAP must be an iterable data-source; got %s", kern.TypeName(leftValue))
}
}
values := kern.NewListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
ctx.SetVar("_", item)
ctx.SetVar("__", it.Index())
ctx.SetVar("_#", it.Count())
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
values.AppendItem(rightValue)
}
ctx.DeleteVar("_#")
ctx.DeleteVar("__")
ctx.DeleteVar("_")
if err != nil {
break
}
}
if err == io.EOF {
err = nil
}
if err == nil {
v = values
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwMap, newMapTerm)
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-plugin.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- plugin term
func newPluginTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalPlugin,
}
}
func evalPlugin(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
var count int
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if count, err = importPluginFromSearchPath(ctx, childValue); err == nil {
v = int64(count)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwPlugin, newPluginTerm)
}
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc-dec.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- post increment term
func newPostIncTerm(tk *scan.Token) *scan.Term {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPostfix,
Priority: scan.PriIncDec,
EvalFunc: evalPostInc,
}
}
func evalPostInc(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(kern.Iterator); ok {
var namePrefix string
v, err = it.Next()
if opTerm.Children[0].Symbol() == scan.SymVariable {
namePrefix = opTerm.Children[0].Source()
}
ctx.UnsafeSetVar(namePrefix+"_index", it.Index())
if c, err1 := it.Current(); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_current", c)
}
if it.HasOperation(kern.KeyName) {
if k, err1 := it.CallOperation(kern.KeyName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_key", k)
}
}
if it.HasOperation(kern.ValueName) {
if v1, err1 := it.CallOperation(kern.ValueName, nil); err1 == nil {
ctx.UnsafeSetVar(namePrefix+"_value", v1)
}
}
} else if kern.IsInteger(childValue) && opTerm.Children[0].Symbol() == scan.SymVariable {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(opTerm.Children[0].Source(), i+1)
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// -------- post decrement term
func newPostDecTerm(tk *scan.Token) *scan.Term {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPostfix,
Priority: scan.PriIncDec,
EvalFunc: evalPostDec,
}
}
func evalPostDec(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
/* if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else */if kern.IsInteger(childValue) && opTerm.Children[0].Symbol() == scan.SymVariable {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(opTerm.Children[0].Source(), i-1)
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDoublePlus, newPostIncTerm)
scan.RegisterTermConstructor(scan.SymDoubleMinus, newPostDecTerm)
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-pre-inc-dec.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- pre increment term
func newPreIncTerm(tk *scan.Token) *scan.Term {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriIncDec,
EvalFunc: evalPreInc,
}
}
func evalPreInc(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if kern.IsInteger(childValue) && opTerm.Children[0].Symbol() == scan.SymVariable {
i := childValue.(int64) + 1
ctx.SetVar(opTerm.Children[0].Source(), i)
v = i
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// -------- pre decrement term
func newPreDecTerm(tk *scan.Token) *scan.Term {
return &scan.Term{
Tk: *tk,
Parent: nil,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriIncDec,
EvalFunc: evalPreDec,
}
}
func evalPreDec(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if kern.IsInteger(childValue) && opTerm.Children[0].Symbol() == scan.SymVariable {
i := childValue.(int64) - 1
ctx.SetVar(opTerm.Children[0].Source(), i)
v = i
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymPreInc, newPreIncTerm)
scan.RegisterTermConstructor(scan.SymPreDec, newPreDecTerm)
}
+155 -47
View File
@@ -1,95 +1,203 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-prod.go
package expr
import (
"errors"
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- multiply term
func newMultiplyTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalMultiply,
func newMultiplyTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriProduct,
EvalFunc: evalMultiply,
}
}
func evalMultiply(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isString(leftValue) && isInteger(rightValue) {
func mulValues(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if kern.IsString(leftValue) && kern.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) {
v = numAsFloat(leftValue) * numAsFloat(rightValue)
} else if kern.IsNumOrFract(leftValue) && kern.IsNumOrFract(rightValue) {
if kern.IsFloat(leftValue) || kern.IsFloat(rightValue) {
v = kern.NumAsFloat(leftValue) * kern.NumAsFloat(rightValue)
} else if kern.IsFraction(leftValue) || kern.IsFraction(rightValue) {
v, err = kern.MulAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt * rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalMultiply(ctx kern.ExprContext, prodTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = prodTerm.EvalInfix(ctx); err != nil {
return
}
return mulValues(prodTerm, leftValue, rightValue)
}
//-------- divide term
func newDivideTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalDivide,
func newDivideTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriProduct,
EvalFunc: evalDivide,
}
}
func evalDivide(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
d := numAsFloat(rightValue)
func divValues(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if kern.IsNumOrFract(leftValue) && kern.IsNumOrFract(rightValue) {
if kern.IsFloat(leftValue) || kern.IsFloat(rightValue) {
d := kern.NumAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
err = opTerm.ErrDivisionByZero()
} else {
v = numAsFloat(leftValue) / d
v = kern.NumAsFloat(leftValue) / d
}
} else if kern.IsFraction(leftValue) || kern.IsFraction(rightValue) {
v, err = kern.DivAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
if rightInt, _ := rightValue.(int64); rightInt == 0 {
err = errors.New("division by zero")
err = opTerm.ErrDivisionByZero()
} else {
v = leftInt / rightInt
}
}
} else if kern.IsString(leftValue) && kern.IsString(rightValue) {
source := leftValue.(string)
sep := rightValue.(string)
v = kern.ListFromStrings(strings.Split(source, sep))
} else if kern.IsString(leftValue) && kern.IsInteger(rightValue) {
source := leftValue.(string)
partSize := int(rightValue.(int64))
if partSize == 0 {
err = opTerm.ErrDivisionByZero()
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
partCount := len(source) / partSize
remainder := len(source) % partSize
listSize := partCount
if remainder > 0 {
listSize++
}
parts := make([]any, 0, listSize)
for i := 0; i < partCount; i++ {
parts = append(parts, source[i*partSize:(i+1)*partSize])
}
if remainder > 0 {
parts = append(parts, source[len(source)-remainder:])
}
v = kern.NewList(parts)
}
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalDivide(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
return divValues(opTerm, leftValue, rightValue)
}
//-------- divide as float term
func newDivideAsFloatTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriProduct,
EvalFunc: evalDivideAsFloat,
}
}
func evalDivideAsFloat(ctx kern.ExprContext, floatDivTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = floatDivTerm.EvalInfix(ctx); err != nil {
return
}
if kern.IsNumOrFract(leftValue) && kern.IsNumOrFract(rightValue) {
d := kern.NumAsFloat(rightValue)
if d == 0.0 {
err = floatDivTerm.ErrDivisionByZero()
} else {
v = kern.NumAsFloat(leftValue) / d
}
} else {
err = floatDivTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- reminder term
func newRemainderTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriProduct,
EvalFunc: evalRemainder,
}
}
func remainderValues(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if kern.IsInteger(leftValue) && kern.IsInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
err = opTerm.ErrDivisionByZero()
} else {
leftInt, _ := leftValue.(int64)
v = leftInt % rightInt
}
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalRemainder(ctx kern.ExprContext, remainderTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = remainderTerm.EvalInfix(ctx); err != nil {
return
}
return remainderValues(remainderTerm, leftValue, rightValue)
}
// init
func init() {
registerTermConstructor(SymStar, newMultiplyTerm)
registerTermConstructor(SymSlash, newDivideTerm)
scan.RegisterTermConstructor(scan.SymStar, newMultiplyTerm)
scan.RegisterTermConstructor(scan.SymSlash, newDivideTerm)
scan.RegisterTermConstructor(scan.SymDotSlash, newDivideAsFloatTerm)
scan.RegisterTermConstructor(scan.SymPercent, newRemainderTerm)
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-range.go
package expr
import (
"fmt"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
// -------- range term
type intPair struct {
a, b int
}
func (p *intPair) TypeName() string {
return kern.TypePair
}
func (p *intPair) ToString(opt kern.FmtOpt) string {
return fmt.Sprintf("(%d, %d)", p.a, p.b)
}
func isIntPair(v any) bool {
_, ok := v.(*intPair)
return ok
}
func newRangeTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRange,
EvalFunc: evalRange,
}
}
func changeColonToRange(t *scan.Term) {
if t.Tk.IsSymbol(scan.SymColon) {
t.Tk.Sym = scan.SymRange
t.EvalFunc = evalRange
}
}
func evalRange(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if len(opTerm.Children) == 0 {
leftValue = int64(0)
rightValue = int64(-1)
} else if len(opTerm.Children) == 1 {
if leftValue, err = opTerm.Children[0].Compute(ctx); err != nil {
return
}
rightValue = int64(kern.ConstLastIndex)
} else if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if !(kern.IsInteger(leftValue) && kern.IsInteger(rightValue)) {
// err = opTerm.errIncompatibleTypes(leftValue, rightValue)
err = errRangeInvalidSpecification(opTerm)
return
}
startIndex, _ := leftValue.(int64)
endIndex, _ := rightValue.(int64)
v = &intPair{int(startIndex), int(endIndex)}
return
}
func errRangeInvalidSpecification(t *scan.Term) error {
return t.Errorf("invalid range specification")
}
func errRangeUnexpectedExpression(t *scan.Term) error {
return t.Errorf("unexpected range expression")
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymRange, newRangeTerm)
}
+156 -148
View File
@@ -1,220 +1,228 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-rel.go
package expr
import (
"reflect"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- equal term
func newEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalEqual,
func newEqualTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalEqual,
}
}
func evalEqual(ctx exprContext, self *term) (v any, err error) {
// type deepFuncTemplate func(a, b any) (eq bool, err error)
func equals(a, b any, deepCmp kern.DeepFuncTemplate) (eq bool, err error) {
if kern.IsNumOrFract(a) && kern.IsNumOrFract(b) {
if kern.IsNumber(a) && kern.IsNumber(b) {
if kern.IsInteger(a) && kern.IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
eq = li == ri
} else {
eq = kern.NumAsFloat(a) == kern.NumAsFloat(b)
}
} else {
var cmp int
if cmp, err = kern.CmpAnyFract(a, b); err == nil {
eq = cmp == 0
}
}
} else if deepCmp != nil && kern.IsList(a) && kern.IsList(b) {
eq, err = deepCmp(a, b)
} else {
eq = reflect.DeepEqual(a, b)
}
return
}
func evalEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li == ri
} else {
v = numAsFloat(leftValue) == numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls == rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
v, err = equals(leftValue, rightValue, nil)
return
}
//-------- not equal term
func newNotEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalNotEqual,
func newNotEqualTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalNotEqual,
}
}
func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil {
b, _ := toBool(v)
func evalNotEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if v, err = evalEqual(ctx, opTerm); err == nil {
b, _ := kern.ToBool(v)
v = !b
}
return
}
// func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
// var leftValue, rightValue any
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
// return
// }
// if isNumber(leftValue) && isNumber(rightValue) {
// if isInteger(leftValue) && isInteger(rightValue) {
// li, _ := leftValue.(int64)
// ri, _ := rightValue.(int64)
// v = li != ri
// } else {
// v = numAsFloat(leftValue) != numAsFloat(rightValue)
// }
// } else if isString(leftValue) && isString(rightValue) {
// ls, _ := leftValue.(string)
// rs, _ := rightValue.(string)
// v = ls != rs
// } else {
// err = self.errIncompatibleTypes(leftValue, rightValue)
// }
// return
// }
//-------- less term
func newLessTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalLess,
func newLessTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalLess,
}
}
func evalLess(ctx exprContext, self *term) (v any, err error) {
func lessThan(self *scan.Term, a, b any) (isLess bool, err error) {
if kern.IsNumOrFract(a) && kern.IsNumOrFract(b) {
if kern.IsNumber(a) && kern.IsNumber(b) {
if kern.IsInteger(a) && kern.IsInteger(b) {
li, _ := a.(int64)
ri, _ := b.(int64)
isLess = li < ri
} else {
isLess = kern.NumAsFloat(a) < kern.NumAsFloat(b)
}
} else {
var cmp int
if cmp, err = kern.CmpAnyFract(a, b); err == nil {
isLess = cmp < 0
}
}
} else if kern.IsString(a) && kern.IsString(b) {
ls, _ := a.(string)
rs, _ := b.(string)
isLess = ls < rs
// Inclusion test
} else if kern.IsList(a) && kern.IsList(b) {
aList, _ := a.(*kern.ListType)
bList, _ := b.(*kern.ListType)
isLess = len(*aList) < len(*bList) && bList.Contains(aList)
} else {
err = self.ErrIncompatibleTypes(a, b)
}
return
}
func evalLess(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li < ri
} else {
v = numAsFloat(leftValue) < numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls < rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
v, err = lessThan(opTerm, leftValue, rightValue)
return
}
//-------- less or equal term
func newLessEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalLessEqual,
func newLessEqualTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalLessEqual,
}
}
func evalLessEqual(ctx exprContext, self *term) (v any, err error) {
func lessThanOrEqual(self *scan.Term, a, b any) (isLessEq bool, err error) {
if isLessEq, err = lessThan(self, a, b); err == nil {
if !isLessEq {
if kern.IsList(a) && kern.IsList(b) {
isLessEq, err = kern.SameContent(a, b)
} else {
isLessEq, err = equals(a, b, nil)
}
}
}
return
}
func evalLessEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isInteger(leftValue) && isInteger(rightValue) {
li, _ := leftValue.(int64)
ri, _ := rightValue.(int64)
v = li <= ri
} else {
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
}
} else if isString(leftValue) && isString(rightValue) {
ls, _ := leftValue.(string)
rs, _ := rightValue.(string)
v = ls <= rs
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
v, err = lessThanOrEqual(opTerm, leftValue, rightValue)
return
}
//-------- greater term
func newGreaterTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalGreater,
func newGreaterTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalGreater,
}
}
func evalGreater(ctx exprContext, self *term) (v any, err error) {
if v, err = evalLessEqual(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
func evalGreater(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = lessThan(opTerm, rightValue, leftValue)
return
}
//-------- greater or equal term
func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
evalFunc: evalGreaterEqual,
func newGreaterEqualTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriRelational,
EvalFunc: evalGreaterEqual,
}
}
func evalGreaterEqual(ctx exprContext, self *term) (v any, err error) {
if v, err = evalLess(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
func evalGreaterEqual(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = lessThanOrEqual(opTerm, rightValue, leftValue)
return
}
// init
func init() {
registerTermConstructor(SymDoubleEqual, newEqualTerm)
registerTermConstructor(SymNotEqual, newNotEqualTerm)
registerTermConstructor(SymLess, newLessTerm)
registerTermConstructor(SymLessOrEqual, newLessEqualTerm)
registerTermConstructor(SymGreater, newGreaterTerm)
registerTermConstructor(SymGreaterOrEqual, newGreaterEqualTerm)
scan.RegisterTermConstructor(scan.SymDoubleEqual, newEqualTerm)
scan.RegisterTermConstructor(scan.SymNotEqual, newNotEqualTerm)
scan.RegisterTermConstructor(scan.SymLess, newLessTerm)
scan.RegisterTermConstructor(scan.SymLessOrEqual, newLessEqualTerm)
scan.RegisterTermConstructor(scan.SymGreater, newGreaterTerm)
scan.RegisterTermConstructor(scan.SymGreaterOrEqual, newGreaterEqualTerm)
}
+86
View File
@@ -0,0 +1,86 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-selector.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- selector term
func newSelectorTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 3),
Position: scan.PosInfix,
Priority: scan.PriSelector,
EvalFunc: evalSelector,
}
}
func trySelectorCase(ctx kern.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)
match = true
} else if filterList, ok := caseData.filterList.Value().([]*scan.Term); ok {
if len(filterList) == 0 {
var valueAsInt = int64(0)
if b, ok := exprValue.(bool); ok {
if !b {
valueAsInt = 1
}
} else if valueAsInt, ok = exprValue.(int64); !ok {
return
}
if valueAsInt == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.Eval(ctx)
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)
match = true
break
}
}
}
}
return
}
func evalSelector(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var exprValue any
var match bool
if err = opTerm.CheckOperands(); err != nil {
return
}
exprTerm := opTerm.Children[0]
if exprValue, err = exprTerm.Compute(ctx); err != nil {
return
}
caseListTerm := opTerm.Children[1]
caseList, _ := caseListTerm.Value().([]*scan.Term)
for i, caseTerm := range caseList {
caseSel := caseTerm.Value()
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match {
break
}
}
if err == nil && !match {
err = exprTerm.Tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymSelector, newSelectorTerm)
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-shift.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- bit right shift term
func newRightShiftTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriBinShift,
EvalFunc: evalRightShift,
}
}
func bitRightShift(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if kern.IsInteger(leftValue) && kern.IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt >> rightInt
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalRightShift(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = bitRightShift(opTerm, leftValue, rightValue)
return
}
func newLeftShiftTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriBinShift,
EvalFunc: evalLeftShift,
}
}
func bitLeftShift(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if kern.IsInteger(leftValue) && kern.IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt << rightInt
} else {
err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalLeftShift(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
return
}
v, err = bitLeftShift(opTerm, leftValue, rightValue)
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDoubleGreater, newRightShiftTerm)
scan.RegisterTermConstructor(scan.SymDoubleLess, newLeftShiftTerm)
}
+31 -27
View File
@@ -1,61 +1,65 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-sign.go
package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- plus sign term
func newPlusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalSign,
func newPlusSignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalSign,
}
}
func newMinusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalSign,
func newMinusSignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalSign,
}
}
func evalSign(ctx exprContext, self *term) (v any, err error) {
func evalSign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
if rightValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
if isFloat(rightValue) {
if self.tk.Sym == SymChangeSign {
if kern.IsFloat(rightValue) {
if opTerm.Tk.Sym == scan.SymChangeSign {
f, _ := rightValue.(float64)
v = -f
} else {
v = rightValue
}
} else if isInteger(rightValue) {
if self.tk.Sym == SymChangeSign {
} else if kern.IsInteger(rightValue) {
if opTerm.Tk.Sym == scan.SymChangeSign {
i, _ := rightValue.(int64)
v = -i
} else {
v = rightValue
}
} else {
err = self.errIncompatibleType(rightValue)
err = opTerm.ErrIncompatiblePrefixPostfixType(rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymUnchangeSign, newPlusSignTerm)
registerTermConstructor(SymChangeSign, newMinusSignTerm)
scan.RegisterTermConstructor(scan.SymUnchangeSign, newPlusSignTerm)
scan.RegisterTermConstructor(scan.SymChangeSign, newMinusSignTerm)
}
+89 -43
View File
@@ -1,84 +1,130 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-sum.go
package expr
import (
"fmt"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- plus term
func newPlusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
evalFunc: evalPlus,
func newPlusTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriSum,
EvalFunc: evalPlus,
}
}
func evalPlus(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
func sumValues(plusTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if (kern.IsString(leftValue) && kern.IsNumberString(rightValue)) || (kern.IsString(rightValue) && kern.IsNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) + numAsFloat(rightValue)
} else if kern.IsNumber(leftValue) && kern.IsNumber(rightValue) {
if kern.IsFloat(leftValue) || kern.IsFloat(rightValue) {
v = kern.NumAsFloat(leftValue) + kern.NumAsFloat(rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt + rightInt
}
} else if kern.IsList(leftValue) && kern.IsList(rightValue) {
var leftList, rightList *kern.ListType
leftList, _ = leftValue.(*kern.ListType)
rightList, _ = rightValue.(*kern.ListType)
sumList := make(kern.ListType, 0, len(*leftList)+len(*rightList))
sumList = append(sumList, *leftList...)
sumList = append(sumList, *rightList...)
v = &sumList
} else if (kern.IsFraction(leftValue) && kern.IsNumber(rightValue)) || (kern.IsFraction(rightValue) && kern.IsNumber(leftValue)) {
if kern.IsFloat(leftValue) || kern.IsFloat(rightValue) {
v = kern.NumAsFloat(leftValue) + kern.NumAsFloat(rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
v, err = kern.SumAnyFract(leftValue, rightValue)
}
} else if kern.IsDict(leftValue) && kern.IsDict(rightValue) {
leftDict, _ := leftValue.(*kern.DictType)
rightDict, _ := rightValue.(*kern.DictType)
c := leftDict.Clone()
c.Merge(rightDict)
v = c
} else if kern.IsFraction(leftValue) && kern.IsFraction(rightValue) {
v, err = kern.SumAnyFract(leftValue, rightValue)
} else {
err = plusTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return v, err
}
func evalPlus(ctx kern.ExprContext, plusTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = plusTerm.EvalInfix(ctx); err != nil {
return
}
return sumValues(plusTerm, leftValue, rightValue)
}
//-------- minus term
func newMinusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
evalFunc: evalMinus,
func newMinusTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriSum,
EvalFunc: evalMinus,
}
}
func evalMinus(ctx exprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
if isFloat(leftValue) || isFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue)
func diffValues(minusTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
if kern.IsNumOrFract(leftValue) && kern.IsNumOrFract(rightValue) {
if kern.IsFloat(leftValue) || kern.IsFloat(rightValue) {
v = kern.NumAsFloat(leftValue) - kern.NumAsFloat(rightValue)
} else if kern.IsFraction(leftValue) || kern.IsFraction(rightValue) {
v, err = kern.SubAnyFract(leftValue, rightValue)
} else {
leftInt, _ := leftValue.(int64)
rightInt, _ := rightValue.(int64)
v = leftInt - rightInt
}
} else if kern.IsList(leftValue) && kern.IsList(rightValue) {
leftList, _ := leftValue.(*kern.ListType)
rightList, _ := rightValue.(*kern.ListType)
diffList := make(kern.ListType, 0, len(*leftList)-len(*rightList))
for _, item := range *leftList {
if slices.Index(*rightList, item) < 0 {
diffList = append(diffList, item)
}
}
v = &diffList
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = minusTerm.ErrIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalMinus(ctx kern.ExprContext, minusTerm *scan.Term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = minusTerm.EvalInfix(ctx); err != nil {
return
}
return diffValues(minusTerm, leftValue, rightValue)
}
// init
func init() {
registerTermConstructor(SymPlus, newPlusTerm)
registerTermConstructor(SymMinus, newMinusTerm)
scan.RegisterTermConstructor(scan.SymPlus, newPlusTerm)
scan.RegisterTermConstructor(scan.SymMinus, newMinusTerm)
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-unset.go
package expr
import (
"strings"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
//-------- unset term
func newUnsetTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 1),
Position: scan.PosPrefix,
Priority: scan.PriSign,
EvalFunc: evalUnset,
}
}
func deleteContextItem(ctx kern.ExprContext, opTerm *scan.Term, item any) (deleted bool, err error) {
if name, ok := item.(string); ok {
var size int
if strings.HasSuffix(name, "()") {
size = ctx.FuncCount()
ctx.DeleteFunc(strings.TrimRight(name, "()"))
deleted = ctx.FuncCount() < size
} else {
size = ctx.VarCount()
ctx.DeleteVar(name)
deleted = ctx.VarCount() < size
}
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(item)
}
return
}
func evalUnset(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
var childValue any
var deleted bool
if childValue, err = opTerm.EvalPrefix(ctx); err != nil {
return
}
count := 0
if kern.IsList(childValue) {
list, _ := childValue.(*kern.ListType)
for _, item := range *list {
if deleted, err = deleteContextItem(ctx, opTerm, item); err != nil {
break
} else if deleted {
count++
}
}
} else if deleted, err = deleteContextItem(ctx, opTerm, childValue); err == nil && deleted {
count++
}
if err == nil {
v = int64(count)
}
return
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymKwUnset, newUnsetTerm)
}

Some files were not shown because too many files have changed in this diff Show More