Compare commits

...

17 Commits

Author SHA1 Message Date
camoroso ac5c97bfd3 t_helpers_test.go: enhanced tests for the helpers module 2026-05-21 03:54:13 +02:00
camoroso e1c24daac4 rationalized context convertion to dict and string types 2026-05-21 03:52:48 +02:00
camoroso a62f27b104 increased test coverage (83.9%) 2026-05-21 03:06:52 +02:00
camoroso 1055569dd6 Dict literal now accepts expressions as keys. Key values are computed at evalutetion time and still must be integer or string 2026-05-20 05:14:22 +02:00
camoroso 1aea1c14d2 LinkedList: added support for index and range extraction 2026-05-19 06:42:45 +02:00
camoroso 081395be5f linked-list-iterator and context operator $$ enhancement 2026-05-18 09:50:06 +02:00
camoroso 35a599b284 operator length # supports linked-list 2026-05-18 08:56:44 +02:00
camoroso eda3037855 operator $$() changed to return a linked-list in place of list 2026-05-18 08:55:28 +02:00
camoroso 47c181546a linked-list: added forgotten source files 2026-05-18 08:53:46 +02:00
camoroso a8a5d6aaa6 Linked-List: constructor NewLinkedListA() accepts any type of int and float 2026-05-18 06:24:59 +02:00
camoroso 84b255a51b new type LinkedList, preliminary implementation 2026-05-17 22:43:27 +02:00
camoroso 9efdeffcac test with mixed field name and index 2026-05-17 19:14:35 +02:00
camoroso d34b9d8a48 dict: implemented sub-field access, e.g D=["sub-dict"]["sub-field"] 2026-05-17 18:57:27 +02:00
camoroso 1bf8015da1 the assign operators, '=' and ':=', can set the value of dict items 2026-05-17 07:15:12 +02:00
camoroso 3ccbeb3978 util/utils.go: new function UnquoteString() 2026-05-17 07:02:35 +02:00
camoroso 0c719025cd new operator ':=', it assigns a value to a variable by deep copy 2026-05-17 05:02:07 +02:00
camoroso 08617378e0 doc: added some details 2026-05-16 17:33:45 +02:00
44 changed files with 1806 additions and 552 deletions
+14
View File
@@ -275,6 +275,16 @@ func charFunc(ctx kern.ExprContext, name string, args map[string]any) (result an
return
}
func seqFunc(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
list := kern.NewLinkedList()
items := args[kern.ParamValue].([]any)
for _, arg := range items {
list.PushBack(arg)
}
result = list
return
}
//// import
func ImportBuiltinsFuncs(ctx kern.ExprContext) {
@@ -318,6 +328,10 @@ func ImportBuiltinsFuncs(ctx kern.ExprContext) {
ctx.RegisterFunc("char", kern.NewGolangFunctor(charFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
})
ctx.RegisterFunc("seq", kern.NewGolangFunctor(seqFunc), kern.TypeLinkedList, []kern.ExprFuncParam{
kern.NewFuncParamFlag(kern.ParamValue, kern.PfRepeat),
})
}
func init() {
+133 -10
View File
@@ -34,7 +34,7 @@ Expressions calculator
toc::[]
#TODO: Work in progress (last update on 2026/05/08)#
#TODO: Work in progress#
== Expr
_Expr_ is a GO package that can analyze, interpret and calculate expressions.
@@ -100,7 +100,28 @@ An interactive tool could like `dev-expr` (see <<_dev-expr_test_tool>>) can be u
[green]`{2sp}}` +
[green]`}`
In order to inspect the global context issue the [blue]`$$global` operator.
In order to inspect the global context issue the [blue]`$$ global` operation.
////
.Example: list all functions whose name starts with "str"
`>>>` [blue]`builtin "string` [gray]__// most function in the builtin module *string* have names starting with "str".__ +
[green]`1`
:dollar: $
:2dollars: $$
`>>>` [blue]`($(($$global).functions) filter strStartsWith($_, "str"))` +
[green]`[` +
[green]`{2sp}"strEndsWith",` +
[green]`{2sp}"strJoin",` +
[green]`{2sp}"strLower",` +
[green]`{2sp}"strSplit",` +
[green]`{2sp}"strStartsWith",` +
[green]`{2sp}"strSub",` +
[green]`{2sp}"strTrim",` +
[green]`{2sp}"strUpper",` +
[green]`{2sp}"string"` +
[green]`]`
////
=== `dev-expr` test tool
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
@@ -223,14 +244,20 @@ Value range: *-9223372036854775808* to *9223372036854775807*
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _Sum_ | Add two values | [blue]`-1 + 2` -> _1_
| [blue]`+` | _Sum_ | Add two values^(<<note_int_plus_string,1>>)^ | [blue]`-1 + 2` -> _1_
| [blue]`-` | _Subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> _2_
| [blue]`*` | _Product_ | Multiply two values | [blue]`-1 * 2` -> _-2_
| [blue]`/` | _Integer division_ | Divide the left value by the right one^(*)^ | [blue]`-11 / 2` -> _-5_
| [blue]`*` | _Product_ | Multiply two values^(<<note_string_repl,2>>)^ | [blue]`-1 * 2` -> _-2_
| [blue]`/` | _Integer division_ | Divide the left value by the right one^(<<note_float_division,3>>)^ | [blue]`-11 / 2` -> _-5_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> _1_
|===
[[note_int_plus_string]]
^(1)^ The sum operator [blue]`+` also supports adding an integer number to a string. In this case, the number is converted to a string and prependend or appended to the string, e.g. `"x" + 48` results in `"x48"`.
^(*)^ See also the _float division_ [blue]`./` below.
[[note_string_repl]]
^(2)^ The product operator also supports multiplying a string by an integer. In this case, the number represents homw may times the string has to be repeated in the result, e.g. `"foo" * 3` returnsn `"foofoofoo"`.
[[note_float_division]]
^(3)^ See also the _float division_ [blue]`./` below.
==== Floats
@@ -268,12 +295,14 @@ _dec-seq_ = _see-integer-literal-syntax_
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _Sum_ | Add two values | [blue]`4 + 0.5` -> 4.5
| [blue]`+` | _Sum_ | Add two values^(<<note_float_plus_string,1>>)^ | [blue]`4 + 0.5` -> 4.5
| [blue]`-` | _Subtraction_ | Subtract the right value from the left one | [blue]`4 - 0.5` -> 3.5
| [blue]`*` | _Product_ | Multiply two values | [blue]`4 * 0.5` -> 2.0
| [blue]`/` | _Float division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
| [blue]`./`| _Forced float division_ | Force float division | [blue]`-1 ./ 2` -> -0.5
|===
[[note_float_plus_string]]
^(1)^ The sum operator [blue]`+` also supports adding a float number to a string. In this case, the number is converted to a string and prependend or appended to the string, e.g. `"x" + 1.2` results in `"x1.2"`.
==== Fractions
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a colon character `:`.
@@ -860,7 +889,7 @@ The table below shows all supported operators by decreasing priorities.
.6+|*ITER-OP*| [blue]`digest` | _Infix_ | _Item-digesting_ | _iterable_ `digest` _expr_ -> _any_
| [blue]`filter` | _Infix_ | _Item-filtering_ | _iterable_ `filter` _expr_ -> _list_
| [blue]`groupby` | _Infix_ | _Dict-grouping_ | _iterable_ `groupby` _key-expr_ -> _dict_
| [blue]`join` | _Infix_ | _Item-joining_ | _iterable_ `join` _iterable_ -> _list_
| [blue]`cat` | _Infix_ | _Item-concatenation_ | _iterable_ `cat ` _iterable_ -> _list_
| [blue]`map` | _Infix_ | _Item-mapping_ | _iterable_ `map` _-expr_ -> _list_
4+| _See iterators section for examples_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
@@ -1507,10 +1536,56 @@ Creates or truncates the named _<file-path>_. If the file already exists, it is
#to-do#
===== fileByteIterator()
#to-do#
Syntax: +
`{4sp}fileByteIterator(handle-or-path) -> iterator`
Returns an iterator that produces the bytes of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.
.Examples
>>> builtin "os.file" +
[green]`1` +
`>>>` [blue]`fileByteIterator("test-file.txt") map $_` +
[green]`[
117,
110,
111,
10,
100,
117,
101,
10
]`
>>> builtin ["os.file", "string"] +
[green]`2` +
`>>>` [blue]`fileByteIterator("test-file.txt") map char($_)` +
[green]`[
"u",
"n",
"o",
"
",
"d",
"u",
"e",
"
",
]`
===== fileLineIterator()
#to-do#
Syntax: +
`{4sp}fileLineIterator(handle-or-path) -> iterator`
Returns an iterator that produces the lines of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.
.Examples
`>>>` [blue]`builtin "os.file"` +
[green]`1` +
`>>>` [blue]`fileLineIterator("test-file.txt") map $_` +
[green]`[
"uno",
"due"
]`
==== Module "string"
@@ -1775,6 +1850,54 @@ TIP: Iterators built on custom data-sources can provide additional named operato
`>>>` [blue]`it.next` +
[green]`"one"`
=== Infixed operators on iterators
There are also some infixed operators that can be used with iterators. They are defined as follows.
* <<_cat,cat operator>>
* <<_diget,digest operator>>
* <<_filter,filter operator>>
* <<_groupby,groupby operator>>
* <<_map,map operator>>
//* <<_reduce,reduce operator>>: applies a binary expression cumulatively to the elements of the iterator, from left to right, to reduce the iterator to a single value.
//* <<_zip,zip operator>>: takes two or more iterators and returns a list of tuples, where the i-th tuple contains the i-th element from each of the input iterators.
---
==== [blue]`cat` operator
Syntax: +
`{4sp}<iterable> cat <iterable> -> <iterator>{4sp}`
[blue]`cat` operator takes two iterators or iterables and returns a new iterator that produces the elements of the first iterator followed by the elements of the second iterator.
.Examples
==== [blue]`filter` operator
Syntax: +
`{4sp}<iterable> filter <expr> -> <iterator>{4sp}`
[blue]`filter` applies a boolean expression to each element of the iterator and returns a list of the elements for which the expression evaluates to true.
==== [blue]`groupby` operator
Syntax: +
`{4sp}<dict> groupby <key> -> <dict>{4sp}`
[blue]`groupby` operator groups the elements of the iterator based on the value of a specified expression and returns a dictionary where the keys are the group values and the values are lists of the elements in each group.
==== [blue]`map` operator
Syntax: +
`{4sp}<iterable> map <expr> -> <list>{4sp}`
[blue]`map` operator iterates over the elements of the iterator and evaluates the expressions provided on the right side for each element. Its result is a list of the computed values of the that expression. The current element of the iterator is available in the expression as the variable `$_`.
.Example: using the [blue]`map` operator
`>>>` [blue]`it = $(["one", "two", "three"])` +
[green]`$(#3)` +
`>>>` [blue]`it map $_ + "!"` +
[green]`["one!", "two!", "three!"]`
=== Iterator over custom data-sources
It is possible to create iterators over custom data-sources by defining a dictionary that has the key `next` whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.
+460 -30
View File
@@ -581,7 +581,11 @@ pre.rouge .ss {
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
<li><a href="#_but_operator">4.2. <code class="blue">but</code> operator</a></li>
<li><a href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></li>
<li><a href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></li>
<li><a href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a>
<ul class="sectlevel3">
<li><a href="#_triple_special_case_of_the_selector_operator">4.4.1. Triple special case of the selector operator</a></li>
</ul>
</li>
<li><a href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code>, <code class="blue">?=</code>, and <code class="blue">?!</code></a></li>
</ul>
</li>
@@ -613,8 +617,10 @@ pre.rouge .ss {
<li><a href="#_dec">dec()</a></li>
<li><a href="#_string">string()</a></li>
<li><a href="#_fract">fract()</a></li>
<li><a href="#_char">char()</a></li>
<li><a href="#_eval">eval()</a></li>
<li><a href="#_var">var()</a></li>
<li><a href="#_set">set</a></li>
</ul>
</li>
<li><a href="#_module_fmt">7.1.2. Module "fmt"</a>
@@ -649,6 +655,8 @@ pre.rouge .ss {
<li><a href="#_filewritetext">fileWriteText()</a></li>
<li><a href="#_filereadtext">fileReadText()</a></li>
<li><a href="#_filereadtextall">fileReadTextAll()</a></li>
<li><a href="#_filebyteiterator">fileByteIterator()</a></li>
<li><a href="#_filelineiterator">fileLineIterator()</a></li>
</ul>
</li>
<li><a href="#_module_string">7.1.7. Module "string"</a>
@@ -674,7 +682,15 @@ pre.rouge .ss {
<li><a href="#_named_operators">8.1.1. Named operators</a></li>
</ul>
</li>
<li><a href="#_iterator_over_custom_data_sources">8.2. Iterator over custom data-sources</a></li>
<li><a href="#_infixed_operators_on_iterators">8.2. Infixed operators on iterators</a>
<ul class="sectlevel3">
<li><a href="#_cat_operator">8.2.1. <code class="blue">cat</code> operator</a></li>
<li><a href="#_filter_operator">8.2.2. <code class="blue">filter</code> operator</a></li>
<li><a href="#_groupby_operator">8.2.3. <code class="blue">groupby</code> operator</a></li>
<li><a href="#_map_operator">8.2.4. <code class="blue">map</code> operator</a></li>
</ul>
</li>
<li><a href="#_iterator_over_custom_data_sources">8.3. Iterator over custom data-sources</a></li>
</ul>
</li>
<li><a href="#_plugins">9. Plugins</a></li>
@@ -686,7 +702,7 @@ pre.rouge .ss {
<div class="sectionbody">
<!-- toc disabled -->
<div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2026/04/15, 6:02 p.m.)</mark></p>
<p><mark>TODO: Work in progress (last update on 2026/05/08)</mark></p>
</div>
</div>
</div>
@@ -788,7 +804,26 @@ pre.rouge .ss {
<code class="green">}</code></p>
</div>
<div class="paragraph">
<p>In order to inspect the global context issue the <code class="blue">$$global</code> operator.</p>
<p>In order to inspect the global context issue the <code class="blue">$$ global</code> operation.</p>
</div>
<div class="paragraph">
<div class="title">Example: list all functions whose name starts with "str"</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">builtin "string</code> <em class="gray">// most function in the builtin module <strong>string</strong> have names starting with "str".</em><br>
<code class="green">1</code></p>
</div>
<div class="paragraph">
<p><code>&gt;&gt;&gt;</code> <code class="blue">($$$global).functions) filter strStartsWith($_, "str"</code><br>
<code class="green">[</code><br>
<code class="green">&#160;&#160;"strEndsWith",</code><br>
<code class="green">&#160;&#160;"strJoin",</code><br>
<code class="green">&#160;&#160;"strLower",</code><br>
<code class="green">&#160;&#160;"strSplit",</code><br>
<code class="green">&#160;&#160;"strStartsWith",</code><br>
<code class="green">&#160;&#160;"strSub",</code><br>
<code class="green">&#160;&#160;"strTrim",</code><br>
<code class="green">&#160;&#160;"strUpper",</code><br>
<code class="green">&#160;&#160;"string"</code><br>
<code class="green">]</code></p>
</div>
</div>
</div>
@@ -1880,10 +1915,10 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
<p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p>
</div>
<div class="exampleblock">
<div class="title">Example 13. Selector literal Syntax</div>
<div class="title">Example 13. Selector literal syntax</div>
<div class="content">
<div class="paragraph">
<p><em>selector-operator</em> = <em>select-expression</em> "<strong>?</strong>" <em>selector-case</em> { "<strong>:</strong>" <em>selector-case</em> } ["<strong>::</strong>" <em>default-multi-expression</em>]<br>
<p><strong><em>selector-operator</em></strong> = <em>select-expression</em> "<strong>?</strong>" <em>selector-case</em> { "<strong>:</strong>" <em>selector-case</em> } ["<strong>::</strong>" <em>default-multi-expression</em>]<br>
<em>selector-case</em> = [<em>match-list</em>] <em>case-value</em><br>
<em>match-list</em> = "<strong>[</strong>" <em>item</em> {"<strong>,</strong>" <em>items</em>} "<strong>]</strong>"<br>
<em>item</em> = <em>expression</em><br>
@@ -1931,11 +1966,45 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
<p><code>&gt;&gt;&gt;</code> <code class="blue">10 ? {"a"} : {"b"}</code><br>
<code class="red">Eval Error: [1:3] no case catches the value (10) of the selection expression</code></p>
</div>
<div class="sect3">
<h4 id="_triple_special_case_of_the_selector_operator"><a class="anchor" href="#_triple_special_case_of_the_selector_operator"></a><a class="link" href="#_triple_special_case_of_the_selector_operator">4.4.1. Triple special case of the selector operator</a></h4>
<div class="paragraph">
<p>If the <em>select-expression</em> is a boolean expression, the selector operator can be used as a sort of <em>if-then-else</em> statement. In this case, the first case is evaluated if the <em>select-expression</em> is true, and the second case is evaluated if the <em>select-expression</em> is false. In this special case, the <em>match-list</em> of both cases must be empty.</p>
</div>
<div class="paragraph">
<div class="title">Example</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">(true) ? {"T"}: {"F"}</code><br>
<code class="green">T</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">(2 &gt; 1) ? {"a"} : {"b"}</code><br>
<code class="green">a</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">(2 &lt; 1) ? {"a"} : {"b"}</code><br>
<code class="green">b</code></p>
</div>
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
<div class="paragraph">
<p>The triple special case of the selector operator is very useful, but it only works with boolean expressions.</p>
</div>
<div class="paragraph">
<div class="title">Example of confusion</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">int(true) ? {"T"}: {"F"}</code><br>
<code class="green">F</code></p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_variable_default_value_and"><a class="anchor" href="#_variable_default_value_and"></a><a class="link" href="#_variable_default_value_and">4.5. Variable default value <code class="blue">??</code>, <code class="blue">?=</code>, and <code class="blue">?!</code></a></h3>
<div class="paragraph">
<p>The left operand of the first two operators, <code class="blue">??</code> and <code class="blue">?=</code>, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.</p>
<p>The left operand of the first two operators, <code class="blue">??</code> and <code class="blue">?=</code>, must be a variable. The right operatand can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.</p>
</div>
<div class="admonitionblock important">
<table>
@@ -1953,7 +2022,7 @@ If the left variable is defined, the right expression is not evaluated at all.
<p>The <code class="blue">??</code> operator do not change the status of the left variable.</p>
</div>
<div class="paragraph">
<p>The <code class="blue">?=</code> assigns the calculated value of the right expression to the left variable.</p>
<p>The <code class="blue">?=</code> assigns the calculated value of the right expression to the variable on the left side.</p>
</div>
<div class="paragraph">
<p>The third one, <code class="blue">?!</code>, is the alternate operator. If the variable on the left size is not defined, it returns <em class="blue">nil</em>. Otherwise it returns the result of the expressione on the right side.</p>
@@ -1965,7 +2034,7 @@ If the left variable is defined, the right expression is not evaluated at all.
<i class="fa icon-important" title="Important"></i>
</td>
<td class="content">
If the left variable is NOT defined, the right expression is not evaluated at all.
If the variable <code class="blue">?!</code> is NOT defined, the expression is not evaluated at all.
</td>
</tr>
</table>
@@ -2371,7 +2440,7 @@ These operators have a high priority, in particular higher than the operator <co
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>identifier</em> <code>=</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" colspan="4"><p class="tableblock"><em>See also the table of special allocation operators below</em></p></td>
<td class="tableblock halign-center valign-top" colspan="4"><p class="tableblock"><em>See also the table of special assignment operators below</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>BUT</strong></p></td>
@@ -2381,6 +2450,40 @@ These operators have a high priority, in particular higher than the operator <co
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>any</em> <code>but</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" rowspan="6"><p class="tableblock"><strong>ITER-OP</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">digest</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-digesting</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>digest</code> <em>expr</em> &#8594; <em>any</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">filter</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-filtering</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>filter</code> <em>expr</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">groupby</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict-grouping</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>groupby</code> <em>key-expr</em> &#8594; <em>dict</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">cat</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-concatenation</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> `cat ` <em>iterable</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">map</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Item-mapping</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>iterable</em> <code>map</code> <em>-expr</em> &#8594; <em>list</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top" colspan="4"><p class="tableblock"><em>See iterators section for examples</em></p></td>
</tr>
<tr>
<td class="tableblock halign-center valign-top"><p class="tableblock"><strong>RANGE</strong></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">:</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
@@ -2536,14 +2639,14 @@ short for<br>
</div>
<div class="paragraph">
<p><code>&gt;&gt;&gt;</code> <em class="gray">// Required and optional parameters</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">measure = func(value, unit="meter"){ value + " " + unit + (value &gt; 1) ? [true] {"s"} :: {""}}</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">measure = func(value, unit="meter"){ value + " " + unit + (value &gt; 1) ? {"s"} :: {""}}</code><br>
<code class="green">measure(value, unit="meter"):any{}</code></p>
</div>
</div>
<div class="sect2">
<h3 id="_golang_function_definition"><a class="anchor" href="#_golang_function_definition"></a><a class="link" href="#_golang_function_definition">6.2. <em>Golang</em> function definition</a></h3>
<div class="paragraph">
<p>Description of how to define Golang functions and how to bind them to <em>Expr</em> are topics covered in another document that I&#8217;ll write, one day, maybe.</p>
<p>Description of how to define Golang functions and how to bind them to <em>Expr</em> are topics covered in another documents that I&#8217;ll write, one day, maybe.</p>
</div>
</div>
<div class="sect2">
@@ -2642,7 +2745,7 @@ short for<br>
<div class="title">Example</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">f = func() { @x = 3; x = 5 }</code> <em class="gray">// f() declares two <strong>different</strong> local variables: <code>@x</code> and <code>x</code></em><br>
<code class="green">f():any{}</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">f()</code> <em class="gray">// The multi-expression (two) in f() is calculated and the last result is returned</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">f()</code> <em class="gray">// The multi-expression (two expressions) in f() is calculated and the last result is returned</em><br>
<code class="green">5</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">x</code> <em class="gray">// The <code>x</code> variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the <code>@x</code> variable, local to f() after its termnation.</em><br>
<code class="green">3</code></p>
@@ -2727,7 +2830,7 @@ The clone modifier <code class="blue">@</code> does not make a variable a refere
<div class="title">Example 16. Builtin activation syntax</div>
<div class="content">
<div class="paragraph">
<p><strong><em>builtin-activation</em></strong> = <code class="blue">BUILTIN</code> (<em>builtin-name</em> | <em>list-of-builtin-names</em>)<br>
<p><strong><em>builtin-activation</em></strong> = <code class="blue">BUILTIN</code> (<em>builtin-name</em> | <em>list-of-builtin-names</em> | <strong>"*"</strong>)<br>
<em>builtin-name</em> = <em>string</em><br>
<em>list-of-builtin-names</em> = <strong>[</strong> <em>string</em> { "<strong>,</strong>" <em>string</em> } <strong>]</strong></p>
</div>
@@ -2815,9 +2918,15 @@ To avoid the need to activate builtin modules one by one, it is possible to acti
<div class="title">Other functions</div>
<ul>
<li>
<p><a href="#_char">char()</a></p>
</li>
<li>
<p><a href="#_eval">eval()</a></p>
</li>
<li>
<p><a href="#_set">set()</a></p>
</li>
<li>
<p><a href="#_var">var()</a></p>
</li>
</ul>
@@ -2833,7 +2942,9 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is boolean, fal
<p><code>&gt;&gt;&gt;</code> <code class="blue">isBool(true)</code><br>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isBool(3==2)</code><br>
<code class="green">true</code></p>
<code class="green">true</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">isBool(3 + 2)</code><br>
<code class="green">false</code></p>
</div>
</div>
<div class="sect4">
@@ -2938,7 +3049,7 @@ Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is fraction or
<h5 id="_isstring"><a class="anchor" href="#_isstring"></a><a class="link" href="#_isstring">isString()</a></h5>
<div class="paragraph">
<p>Syntax: <code>isString(&lt;expr&gt;) &#8594; bool</code><br>
Returns a boolean value , false otherwise.</p>
Returns <em>true</em> if the value type of <em>&lt;expr&gt;</em> is string, false otherwise.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
@@ -2954,7 +3065,7 @@ Returns a boolean value , false otherwise.</p>
<h5 id="_bool"><a class="anchor" href="#_bool"></a><a class="link" href="#_bool">bool()</a></h5>
<div class="paragraph">
<p>Syntax: <code>bool(&lt;expr&gt;) &#8594; bool</code><br>
Returns a <em>boolean</em> value consisent to the value of the expression.</p>
Returns a <em>boolean</em> value consisent with the value of the expression.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
@@ -3077,6 +3188,20 @@ Returns a <em>fraction</em> value consistent with the value of the expression.</
</div>
</div>
<div class="sect4">
<h5 id="_char"><a class="anchor" href="#_char"></a><a class="link" href="#_char">char()</a></h5>
<div class="paragraph">
<p>Syntax: <code>char(&lt;intexpr&gt;) &#8594; string</code><br>
Returns the character whose ASCII (soon Unicode too) code point is specified by the integer expression.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">char(65)</code><br>
<code class="green">"A"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">char(97)</code><br>
<code class="green">"a"</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_eval"><a class="anchor" href="#_eval"></a><a class="link" href="#_eval">eval()</a></h5>
<div class="paragraph">
<p>Syntax: <code>eval(&lt;string-expr&gt;) &#8594; any</code><br>
@@ -3096,13 +3221,13 @@ Computes and returns the value of the <span class="underline">string</span> expr
<code>&#160;&#160;&#160;&#160;var(&lt;string-expr&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>This function allows you to define variables whose names must include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.</p>
<p>This function allows you to define variables whose names can include special characters. The first form of the function allows you to define a variable with a name specified by the first parameter and assign it the value of the second parameter. The second form only returns the value of the variable with the specified name.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">var("$x", 3+9)</code><br>
<code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("$x")</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("$"+"x")</code><br>
<code class="green">12</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("gain%", var("$x"))</code><br>
<code class="green">12</code><br>
@@ -3110,9 +3235,32 @@ Computes and returns the value of the <span class="underline">string</span> expr
<code class="green">13</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_set"><a class="anchor" href="#_set"></a><a class="link" href="#_set">set</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;set(&lt;string-expr&gt;, &lt;expr&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>This function allows you to set the value of a variable whose name can include special characters. The first parameter is the name of the variable and the second parameter is the new value to assign to that variable.</p>
</div>
<div class="paragraph">
<p>It is equivalent to the first form of the var() function, but it is more explicit about the intent of changing the value of an existing variable.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">set("$x", 100)</code><br>
<code class="green">100</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">var("$x")</code><br>
<code class="green">100</code><br></p>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_module_fmt"><a class="anchor" href="#_module_fmt"></a><a class="link" href="#_module_fmt">7.1.2. Module "fmt"</a></h4>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
<div class="sect4">
<h5 id="_print"><a class="anchor" href="#_print"></a><a class="link" href="#_print">print()</a></h5>
@@ -3124,10 +3272,18 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div>
<div class="sect3">
<h4 id="_module_import"><a class="anchor" href="#_module_import"></a><a class="link" href="#_module_import">7.1.3. Module "import"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "import"</code></p>
</div>
<div class="sect4">
<h5 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import"><em>import()</em></a></h5>
<div class="paragraph">
<p><em class="blue">import(<span class="grey">&lt;source-file&gt;</span>)</em>&#8201;&#8212;&#8201;loads the multi-expression contained in the specified source and returns its value.</p>
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;import(&lt;source-file&gt;)</code></p>
</div>
<div class="paragraph">
<p>Loads the multi-expression contained in the specified source and returns its value.</p>
</div>
</div>
<div class="sect4">
@@ -3137,16 +3293,40 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div>
<div class="sect3">
<h4 id="_module_iterator"><a class="anchor" href="#_module_iterator"></a><a class="link" href="#_module_iterator">7.1.4. Module "iterator"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "iterator"</code></p>
</div>
<div class="sect4">
<h5 id="_run"><a class="anchor" href="#_run"></a><a class="link" href="#_run">run()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;run(&lt;iterator&gt;, &lt;operator&gt;, &lt;vars&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>Iterates over the specified iterator and applies the specified operator to the current value of the iterator.</p>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_module_math_arith"><a class="anchor" href="#_module_math_arith"></a><a class="link" href="#_module_math_arith">7.1.5. Module "math.arith"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "math.arith"</code></p>
</div>
<div class="paragraph">
<p>Currently, the "math.arith" module provides two functions, add() and mul(), that perform addition and multiplication of an arbitrary number of parameters. More functions will be added in the future.</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#_add">add()</a></p>
</li>
<li>
<p><a href="#_mul">mul()</a></p>
</li>
</ul>
</div>
<div class="sect4">
<h5 id="_add"><a class="anchor" href="#_add"></a><a class="link" href="#_add">add()</a></h5>
<div class="paragraph">
@@ -3202,37 +3382,213 @@ Computes and returns the value of the <span class="underline">string</span> expr
</div>
<div class="sect3">
<h4 id="_module_os_file"><a class="anchor" href="#_module_os_file"></a><a class="link" href="#_module_os_file">7.1.6. Module "os.file"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "os.file"</code></p>
</div>
<div class="paragraph">
<p>The "os.file" module provides functions for working with files.</p>
</div>
<div class="ulist">
<div class="title">File related functions</div>
<ul>
<li>
<p><a href="#_fileOpen">fileOpen()</a></p>
</li>
<li>
<p><a href="#_fileAppend">fileAppend()</a></p>
</li>
<li>
<p><a href="#_fileCreate">fileCreate()</a></p>
</li>
<li>
<p><a href="#_fileClose">fileClose()</a></p>
</li>
<li>
<p><a href="#_fileWriteText">fileWriteText()</a></p>
</li>
<li>
<p><a href="#_fileReadText">fileReadText()</a></p>
</li>
<li>
<p><a href="#_fileReadTextAll">fileReadTextAll()</a></p>
</li>
</ul>
</div>
<div class="ulist">
<div class="title">Iterator functions for files</div>
<ul>
<li>
<p><a href="#_fileByteIterator">fileByteIterator()</a></p>
</li>
<li>
<p><a href="#_fileLineIterator">fileLineIterator()</a></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>More functions will be added in the future.</p>
</div>
<hr>
<div class="sect4">
<h5 id="_fileopen"><a class="anchor" href="#_fileopen"></a><a class="link" href="#_fileopen">fileOpen()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileOpen(&lt;file-path&gt;) &#8594; file-handle</code></p>
</div>
<div class="paragraph">
<p>Returns a file handle for the specified file path. The file is opened in read-write mode. If the file does not exist, it is created.</p>
</div>
</div>
<div class="sect4">
<h5 id="_fileappend"><a class="anchor" href="#_fileappend"></a><a class="link" href="#_fileappend">fileAppend()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileAppend(&lt;file-path&gt;) &#8594; any</code></p>
</div>
<div class="paragraph">
<p>Like <a href="#_fileCreate">fileCreate()</a> but write operations happen at the end of the file.</p>
</div>
</div>
<div class="sect4">
<h5 id="_filecreate"><a class="anchor" href="#_filecreate"></a><a class="link" href="#_filecreate">fileCreate()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileCreate(&lt;file-path&gt;) &#8594; file-handle</code></p>
</div>
<div class="paragraph">
<p>Creates or truncates the named <em>&lt;file-path&gt;</em>. If the file already exists, it is truncated. If the file does not exist, it is created with mode 0o666 (before umask). The associated file descriptor has mode [O_RDWR]. The directory containing the file must already exist.</p>
</div>
</div>
<div class="sect4">
<h5 id="_fileclose"><a class="anchor" href="#_fileclose"></a><a class="link" href="#_fileclose">fileClose()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div>
<div class="sect4">
<h5 id="_filewritetext"><a class="anchor" href="#_filewritetext"></a><a class="link" href="#_filewritetext">fileWriteText()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div>
<div class="sect4">
<h5 id="_filereadtext"><a class="anchor" href="#_filereadtext"></a><a class="link" href="#_filereadtext">fileReadText()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div>
<div class="sect4">
<h5 id="_filereadtextall"><a class="anchor" href="#_filereadtextall"></a><a class="link" href="#_filereadtextall">fileReadTextAll()</a></h5>
<div class="paragraph">
<p><mark>to-do</mark></p>
</div>
</div>
<div class="sect4">
<h5 id="_filebyteiterator"><a class="anchor" href="#_filebyteiterator"></a><a class="link" href="#_filebyteiterator">fileByteIterator()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileByteIterator(handle-or-path) &#8594; iterator</code></p>
</div>
<div class="paragraph">
<p>Returns an iterator that produces the bytes of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p>&gt;&gt;&gt; builtin "os.file"<br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fileByteIterator("test-file.txt") map $_</code><br>
<code class="green">[
117,
110,
111,
10,
100,
117,
101,
10
]</code></p>
</div>
<div class="paragraph">
<p>&gt;&gt;&gt; builtin ["os.file", "string"]<br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fileByteIterator("test-file.txt") map char($_)</code><br>
<code class="green">[
"u",
"n",
"o",
"
",
"d",
"u",
"e",
"
",
]</code></p>
</div>
</div>
<div class="sect4">
<h5 id="_filelineiterator"><a class="anchor" href="#_filelineiterator"></a><a class="link" href="#_filelineiterator">fileLineIterator()</a></h5>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;fileLineIterator(handle-or-path) &#8594; iterator</code></p>
</div>
<div class="paragraph">
<p>Returns an iterator that produces the lines of the specified file. The parameter can be either a file handle or a file path. If a file path is provided, the file is opened and closed automatically by the iterator.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">builtin "os.file"</code><br>
<code class="green">1</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">fileLineIterator("test-file.txt") map $_</code><br>
<code class="green">[
"uno",
"due"
]</code></p>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_module_string"><a class="anchor" href="#_module_string"></a><a class="link" href="#_module_string">7.1.7. Module "string"</a></h4>
<div class="paragraph">
<p>Module activation:<br>
<code>&#160;&#160;&#160;&#160;BUILTIN "string"</code></p>
</div>
<div class="paragraph">
<p>This module provides functions for working with strings.</p>
</div>
<div class="paragraph">
<p>Currently available functions:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#_strJoin">strJoin()</a></p>
</li>
<li>
<p><a href="#_strSub">strSub()</a></p>
</li>
<li>
<p><a href="#_strSplit">strSplit()</a></p>
</li>
<li>
<p><a href="#_strTrim">strTrim()</a></p>
</li>
<li>
<p><a href="#_strStartsWith">strStartsWith()</a></p>
</li>
<li>
<p><a href="#_strEndsWith">strEndsWith()</a></p>
</li>
<li>
<p><a href="#_strUpper">strUpper()</a></p>
</li>
<li>
<p><a href="#_strLower">strLower()</a></p>
</li>
</ul>
</div>
<hr>
<div class="sect4">
<h5 id="_strjoin"><a class="anchor" href="#_strjoin"></a><a class="link" href="#_strjoin">strJoin()</a></h5>
<div class="paragraph">
@@ -3589,7 +3945,81 @@ Iterators built on custom data-sources can provide additional named operators, d
</div>
</div>
<div class="sect2">
<h3 id="_iterator_over_custom_data_sources"><a class="anchor" href="#_iterator_over_custom_data_sources"></a><a class="link" href="#_iterator_over_custom_data_sources">8.2. Iterator over custom data-sources</a></h3>
<h3 id="_infixed_operators_on_iterators"><a class="anchor" href="#_infixed_operators_on_iterators"></a><a class="link" href="#_infixed_operators_on_iterators">8.2. Infixed operators on iterators</a></h3>
<div class="paragraph">
<p>There are also some infixed operators that can be used with iterators. They are defined as follows.</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#_cat">cat operator</a></p>
</li>
<li>
<p><a href="#_diget">digest operator</a></p>
</li>
<li>
<p><a href="#_filter">filter operator</a></p>
</li>
<li>
<p><a href="#_groupby">groupby operator</a></p>
</li>
<li>
<p><a href="#_map">map operator</a></p>
</li>
</ul>
</div>
<hr>
<div class="sect3">
<h4 id="_cat_operator"><a class="anchor" href="#_cat_operator"></a><a class="link" href="#_cat_operator">8.2.1. <code class="blue">cat</code> operator</a></h4>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;iterable&gt; cat &lt;iterable&gt; &#8594; &lt;iterator&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">cat</code> operator takes two iterators or iterables and returns a new iterator that produces the elements of the first iterator followed by the elements of the second iterator.</p>
</div>
</div>
<div class="sect3">
<h4 id="_filter_operator"><a class="anchor" href="#_filter_operator"></a><a class="link" href="#_filter_operator">8.2.2. <code class="blue">filter</code> operator</a></h4>
<div class="paragraph">
<div class="title">Examples</div>
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;iterable&gt; filter &lt;expr&gt; &#8594; &lt;iterator&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">filter</code> applies a boolean expression to each element of the iterator and returns a list of the elements for which the expression evaluates to true.</p>
</div>
</div>
<div class="sect3">
<h4 id="_groupby_operator"><a class="anchor" href="#_groupby_operator"></a><a class="link" href="#_groupby_operator">8.2.3. <code class="blue">groupby</code> operator</a></h4>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;dict&gt; groupby &lt;key&gt; &#8594; &lt;dict&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">groupby</code> operator groups the elements of the iterator based on the value of a specified expression and returns a dictionary where the keys are the group values and the values are lists of the elements in each group.</p>
</div>
</div>
<div class="sect3">
<h4 id="_map_operator"><a class="anchor" href="#_map_operator"></a><a class="link" href="#_map_operator">8.2.4. <code class="blue">map</code> operator</a></h4>
<div class="paragraph">
<p>Syntax:<br>
<code>&#160;&#160;&#160;&#160;&lt;iterable&gt; map &lt;expr&gt; &#8594; &lt;list&gt;&#160;&#160;&#160;&#160;</code></p>
</div>
<div class="paragraph">
<p><code class="blue">map</code> operator iterates over the elements of the iterator and evaluates the expressions provided on the right side for each element. Its result is a list of the computed values of the that expression. The current element of the iterator is available in the expression as the variable <code>$_</code>.</p>
</div>
<div class="paragraph">
<div class="title">Example: using the <code class="blue">map</code> operator</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">it = $(["one", "two", "three"])</code><br>
<code class="green">$(#3)</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">it map $_ + "!"</code><br>
<code class="green">["one!", "two!", "three!"]</code></p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_iterator_over_custom_data_sources"><a class="anchor" href="#_iterator_over_custom_data_sources"></a><a class="link" href="#_iterator_over_custom_data_sources">8.3. Iterator over custom data-sources</a></h3>
<div class="paragraph">
<p>It is possible to create iterators over custom data-sources by defining a dictionary that has the key <code>next</code> whose value is a function that returns the next element of the collection and updates the state of the iterator. The syntax for creating an iterator over a custom data-source is as follows.</p>
</div>
@@ -3610,7 +4040,7 @@ Iterators built on custom data-sources can provide additional named operators, d
</div>
<div id="footer">
<div id="footer-text">
Last updated 2026-04-21 06:35:14 +0200
Last updated 2026-05-12 16:25:27 +0200
</div>
</div>
</body>
+9 -11
View File
@@ -42,12 +42,16 @@ func ImportInContextByGlobPattern(ctx kern.ExprContext, pattern string) (count i
return
}
func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
func fixCtrlVar(name string) string {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
return name
}
func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
name = fixCtrlVar(name)
currentValue, _ = globalCtx.GetVar(name)
globalCtx.SetVar(name, newValue)
}
@@ -56,18 +60,14 @@ func GlobalCtrlSet(ctx kern.ExprContext, name string, newValue any) (currentValu
func GlobalCtrlGet(ctx kern.ExprContext, name string) (currentValue any) {
if globalCtx := ctx.GetGlobal(); globalCtx != nil {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
name = fixCtrlVar(name)
currentValue, _ = globalCtx.GetVar(name)
}
return currentValue
}
func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
name = fixCtrlVar(name)
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool)
}
@@ -77,9 +77,7 @@ func CtrlEnable(ctx kern.ExprContext, name string) (currentStatus bool) {
}
func CtrlDisable(ctx kern.ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
name = fixCtrlVar(name)
if v, exists := ctx.GetVar(name); exists && kern.IsBool(v) {
currentStatus, _ = v.(bool)
}
+4
View File
@@ -20,6 +20,10 @@ type IntIterator struct {
step int64
}
func NewIntIteratorA(args ...any) (it *IntIterator, err error) {
return NewIntIterator(args)
}
func NewIntIterator(args []any) (it *IntIterator, err error) {
var argc int = 0
if args != nil {
+8 -6
View File
@@ -19,6 +19,8 @@ func NewIterator(ctx kern.ExprContext, value any, ops []*scan.Term) (it kern.Ite
switch v := value.(type) {
case *kern.ListType:
it = NewListIterator(v, nil)
case *kern.LinkedList:
it = NewLinkedListIterator(v, nil)
case *kern.DictType:
it, err = NewDictIterator(v, nil)
case []any:
@@ -36,9 +38,9 @@ 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)
}
// 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)
// }
+30
View File
@@ -0,0 +1,30 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// clone-value.go
package kern
func Clone(v any) (c any) {
if v == nil {
return
}
switch unboxed := v.(type) {
case int64:
c = unboxed
case float64:
c = unboxed
case string:
c = unboxed
case bool:
c = unboxed
case *ListType:
c = unboxed.Clone()
case *DictType:
c = unboxed.Clone()
case *LinkedList:
c = unboxed.Clone()
default:
c = v
}
return
}
+1
View File
@@ -20,4 +20,5 @@ const (
TypeDict = "dict"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
TypeLinkedList = "linked-list"
)
+4
View File
@@ -20,6 +20,10 @@ func Equal(value1, value2 any) (equal bool) {
d1 := value1.(*DictType)
d2 := value2.(*DictType)
equal = d1.Equals(*d2)
} else if IsLinkedList(value1) && IsLinkedList(value2) {
ll1 := value1.(*LinkedList)
ll2 := value2.(*LinkedList)
equal = ll1.Equals(ll2)
} else if IsInteger(value1) && IsInteger(value2) {
equal = value1.(int64) == value2.(int64)
} else if IsString(value1) && IsString(value2) {
+14 -8
View File
@@ -10,6 +10,8 @@ import (
"strings"
)
const DictTypeName = "dict"
type DictType map[any]any
func IsDict(v any) (ok bool) {
@@ -74,9 +76,11 @@ func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
sb.WriteString(nest)
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
sb.WriteByte('"')
sb.WriteString(key)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", name))
fmt.Fprintf(sb, "%v", name)
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
@@ -84,7 +88,7 @@ func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
fmt.Fprintf(sb, "%v", value)
}
}
sb.WriteByte('\n')
@@ -108,9 +112,11 @@ func (dict *DictType) ToString(opt FmtOpt) string {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", key))
fmt.Fprintf(&sb, "%v", key)
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
@@ -118,7 +124,7 @@ func (dict *DictType) ToString(opt FmtOpt) string {
} else if t, ok := value.(Term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
fmt.Fprintf(&sb, "%#v", value)
}
}
sb.WriteByte('}')
@@ -131,7 +137,7 @@ func (dict *DictType) String() string {
}
func (dict *DictType) TypeName() string {
return "dict"
return DictTypeName
}
func (dict *DictType) HasKey(target any) (ok bool) {
@@ -155,7 +161,7 @@ func (dict *DictType) GetItem(key any) (value any, exists bool) {
func (dict *DictType) Clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
(*c)[k] = Clone(v)
}
return
}
+32
View File
@@ -28,4 +28,36 @@ type ExprContext interface {
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)
ToDict() (dict *DictType)
ToString(opt FmtOpt) string
}
func ContextToDict(ctx ExprContext) (dict *DictType) {
var keys []string
// Variables
keys = ctx.EnumVars(nil)
vars := MakeDict()
for _, key := range keys {
value, _ := ctx.GetVar(key)
vars.SetItem(key, value)
}
// Functions
keys = ctx.EnumFuncs(func(name string) bool { return true })
funcs := MakeDict()
for _, key := range keys {
funcInfo, _ := ctx.GetFuncInfo(key)
funcs.SetItem(key, funcInfo)
}
dict = MakeDict()
dict.SetItem("vars", vars)
dict.SetItem("funcs", funcs)
return
}
func ContextToString(ctx ExprContext, opt FmtOpt) string {
dict := ctx.ToDict()
return dict.ToString(opt)
}
+16 -1
View File
@@ -4,7 +4,10 @@
// formatter.go
package kern
import "fmt"
import (
"fmt"
"strings"
)
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
@@ -46,6 +49,18 @@ type Formatter interface {
ToString(options FmtOpt) string
}
func Format(sb *strings.Builder, item any, opt FmtOpt) {
if s, ok := item.(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else {
fmt.Fprintf(sb, "%v", item)
}
}
func GetFormatted(v any, opt FmtOpt) (text string) {
if v == nil {
text = "(nil)"
+130
View File
@@ -0,0 +1,130 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// linked-list-type.go
package kern
import (
"strings"
)
const LinkedListTypeName = "lisked-list"
const MaxUint64Allowed = uint64(9_223_372_036_854_775_807)
func IsLinkedList(v any) (ok bool) {
_, ok = v.(*LinkedList)
return ok
}
func NewLinkedListA(listAny ...any) (list *LinkedList) {
if listAny == nil {
listAny = []any{}
}
list = NewLinkedList()
for _, item := range listAny {
list.PushBack(FixAnyNumber(item))
}
return
}
func LinkedListFromStrings(stringList []string) (list *LinkedList) {
list = NewLinkedList()
for _, s := range stringList {
list.PushBack(s)
}
return
}
func (ls *LinkedList) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteString("[<")
if ls.Len() > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
}
i := 0
for item := ls.FirstNode(); item != nil; item = item.Next() {
if i > 0 {
if flags&MultiLine != 0 {
sb.WriteString(",\n")
sb.WriteString(nest)
} else {
sb.WriteString(", ")
}
}
// data := item.Data()
// if s, ok := data.(string); ok {
// sb.WriteByte('"')
// sb.WriteString(s)
// sb.WriteByte('"')
// } else if formatter, ok := data.(Formatter); ok {
// sb.WriteString(formatter.ToString(innerOpt))
// } else {
// fmt.Fprintf(&sb, "%v", data)
// }
Format(&sb, item.Data(), innerOpt)
i++
}
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
}
sb.WriteString(">]")
s = sb.String()
if flags&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
return
}
func (ls *LinkedList) String() string {
return ls.ToString(0)
}
func (ls *LinkedList) TypeName() string {
return LinkedListTypeName
}
// func (ls *LinkedList) Contains(t *ListType) (answer bool) {
// if len(*ls) >= len(*t) {
// answer = true
// for _, item := range *t {
// if answer = ls.IndexDeepSameCmp(item) >= 0; !answer {
// break
// }
// }
// }
// return
// }
func (ls1 *LinkedList) Equals(ls2 *LinkedList) (answer bool) {
if ls2 != nil && ls1.Len() == ls2.Len() {
answer = true
i2 := ls2.FirstNode()
for i1 := ls1.FirstNode(); i1 != nil; i1 = i1.Next() {
if !Equal(i1.Data(), i2.Data()) {
answer = false
break
}
i2 = i2.Next()
}
}
return
}
func (ls1 *LinkedList) Clone() (ls2 *LinkedList) {
ls2 = NewLinkedListA()
for i1 := ls1.FirstNode(); i1 != nil; i1 = i1.Next() {
ls2.PushBack(Clone(i1.Data()))
}
return
}
+24 -13
View File
@@ -19,7 +19,7 @@ func (self *ListNode) Data() any {
}
type LinkedList struct {
count uint32
count int
first *ListNode
last *ListNode
}
@@ -29,7 +29,7 @@ func NewLinkedList() (list *LinkedList) {
return
}
func (ls *LinkedList) Len() uint32 {
func (ls *LinkedList) Len() int {
return ls.count
}
@@ -121,7 +121,7 @@ func (ls *LinkedList) Last() (data interface{}, err error) {
return
}
func (ls *LinkedList) NodeAt(index uint32) (node *ListNode, err error) {
func (ls *LinkedList) NodeAt(index int) (node *ListNode, err error) {
if ls.count == 0 {
err = errorListEmpty()
} else if index > ls.count {
@@ -130,7 +130,7 @@ func (ls *LinkedList) NodeAt(index uint32) (node *ListNode, err error) {
node = ls.last
} else {
current := ls.first
for pos := uint32(0); pos < index; pos++ {
for range index {
current = current.next
}
node = current
@@ -138,7 +138,7 @@ func (ls *LinkedList) NodeAt(index uint32) (node *ListNode, err error) {
return
}
func (ls *LinkedList) At(index uint32) (data interface{}, err error) {
func (ls *LinkedList) At(index int) (data interface{}, err error) {
node, err := ls.NodeAt(index)
if err == nil {
data = node.data
@@ -146,12 +146,12 @@ func (ls *LinkedList) At(index uint32) (data interface{}, err error) {
return
}
func (ls *LinkedList) Insert(index uint32, data interface{}) *ListNode {
func (ls *LinkedList) Insert(index int, data any) *ListNode {
var prev *ListNode
prev = nil
current := ls.first
for pos := uint32(0); current != nil && pos < index; pos++ {
for pos := 0; current != nil && pos < index; pos++ {
prev = current
current = current.next
}
@@ -178,10 +178,21 @@ func (ls *LinkedList) PushBackStringArray(items []string) {
}
}
// type TraverseOperator func(index uint32, elem interface{}, userData interface{}) (err error)
func (ls *LinkedList) Sub(start, end int) (subList *LinkedList) {
subList = NewLinkedList()
if node, err := ls.NodeAt(start); err == nil {
for i := start; i < end && node != nil; i++ {
subList.PushBack(node.data)
node = node.next
}
}
return
}
// type TraverseOperator func(index int, elem interface{}, userData interface{}) (err error)
// func (self *LinkedList) Traverse(op TraverseOperator, user_data interface{}) (err error) {
// index := uint32(0)
// index := int(0)
// for current := self.first; current != nil; current = current.next {
// err = op(index, current.data, user_data)
// if err != nil {
@@ -193,7 +204,7 @@ func (ls *LinkedList) PushBackStringArray(items []string) {
// }
// func (self *LinkedList) Traverse2(observer Observer, abortOnError bool) (err error) {
// index := uint32(0)
// index := int(0)
// for current := self.first; current != nil; current = current.next {
// err = observer.Observe(current, index)
// if err != nil && abortOnError {
@@ -227,9 +238,9 @@ func (ls *LinkedList) FindNext(eqFunc EqualFunc, startNode *ListNode) (targetNod
}
// type DataFeeder func(user_data interface{}) interface{}
// type NodeObserver func(node *ListNode, index uint32, userData interface{})
// type NodeObserver func(node *ListNode, index int, userData interface{})
// func (self *LinkedList) FeedTail(feeder DataFeeder, feederUserData interface{}, observer NodeObserver, observerUserData interface{}) (count uint32) {
// func (self *LinkedList) FeedTail(feeder DataFeeder, feederUserData interface{}, observer NodeObserver, observerUserData interface{}) (count int) {
// count = 0
// for item := feeder(feederUserData); item != nil; item = feeder(feederUserData) {
// // fmt.Println("Item", count, item)
@@ -242,7 +253,7 @@ func (ls *LinkedList) FindNext(eqFunc EqualFunc, startNode *ListNode) (targetNod
// return
// }
// func (self *LinkedList) FeedTail2(feeder Feeder, observer Observer, abortOnError bool) (count uint32, err error) {
// func (self *LinkedList) FeedTail2(feeder Feeder, observer Observer, abortOnError bool) (count int, err error) {
// count = 0
// // item := feeder.Next()
// for item, err1 := feeder.Next(); item != nil; item, err1 = feeder.Next() {
+24 -23
View File
@@ -73,15 +73,7 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
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))
}
Format(&sb, item, innerOpt)
}
if flags&MultiLine != 0 {
sb.WriteByte('\n')
@@ -104,11 +96,11 @@ func (ls *ListType) TypeName() string {
return "list"
}
func (dict *ListType) Contains(t *ListType) (answer bool) {
if len(*dict) >= len(*t) {
func (ls *ListType) Contains(t *ListType) (answer bool) {
if len(*ls) >= len(*t) {
answer = true
for _, item := range *t {
if answer = dict.IndexDeepSameCmp(item) >= 0; !answer {
if answer = ls.IndexDeepSameCmp(item) >= 0; !answer {
break
}
}
@@ -116,10 +108,10 @@ func (dict *ListType) Contains(t *ListType) (answer bool) {
return
}
func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls1) == len(ls2) {
func (ls *ListType) Equals(ls2 ListType) (answer bool) {
if ls2 != nil && len(*ls) == len(ls2) {
answer = true
for index, i1 := range *ls1 {
for index, i1 := range *ls {
// if !reflect.DeepEqual(i1, ls2[index]) {
// answer = false
// break
@@ -133,11 +125,20 @@ func (ls1 *ListType) Equals(ls2 ListType) (answer bool) {
return
}
func (dict *ListType) IndexDeepSameCmp(target any) (index int) {
func (ls1 *ListType) Clone() (ls2 *ListType) {
ls := make(ListType, len(*ls1))
for i, item := range *ls1 {
ls[i] = Clone(item)
}
ls2 = &ls
return
}
func (ls *ListType) IndexDeepSameCmp(target any) (index int) {
var eq bool
var err error
index = -1
for i, item := range *dict {
for i, item := range *ls {
if eq, err = deepSame(item, target, SameContent); err != nil {
break
} else if eq {
@@ -188,15 +189,15 @@ func deepSame(a, b any, deepCmp DeepFuncTemplate) (eq bool, err error) {
return
}
func (dict *ListType) SetItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*dict)) {
(*dict)[index] = value
func (ls *ListType) SetItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*ls)) {
(*ls)[index] = value
} else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*dict)-1)
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*ls)-1)
}
return
}
func (dict *ListType) AppendItem(value any) {
*dict = append(*dict, value)
func (ls *ListType) AppendItem(value any) {
*ls = append(*ls, value)
}
+32
View File
@@ -65,6 +65,38 @@ func AnyInteger(v any) (i int64, ok bool) {
return
}
func FixAnyNumber(v any) (fixed any) {
switch unboxed := v.(type) {
case int:
fixed = int64(unboxed)
case int8:
fixed = int64(unboxed)
case int16:
fixed = int64(unboxed)
case int32:
fixed = int64(unboxed)
case uint:
fixed = int64(unboxed)
case uint8:
fixed = int64(unboxed)
case uint16:
fixed = int64(unboxed)
case uint32:
fixed = int64(unboxed)
case uint64:
if unboxed <= MaxUint64Allowed {
fixed = int64(unboxed)
} else {
fixed = float64(unboxed)
}
case float32:
fixed = float64(unboxed)
default:
fixed = v
}
return
}
func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
+102
View File
@@ -0,0 +1,102 @@
// 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 LinkedListIterator struct {
a *kern.LinkedList
count int64
index int64
current *kern.ListNode
}
func NewLinkedListIterator(list *kern.LinkedList, args []any) (it *LinkedListIterator) {
it = &LinkedListIterator{a: list, count: 0, index: -1, current: list.FirstNode()}
return
}
func (it *LinkedListIterator) String() string {
var l = int64(0)
if it.a != nil {
l = int64(it.a.Len())
}
return fmt.Sprintf("$([<#%d>])", l)
}
func (it *LinkedListIterator) TypeName() string {
return "LinkedListIterator"
}
func (it *LinkedListIterator) HasOperation(name string) bool {
yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName}, name)
return yes
}
func (it *LinkedListIterator) 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 *LinkedListIterator) Current() (item any, err error) {
if it.current != nil {
item = it.current.Data()
} else {
err = io.EOF
}
return
}
func (it *LinkedListIterator) Next() (item any, err error) {
if it.current != nil {
item = it.current.Data()
it.current = it.current.Next()
it.index++
it.count++
} else {
err = io.EOF
}
return
}
func (it *LinkedListIterator) Index() int64 {
return it.index
}
func (it *LinkedListIterator) Count() int64 {
return it.count
}
func (it *LinkedListIterator) Reset() error {
it.current = it.a.FirstNode()
it.index = -1
it.count = 0
return nil
}
func (it *LinkedListIterator) Clean() error {
return nil
}
+8 -1
View File
@@ -30,7 +30,14 @@ func evalDict(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if param, err = tree.Compute(ctx); err != nil {
break
}
items[key] = param
var keyValue any
if keyValue, err = (key.(*scan.Term)).Compute(ctx); err == nil {
if kern.IsInteger(keyValue) || kern.IsString(keyValue) {
items[keyValue] = param
} else {
err = key.(*scan.Term).Errorf("dict key can be integer or string, got %s", kern.TypeName(keyValue))
}
}
}
if err == nil {
v = &items
+5
View File
@@ -137,6 +137,11 @@ func evalIterator(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v = NewListIterator(list, args)
}
} else if list, ok := firstChildValue.(*kern.LinkedList); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, nil); err == nil {
v = NewLinkedListIterator(list, args)
}
} else if intVal, ok := firstChildValue.(int64); ok {
var args []any
if args, err = evalSiblings(ctx, opTerm.Children, intVal); err == nil {
+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 newLinkedListTermA(args ...*scan.Term) *scan.Term {
// return newLinkedListTerm(0, 0, args)
// }
func newLinkedListTerm(row, col int, args []*scan.Term) *scan.Term {
return &scan.Term{
Tk: *scan.NewValueToken(row, col, scan.SymLinkedList, "[<>]", args),
Parent: nil,
Children: nil,
Position: scan.PosLeaf,
Priority: scan.PriValue,
EvalFunc: evalLinkedList,
}
}
// -------- list func
func evalLinkedList(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
list, _ := opTerm.Value().([]*scan.Term)
items := kern.NewLinkedList()
for _, tree := range list {
var param any
if param, err = tree.Compute(ctx); err != nil {
break
}
items.PushBack(param)
}
if err == nil {
v = items
}
return
}
+66 -6
View File
@@ -22,6 +22,26 @@ func newAssignTerm(tk *scan.Token) (inst *scan.Term) {
}
}
func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v, err = generalEvalAssign(ctx, opTerm, false)
return
}
func newDeepCopyAssignTerm(tk *scan.Token) (inst *scan.Term) {
return &scan.Term{
Tk: *tk,
Children: make([]*scan.Term, 0, 2),
Position: scan.PosInfix,
Priority: scan.PriAssign,
EvalFunc: evalDeepCopyAssign,
}
}
func evalDeepCopyAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
v, err = generalEvalAssign(ctx, opTerm, true)
return
}
func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *scan.Term, value any) (err error) {
var collectionValue, keyListValue, keyValue any
var keyList *kern.ListType
@@ -57,23 +77,62 @@ func assignCollectionItem(ctx kern.ExprContext, collectionTerm, keyListTerm *sca
return
}
func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any) (err error) {
func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any, deepCopy bool) (err error) {
if leftTerm.Symbol() == scan.SymIndex {
err = assignCollectionItem(ctx, leftTerm.Children[0], leftTerm.Children[1], v)
} else {
if deepCopy {
v = kern.Clone(v)
}
ctx.UnsafeSetVar(leftTerm.Source(), v)
}
return
}
func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
func evalAssignDictItem(ctx kern.ExprContext, dotTerm *scan.Term, valueTerm kern.Term) (v any, err error) {
var ok bool
var dictAny, dotKey any
var dict *kern.DictType
if err = dotTerm.CheckOperands(); err != nil {
return
}
dotLeftTerm := dotTerm.GetChild(0)
if dictAny, err = dotLeftTerm.Compute(ctx); err != nil {
return
}
if dict, ok = dictAny.(*kern.DictType); !ok {
err = dotTerm.Tk.ErrorExpectedGot(kern.DictTypeName)
return
}
dotRightTerm := dotTerm.Children[1]
dotRightSym := dotRightTerm.Symbol()
if dotRightSym == scan.SymVariable || dotRightSym == scan.SymString {
dotKey = util.UnquoteString(dotRightTerm.Source())
} else if dotKey, err = dotRightTerm.Compute(ctx); err != nil {
return
}
if v, err = valueTerm.Compute(ctx); err != nil {
return
}
dict.SetItem(dotKey, v)
return
}
func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (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 {
if leftSym == scan.SymDot {
return evalAssignDictItem(ctx, opTerm.Children[0], opTerm.GetChild(1))
} else 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
}
@@ -93,10 +152,10 @@ func evalAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
err = opTerm.Errorf("unknown function %s()", rightChild.Source())
}
} else {
err = assignValue(ctx, leftTerm, v)
err = assignValue(ctx, leftTerm, v, deepCopy)
}
} else {
err = assignValue(ctx, leftTerm, v)
err = assignValue(ctx, leftTerm, v, deepCopy)
}
}
if err != nil {
@@ -203,7 +262,7 @@ func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
err = opTerm.Errorf("unsupported assign operator %q", opTerm.Source())
}
if err == nil {
err = assignValue(ctx, leftTerm, v)
err = assignValue(ctx, leftTerm, v, false)
}
}
}
@@ -213,6 +272,7 @@ func evalOpAssign(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
// init
func init() {
scan.RegisterTermConstructor(scan.SymEqual, newAssignTerm)
scan.RegisterTermConstructor(scan.SymColonEqual, newDeepCopyAssignTerm)
scan.RegisterTermConstructor(scan.SymPlusEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymMinusEqual, newOpAssignTerm)
scan.RegisterTermConstructor(scan.SymStarEqual, newOpAssignTerm)
+8 -22
View File
@@ -40,36 +40,22 @@ func evalContextValue(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error
}
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
}
v = sourceCtx.ToDict()
} else if childValue != nil {
if it, ok := childValue.(kern.Iterator); ok {
it, ok := childValue.(kern.Iterator)
if !ok {
it, err = NewIterator(ctx, childValue, nil)
}
if err == nil {
var item any
values := kern.NewListA()
values := kern.NewLinkedListA()
for item, err = it.Next(); err == nil; item, err = it.Next() {
values.AppendItem(item)
values.PushBack(item)
}
if err == io.EOF {
err = nil
v = values
}
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
}
} else {
err = opTerm.ErrIncompatiblePrefixPostfixType(childValue)
+46 -16
View File
@@ -7,6 +7,7 @@ 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"
)
// -------- dot term
@@ -34,7 +35,7 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
switch unboxedValue := leftValue.(type) {
case kern.ExtIterator:
if indexTerm.Tk.Sym == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ {
if indexTerm.Symbol() == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ {
opName := indexTerm.Source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, map[string]any{})
@@ -46,21 +47,22 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
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)
}
}
// var ok bool
// s := opTerm.Children[1].Symbol()
// 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)
// }
// }
v, err = dotGetDictItemValue(ctx, unboxedValue, opTerm.Children[1])
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))
@@ -69,6 +71,34 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return
}
func dotGetDictItemValue(ctx kern.ExprContext, d *kern.DictType, rightTerm *scan.Term) (v any, err error) {
var ok bool
var rightValue any
s := rightTerm.Symbol()
if s == scan.SymVariable || s == scan.SymString {
// src := rightTerm.Source()
// if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
// src = src[1 : len(src)-1]
// }
src := util.UnquoteString(rightTerm.Source())
if v, ok = d.GetItem(src); !ok {
err = errDictKeyNotFound(rightTerm, src)
}
} else if rightValue, err = rightTerm.Compute(ctx); err == nil {
if v, ok = d.GetItem(rightValue); !ok {
err = errDictKeyNotFound(rightTerm, rightValue)
}
}
return
}
func errDictKeyNotFound(term *scan.Term, key any) error {
if s, ok := key.(string); ok {
return term.Errorf("key %q not found", s)
}
return term.Errorf("key %v not found", key)
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDot, newDotTerm)
+10
View File
@@ -89,6 +89,11 @@ func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
v = (*unboxedValue)[index]
}
case *kern.LinkedList:
var index int
if index, err = verifyIndex(indexTerm, indexList, unboxedValue.Len()); err == nil {
v, err = unboxedValue.At(index)
}
case string:
var index int
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
@@ -107,6 +112,11 @@ func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
sublist := kern.ListType((*unboxedValue)[start:end])
v = &sublist
}
case *kern.LinkedList:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, unboxedValue.Len()); err == nil {
v = unboxedValue.Sub(start, end)
}
case string:
var start, end int
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
+2
View File
@@ -37,6 +37,8 @@ func evalLength(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
} else if kern.IsDict(childValue) {
m, _ := childValue.(*kern.DictType)
v = int64(len(*m))
} else if lls, ok := childValue.(*kern.LinkedList); ok {
v = int64(lls.Len())
} else if it, ok := childValue.(kern.Iterator); ok {
v = int64(it.Count())
// if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
+60 -30
View File
@@ -6,6 +6,8 @@ package expr
import (
"errors"
"fmt"
"slices"
"git.portale-stac.it/go-pkg/expr/scan"
"golang.org/x/exp/constraints"
@@ -163,16 +165,16 @@ func paramAlreadyDefined(args []*scan.Term, param *scan.Term) (position int) {
return
}
func (parser *parser) parseList(scanner *scan.Scanner, ctx parserContext) (listTerm *scan.Term, err error) {
func (parser *parser) parseList(scanner *scan.Scanner, ctx parserContext, termSym scan.Symbol) (listTerm *scan.Term, err error) {
r, c := scanner.LastPos()
args := make([]*scan.Term, 0)
lastSym := scan.SymUnknown
itemExpected := false
itemCtx := remFlags(ctx, allowIndex)
for lastSym != scan.SymClosedSquare && lastSym != scan.SymEos {
for lastSym != termSym && lastSym != scan.SymEos {
zeroRequired := scanner.Current().Sym == scan.SymColon
var itemTree *scan.Ast
if itemTree, err = parser.parseItem(scanner, itemCtx, scan.SymComma, scan.SymClosedSquare); err == nil {
if itemTree, err = parser.parseItem(scanner, itemCtx, scan.SymComma, termSym); err == nil {
root := itemTree.Root()
if root != nil {
if hasFlag(ctx, allowIndex) && root.Symbol() == scan.SymColon {
@@ -211,10 +213,14 @@ func (parser *parser) parseList(scanner *scan.Scanner, ctx parserContext) (listT
}
}
if err == nil {
if lastSym != scan.SymClosedSquare {
if lastSym != termSym {
err = scanner.Previous().ErrorExpectedGot("]")
} else {
} else if termSym == scan.SymClosedSquare {
listTerm = newListTerm(r, c, args)
} else if termSym == scan.SymGreaterClosedSquare {
listTerm = newLinkedListTerm(r, c, args)
} else {
err = fmt.Errorf("[%d:%d] unknown list type", r, c)
}
}
return
@@ -251,24 +257,41 @@ func (parser *parser) parseIterDef(scanner *scan.Scanner, ctx parserContext) (su
return
}
func (parser *parser) parseDictKey(scanner *scan.Scanner) (key any, err error) {
tk := parser.Next(scanner)
if tk.Sym == scan.SymError {
err = tk.Error()
return
// func (parser *parser) parseDictKey(scanner *scan.Scanner) (key any, err error) {
// tk := parser.Next(scanner)
// if tk.Sym == scan.SymError {
// err = tk.Error()
// return
// }
// if tk.Sym == scan.SymClosedBrace || tk.Sym == scan.SymEos {
// return
// }
// if tk.Sym == scan.SymInteger || tk.Sym == scan.SymString || tk.Sym == scan.SymIdentifier {
// tkSep := parser.Next(scanner)
// if tkSep.Sym != scan.SymColon {
// err = tkSep.ErrorExpectedGot(":")
// } else {
// key = tk.Value
// }
// } else {
// err = tk.ErrorExpectedGot("dictionary-key or }")
// }
// return
// }
func (parser *parser) parseDictKey(scanner *scan.Scanner) (key *scan.Term, err error) {
var keyTree *scan.Ast
if keyTree, err = parser.parseItem(scanner, parserNoFlags, scan.SymColon, scan.SymClosedBrace); err == nil {
key = keyTree.Root()
tkSep := scanner.Previous()
sym := tkSep.Sym
if sym == scan.SymClosedBrace || sym == scan.SymEos {
if key != nil {
err = tkSep.ErrorExpectedGot(":")
}
if tk.Sym == scan.SymClosedBrace || tk.Sym == scan.SymEos {
return
}
if tk.Sym == scan.SymInteger || tk.Sym == scan.SymString {
tkSep := parser.Next(scanner)
if tkSep.Sym != scan.SymColon {
} else if sym != scan.SymColon {
err = tkSep.ErrorExpectedGot(":")
} else {
key = tk.Value
}
} else {
err = tk.ErrorExpectedGot("dictionary-key or }")
}
return
}
@@ -278,11 +301,11 @@ func (parser *parser) parseDictionary(scanner *scan.Scanner, ctx parserContext)
lastSym := scan.SymUnknown
itemExpected := false
for lastSym != scan.SymClosedBrace && lastSym != scan.SymEos {
var subTree *scan.Ast
var valueTree *scan.Ast
var key any
if key, err = parser.parseDictKey(scanner); err != nil {
break
} else if key == nil {
} else if key.(*scan.Term) == nil {
tk := scanner.Previous()
lastSym = tk.Sym
if itemExpected {
@@ -290,9 +313,9 @@ func (parser *parser) parseDictionary(scanner *scan.Scanner, ctx parserContext)
}
break
}
if subTree, err = parser.parseItem(scanner, ctx, scan.SymComma, scan.SymClosedBrace); err == nil {
if subTree.Root() != nil {
args[key] = subTree.Root()
if valueTree, err = parser.parseItem(scanner, ctx, scan.SymComma, scan.SymClosedBrace); err == nil {
if valueTree.Root() != nil {
args[key] = valueTree.Root()
} else /*if key != nil*/ {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("dictionary-value")
@@ -326,7 +349,7 @@ func (parser *parser) parseSelectorCase(scanner *scan.Scanner, ctx parserContext
err = tk.Errorf("case list in default clause")
return
}
if filterList, err = parser.parseList(scanner, remFlags(ctx, allowIndex)); err != nil {
if filterList, err = parser.parseList(scanner, remFlags(ctx, allowIndex), scan.SymClosedSquare); err != nil {
return
}
tk = parser.Next(scanner)
@@ -401,7 +424,8 @@ func couldBeACollection(t *scan.Term) bool {
if t != nil {
sym = t.Symbol()
}
return sym == scan.SymList || sym == scan.SymString || sym == scan.SymDict || sym == scan.SymExpression || sym == scan.SymVariable
// return sym == scan.SymList || sym == scan.SymString || sym == scan.SymDict || sym == scan.SymExpression || sym == scan.SymVariable
return slices.Contains([]scan.Symbol{scan.SymList, scan.SymLinkedList, scan.SymString, scan.SymDict, scan.SymExpression, scan.SymVariable, scan.SymIndex}, sym)
}
func listSubTree(tree *scan.Ast, listTerm *scan.Term, allowIndeces bool) (root *scan.Term, err error) {
@@ -444,7 +468,7 @@ func (parser *parser) parseGeneral(scanner *scan.Scanner, ctx parserContext, ter
tree = scan.NewAst()
firstToken := true
// lastSym := SymUnknown
for tk = parser.Next(scanner); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = parser.Next(scanner) {
for tk = parser.Next(scanner); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = parser.Next(scanner) {
// if tk.Sym == SymComment {
// continue
// }
@@ -457,7 +481,7 @@ func (parser *parser) parseGeneral(scanner *scan.Scanner, ctx parserContext, ter
selectorTerm = nil
continue
} else {
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.Source())
err = tk.ErrorExpectedOneOfGot(termSymbols...)
break
}
}
@@ -489,7 +513,13 @@ func (parser *parser) parseGeneral(scanner *scan.Scanner, ctx parserContext, ter
case scan.SymOpenSquare:
var listTerm *scan.Term
newCtx := addFlagsCond(addFlags(ctx, squareContext), allowIndex, couldBeACollection(currentTerm))
if listTerm, err = parser.parseList(scanner, newCtx); err == nil {
if listTerm, err = parser.parseList(scanner, newCtx, scan.SymClosedSquare); err == nil {
currentTerm, err = listSubTree(tree, listTerm, hasFlag(newCtx, allowIndex))
}
case scan.SymOpenSquareLess:
var listTerm *scan.Term
newCtx := addFlagsCond(addFlags(ctx, listContext), allowIndex, false)
if listTerm, err = parser.parseList(scanner, newCtx, scan.SymGreaterClosedSquare); err == nil {
currentTerm, err = listSubTree(tree, listTerm, hasFlag(newCtx, allowIndex))
}
case scan.SymOpenBrace:
+1 -1
View File
@@ -98,7 +98,7 @@ func (ast *Ast) insert(tree, node *Term) (root *Term, err error) {
root = node
tree.SetParent(node)
} else {
err = node.Errorf("two adjacent operators: %q and %q", tree, node)
err = node.Errorf("two adjacent operators: %q and %q", tree, node.Source())
}
return
}
+8
View File
@@ -192,6 +192,8 @@ func (scanner *Scanner) fetchNextToken() (tk *Token) {
case ':':
if next, _ := scanner.peek(); next == ':' {
tk = scanner.moveOn(SymDoubleColon, ch, next)
} else if next == '=' {
tk = scanner.moveOn(SymColonEqual, ch, next)
} else {
tk = scanner.MakeToken(SymColon, ch)
}
@@ -313,6 +315,8 @@ func (scanner *Scanner) fetchNextToken() (tk *Token) {
} else {
tk = scanner.accept(SymDoubleGreater, ch, next)
}
} else if next == ']' {
tk = scanner.moveOn(SymGreaterClosedSquare, ch, next)
} else {
tk = scanner.MakeToken(SymGreater, ch)
}
@@ -342,7 +346,11 @@ func (scanner *Scanner) fetchNextToken() (tk *Token) {
case ')':
tk = scanner.MakeToken(SymClosedRound, ch)
case '[':
if next, _ := scanner.peek(); next == '<' {
tk = scanner.moveOn(SymOpenSquareLess, ch, next)
} else {
tk = scanner.MakeToken(SymOpenSquare, ch)
}
case ']':
tk = scanner.MakeToken(SymClosedSquare, ch)
case '{':
+45 -60
View File
@@ -64,47 +64,50 @@ func init() {
SymAt: {"@", SymClassOperator, PosPrefix}, // 28: '@'
SymUndescore: {"_", SymClassIdentifier, PosLeaf}, // 29: '_'
SymEqual: {"=", SymClassOperator, PosInfix}, // 30: '='
SymDoubleEqual: {"==", SymClassOperator, PosInfix}, // 31: '=='
SymLess: {"<", SymClassOperator, PosInfix}, // 32: '<'
SymLessOrEqual: {"<=", SymClassOperator, PosInfix}, // 33: '<='
SymGreater: {">", SymClassOperator, PosInfix}, // 34: '>'
SymGreaterOrEqual: {">=", SymClassOperator, PosInfix}, // 35: '>='
SymLessGreater: {"<>", SymClassOperator, PosInfix}, // 36: '<>'
SymNotEqual: {"!=", SymClassOperator, PosInfix}, // 37: '!='
SymDollar: {"$", SymClassOperator, PosPrefix}, // 38: '$'
SymHash: {"#", SymClassOperator, PosPrefix}, // 39: '#'
SymOpenRound: {"(", SymClassParenthesis, PosPrefix}, // 40: '('
SymClosedRound: {")", SymClassParenthesis, PosPostfix}, // 41: ')'
SymOpenSquare: {"[", SymClassParenthesis, PosPrefix}, // 42: '['
SymClosedSquare: {"]", SymClassParenthesis, PosPostfix}, // 43: ']'
SymOpenBrace: {"{", SymClassParenthesis, PosPrefix}, // 44: '{'
SymClosedBrace: {"}", SymClassParenthesis, PosPostfix}, // 45: '}'
SymTilde: {"~", SymClassOperator, PosPrefix}, // 46: '~'
SymDoubleQuestion: {"??", SymClassOperator, PosInfix}, // 47: '??'
SymQuestionEqual: {"?=", SymClassOperator, PosInfix}, // 48: '?='
SymQuestionExclam: {"?!", SymClassOperator, PosInfix}, // 49: '?!'
SymDoubleAt: {"@@", SymClassCommand, PosLeaf}, // 50: '@@'
SymDoubleColon: {"::", SymClassOperator, PosInfix}, // 51: '::'
SymDoubleGreater: {">>", SymClassOperator, PosInfix}, // 52: '>>'
SymDoubleLess: {"<<", SymClassOperator, PosInfix}, // 53: '<<'
SymCaret: {"^", SymClassOperator, PosInfix}, // 54: '^'
SymDollarRound: {"$(", SymClassOperator, PosPrefix}, // 55: '$('
SymOpenClosedRound: {"()", SymClassOperator, PosPostfix}, // 56: '()'
SymDoubleDollar: {"$$", SymClassCommand, PosLeaf}, // 57: '$$'
SymDoubleDot: {"..", SymClassOperator, PosInfix}, // 58: '..'
SymTripleDot: {"...", SymClassOperator, PosPostfix}, // 59: '...'
SymStarEqual: {"*=", SymClassOperator, PosInfix}, // 60: '*='
SymSlashEqual: {"/=", SymClassOperator, PosInfix}, // 61: '/='
SymPercEqual: {"%=", SymClassOperator, PosInfix}, // 62: '%='
SymDoubleLessEqual: {"<<=", SymClassOperator, PosInfix}, // 63: '<<='
SymDoubleGreaterEqual: {">>=", SymClassOperator, PosInfix}, // 64: '>>='
SymAmpersandEqual: {"&=", SymClassOperator, PosInfix}, // 65: '&='
SymVertBarEqual: {"|=", SymClassOperator, PosInfix}, // 65: '|='
SymCaretEqual: {"^=", SymClassOperator, PosInfix}, // 66: '^='
SymPlusGreater: {"+>", SymClassOperator, PosInfix}, // 67: '+>'
SymLessPlus: {"<+", SymClassOperator, PosInfix}, // 68: '<+'
SymPreInc: {"++", SymClassOperator, PosPrefix}, // : '++'
SymPreDec: {"--", SymClassOperator, PosPrefix}, // : '--'
SymColonEqual: {":=", SymClassOperator, PosInfix}, // 31: ':='
SymDoubleEqual: {"==", SymClassOperator, PosInfix}, // 32: '=='
SymLess: {"<", SymClassOperator, PosInfix}, // 33: '<'
SymLessOrEqual: {"<=", SymClassOperator, PosInfix}, // 34: '<='
SymGreater: {">", SymClassOperator, PosInfix}, // 35: '>'
SymGreaterOrEqual: {">=", SymClassOperator, PosInfix}, // 36: '>='
SymLessGreater: {"<>", SymClassOperator, PosInfix}, // 37: '<>'
SymNotEqual: {"!=", SymClassOperator, PosInfix}, // 38: '!='
SymDollar: {"$", SymClassOperator, PosPrefix}, // 39: '$'
SymHash: {"#", SymClassOperator, PosPrefix}, // 40: '#'
SymOpenRound: {"(", SymClassParenthesis, PosPrefix}, // 41: '('
SymClosedRound: {")", SymClassParenthesis, PosPostfix}, // 42: ')'
SymOpenSquare: {"[", SymClassParenthesis, PosPrefix}, // 43: '['
SymClosedSquare: {"]", SymClassParenthesis, PosPostfix}, // 44: ']'
SymOpenBrace: {"{", SymClassParenthesis, PosPrefix}, // 45: '{'
SymClosedBrace: {"}", SymClassParenthesis, PosPostfix}, // 46: '}'
SymTilde: {"~", SymClassOperator, PosPrefix}, // 47: '~'
SymDoubleQuestion: {"??", SymClassOperator, PosInfix}, // 48: '??'
SymQuestionEqual: {"?=", SymClassOperator, PosInfix}, // 49: '?='
SymQuestionExclam: {"?!", SymClassOperator, PosInfix}, // 50: '?!'
SymDoubleAt: {"@@", SymClassCommand, PosLeaf}, // 51: '@@'
SymDoubleColon: {"::", SymClassOperator, PosInfix}, // 52: '::'
SymDoubleGreater: {">>", SymClassOperator, PosInfix}, // 53: '>>'
SymDoubleLess: {"<<", SymClassOperator, PosInfix}, // 54: '<<'
SymCaret: {"^", SymClassOperator, PosInfix}, // 55: '^'
SymDollarRound: {"$(", SymClassOperator, PosPrefix}, // 56: '$('
SymOpenClosedRound: {"()", SymClassOperator, PosPostfix}, // 57: '()'
SymDoubleDollar: {"$$", SymClassCommand, PosLeaf}, // 58: '$$'
SymDoubleDot: {"..", SymClassOperator, PosInfix}, // 59: '..'
SymTripleDot: {"...", SymClassOperator, PosPostfix}, // 60: '...'
SymStarEqual: {"*=", SymClassOperator, PosInfix}, // 61: '*='
SymSlashEqual: {"/=", SymClassOperator, PosInfix}, // 62: '/='
SymPercEqual: {"%=", SymClassOperator, PosInfix}, // 63: '%='
SymDoubleLessEqual: {"<<=", SymClassOperator, PosInfix}, // 64: '<<='
SymDoubleGreaterEqual: {">>=", SymClassOperator, PosInfix}, // 65: '>>='
SymAmpersandEqual: {"&=", SymClassOperator, PosInfix}, // 66: '&='
SymVertBarEqual: {"|=", SymClassOperator, PosInfix}, // 67: '|='
SymCaretEqual: {"^=", SymClassOperator, PosInfix}, // 68: '^='
SymPlusGreater: {"+>", SymClassOperator, PosInfix}, // 69: '+>'
SymLessPlus: {"<+", SymClassOperator, PosInfix}, // 70: '<+'
SymPreInc: {"++", SymClassOperator, PosPrefix}, // 71: '++'
SymPreDec: {"--", SymClassOperator, PosPrefix}, // 72: '--'
SymOpenSquareLess: {"[<", SymClassOperator, PosPrefix}, // 97: '[<'
SymGreaterClosedSquare: {">]", SymClassOperator, PosPostfix}, // 98: '>]'
// SymChangeSign
// SymUnchangeSign
// SymIdentifier
@@ -122,6 +125,7 @@ func init() {
// SymFuncCall
// SymFuncDef
// SymList
// SymLinkedList
// SymDict
// SymIndex
// SymExpression
@@ -184,25 +188,6 @@ func StringEndsWithOperator(s string) bool {
return endingOperator(s) != SymNone
}
// func endingOperator(s string) (sym Symbol) {
// var matchLength = 0
// sym = SymNone
// lower := strings.TrimRight(strings.ToLower(s), " \t")
// for symbol, spec := range symbolMap {
// if strings.HasSuffix(lower, spec.repr) {
// if len(spec.repr) > matchLength {
// matchLength = len(spec.repr)
// if spec.kind == symClassOperator && (spec.opType == PosInfix || spec.opType == PosPrefix) {
// sym = symbol
// } else {
// sym = SymNone
// }
// }
// }
// }
// return
// }
func endingOperator(s string) (sym Symbol) {
var matchLength = 0
var repr string
+70 -66
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Symbol.go
// symbol.go
package scan
type Symbol int16
@@ -39,71 +39,75 @@ const (
SymAt // 28: '@'
SymUndescore // 29: '_'
SymEqual // 30: '='
SymDoubleEqual // 31: '=='
SymLess // 32: '<'
SymLessOrEqual // 33: '<='
SymGreater // 34: '>'
SymGreaterOrEqual // 35: '>='
SymLessGreater // 36: '<>'
SymNotEqual // 37: '!='
SymDollar // 38: '$'
SymHash // 39: '#'
SymOpenRound // 40: '('
SymClosedRound // 41: ')'
SymOpenSquare // 42: '['
SymClosedSquare // 43: ']'
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymDoubleGreater // 52: '>>'
SymDoubleLess // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymSlashEqual // 61: '/='
SymPercEqual // 62: '%='
SymDoubleLessEqual // 63: '<<='
SymDoubleGreaterEqual // 64: '>>='
SymAmpersandEqual // 65: '&='
SymVertBarEqual // 65: '|='
SymCaretEqual // 66: '^='
SymPlusGreater // 67: '+>'
SymLessPlus // 68: '<+'
SymChangeSign
SymUnchangeSign
SymDereference
SymPreInc
SymPreDec
SymIdentifier
SymBool
SymInteger
SymVariable
SymFloat
SymFraction
SymString
SymIterator
SymOr
SymAnd
SymNot
SymComment
SymFuncCall
SymFuncDef
SymList
SymDict
SymIndex
SymRange // [index : index]
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
SymColonEqual // 31: ':='
SymDoubleEqual // 32: '=='
SymLess // 33: '<'
SymLessOrEqual // 34: '<='
SymGreater // 35: '>'
SymGreaterOrEqual // 36: '>='
SymLessGreater // 37: '<>'
SymNotEqual // 38: '!='
SymDollar // 39: '$'
SymHash // 40: '#'
SymOpenRound // 41: '('
SymClosedRound // 42: ')'
SymOpenSquare // 43: '['
SymClosedSquare // 44: ']'
SymOpenBrace // 45: '{'
SymClosedBrace // 46: '}'
SymTilde // 47: '~'
SymDoubleQuestion // 48: '??'
SymQuestionEqual // 49: '?='
SymQuestionExclam // 50: '?!'
SymDoubleAt // 51: '@@'
SymDoubleColon // 52: '::'
SymDoubleGreater // 53: '>>'
SymDoubleLess // 54: '<<'
SymCaret // 55: '^'
SymDollarRound // 56: '$('
SymOpenClosedRound // 57: '()'
SymDoubleDollar // 58: '$$'
SymDoubleDot // 59: '..'
SymTripleDot // 60: '...'
SymStarEqual // 61: '*='
SymSlashEqual // 62: '/='
SymPercEqual // 63: '%='
SymDoubleLessEqual // 64: '<<='
SymDoubleGreaterEqual // 65: '>>='
SymAmpersandEqual // 66: '&='
SymVertBarEqual // 67: '|='
SymCaretEqual // 68: '^='
SymPlusGreater // 69: '+>'
SymLessPlus // 70: '<+'
SymChangeSign // 71: '-'
SymUnchangeSign // 72: '+''
SymDereference // 73: '*'
SymPreInc // 74: '++'
SymPreDec // 75: '--'
SymIdentifier // 76: identifier
SymBool // 77: boolean
SymInteger // 78: integer
SymVariable // 79: variable
SymFloat // 80: float
SymFraction // 81: fraction
SymString // 82: string
SymIterator // 83: iterator
SymOr // 84: 'or'
SymAnd // 85: 'and'
SymNot // 86: 'not'
SymComment // 87: comment
SymFuncCall // 88: function call
SymFuncDef // 89: function definition
SymList // 90: list
SymDict // 91: dict
SymIndex // 92: index
SymRange // 93: range [index : index]
SymExpression // 94: expression
SymSelector // 95: selector <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // 96: <selector-case> ::= [<list>] "{" <multi-expr> "}"
SymOpenSquareLess // 97: '[<'
SymGreaterClosedSquare // 98: '>]'
SymLinkedList // 99: linked-list
// SymOpenComment // 0: '/*'
// SymClosedComment // 0: '*/'
// SymOneLineComment // 0: '//'
+4
View File
@@ -136,3 +136,7 @@ func (tk *Token) ErrorExpectedGotStringWithPrefix(prefix, symbol, got string) (e
err = fmt.Errorf("[%d:%d] %s %s, got `%s`", tk.row, tk.col, prefix, symbol, got)
return
}
func (tk *Token) ErrorExpectedOneOfGot(expected ...Symbol) (err error) {
return tk.ErrorExpectedGotStringWithPrefix("expected one of ", SymListToString(expected, true), SymToString(tk.Sym))
}
+47 -40
View File
@@ -6,7 +6,6 @@ package expr
import (
"fmt"
"slices"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/util"
@@ -77,48 +76,56 @@ func (ctx *SimpleStore) Clone() kern.ExprContext {
// }
// }
func (ctx *SimpleStore) ToString(opt kern.FmtOpt) string {
dict := ctx.ToDict()
return dict.ToString(opt)
}
func (ctx *SimpleStore) varsToDict(dict *kern.DictType) *kern.DictType {
names := ctx.EnumVars(nil)
slices.Sort(names)
for _, name := range ctx.EnumVars(nil) {
value, _ := ctx.GetVar(name)
if f, ok := value.(kern.Formatter); ok {
(*dict)[name] = f.ToString(0)
} else if _, ok = value.(kern.Functor); ok {
(*dict)[name] = "func(){}"
} else {
(*dict)[name] = fmt.Sprintf("%v", value)
}
}
return dict
}
func (ctx *SimpleStore) funcsToDict(dict *kern.DictType) *kern.DictType {
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
value, _ := ctx.GetFuncInfo(name)
if formatter, ok := value.(kern.Formatter); ok {
(*dict)[name] = formatter.ToString(0)
} else {
(*dict)[name] = fmt.Sprintf("%v", value)
}
}
return dict
}
func (ctx *SimpleStore) ToDict() (dict *kern.DictType) {
dict = kern.MakeDict()
(*dict)["variables"] = ctx.varsToDict(kern.MakeDict())
(*dict)["functions"] = ctx.funcsToDict(kern.MakeDict())
return
return kern.ContextToDict(ctx)
}
func (ctx *SimpleStore) ToString(opt kern.FmtOpt) string {
return kern.ContextToString(ctx, opt)
}
// func (ctx *SimpleStore) ToString(opt kern.FmtOpt) string {
// dict := ctx.ToDict()
// return dict.ToString(opt)
// }
// func (ctx *SimpleStore) varsToDict(dict *kern.DictType) *kern.DictType {
// names := ctx.EnumVars(nil)
// slices.Sort(names)
// for _, name := range ctx.EnumVars(nil) {
// value, _ := ctx.GetVar(name)
// if f, ok := value.(kern.Formatter); ok {
// (*dict)[name] = f.ToString(0)
// } else if _, ok = value.(kern.Functor); ok {
// (*dict)[name] = "func(){}"
// } else {
// (*dict)[name] = fmt.Sprintf("%v", value)
// }
// }
// return dict
// }
// func (ctx *SimpleStore) funcsToDict(dict *kern.DictType) *kern.DictType {
// names := ctx.EnumFuncs(func(name string) bool { return true })
// slices.Sort(names)
// for _, name := range names {
// value, _ := ctx.GetFuncInfo(name)
// if formatter, ok := value.(kern.Formatter); ok {
// (*dict)[name] = formatter.ToString(0)
// } else {
// (*dict)[name] = fmt.Sprintf("%v", value)
// }
// }
// return dict
// }
// func (ctx *SimpleStore) ToDict() (dict *kern.DictType) {
// dict = kern.MakeDict()
// (*dict)["variables"] = ctx.varsToDict(kern.MakeDict())
// (*dict)["functions"] = ctx.funcsToDict(kern.MakeDict())
// return
// }
func (ctx *SimpleStore) GetVar(varName string) (value any, exists bool) {
if value, exists = ctx.varStore[varName]; !exists && ctx.global != nil {
value, exists = ctx.global.GetVar(varName)
+2 -2
View File
@@ -118,8 +118,8 @@ func TestFuncBaseOthers(t *testing.T) {
inputs := []inputType{
/* 1 */ {`set("a", 3); a`, int64(3), nil},
/* 2 */ {`set(true, 3)`, nil, `set(): the "name" parameter must be a string, got a bool (true)`},
// /* 3 */ {`a=3; unset("a"); a`, nil, `undefined variable or function "a"`},
// /* 4 */ {`unset("a")`, nil, `undefined variable or function "a"`},
/* 3 */ {`seq(1,2,3)`, kern.NewLinkedListA(int64(1), int64(2), int64(3)), nil},
// /* 4 */ {`seq(1,2,4)`, kern.NewLinkedListA(int64(1), int64(2), int64(3)), nil},
}
// runTestSuiteSpec(t, section, inputs, 4)
+24
View File
@@ -121,3 +121,27 @@ func logTest(t *testing.T, n int, section, source string, wantResult any, wantEr
t.Logf("[-]%s nr %3d -- `%s` --> %v", section, n, source, wantErr)
}
}
func testIteratorCallOp(t *testing.T, section string, it kern.Iterator) {
if _, err := it.CallOperation("next", map[string]any{}); err != nil {
t.Errorf(`%s -- CallOperation("next") failed: %v`, section, err)
}
if _, err := it.CallOperation("current", map[string]any{}); err != nil {
t.Errorf(`%s -- CallOperation("current") failed: %v`, section, err)
}
if _, err := it.CallOperation("clean", map[string]any{}); err != nil {
t.Errorf(`%s -- CallOperation("clean") failed: %v`, section, err)
}
if _, err := it.CallOperation("index", map[string]any{}); err != nil {
t.Errorf(`%s -- CallOperation("index") failed: %v`, section, err)
}
if _, err := it.CallOperation("count", map[string]any{}); err != nil {
t.Errorf(`%s -- CallOperation("count") failed: %v`, section, err)
}
if _, err := it.CallOperation("reset", map[string]any{}); err != nil {
t.Errorf(`%s -- CallOperation("reset") failed: %v`, section, err)
}
if _, err := it.CallOperation("fake", map[string]any{}); err == nil {
t.Errorf(`%s -- CallOperation("fake") should return error`, section)
}
}
+33
View File
@@ -6,6 +6,8 @@ package expr
import (
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
)
func TestCtrlSet(t *testing.T) {
@@ -58,3 +60,34 @@ func TestCtrlGetNotDefined(t *testing.T) {
t.Errorf(`%s -- CtrlGet(%q) should have returned nil, got %v`, section, varName, v)
}
}
func TestCtrlEnable(t *testing.T) {
section := "Context"
varName := "test_var"
varValue := true
ctx := NewSimpleStore()
GlobalCtrlSet(ctx, varName, varValue)
if !CtrlEnable(ctx, varName) {
t.Errorf(`%s -- CtrlEnable(ctx, %q) should have returned 'true', got 'false'`, section, varName)
}
if !CtrlDisable(ctx, varName) {
t.Errorf(`%s -- CtrlEnable(ctx, %q) should have returned 'true', got 'false'`, section, varName)
}
}
func TestList(t *testing.T) {
section := "Context"
inputs := []inputType{
/* 1 */ {`$$(5)`, kern.NewLinkedListA(5), nil},
/* 2 */ {`$$($(2))`, kern.NewLinkedListA(0, 1), nil},
/* 3 */ {`string(($$global).funcs.bool)`, `bool(value):boolean{}`, nil},
}
// runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+35 -81
View File
@@ -7,7 +7,6 @@ package expr
import (
"errors"
"strings"
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
@@ -17,105 +16,60 @@ import (
func TestDictParser(t *testing.T) {
section := "Dict"
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil},
/* 1 */ {`{}`, kern.NewDict(nil), nil},
/* 2 */ {`{123}`, nil, errors.New("[1:6] expected `:`, got `}`")},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, kern.NewDict(map[any]any{int64(1): "one", int64(2): "two", int64(3): "three"}), nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
/* 6 */ {`{1:"one"} + {2:"two"}`, kern.NewDict(map[any]any{int64(1): "one", int64(2): "two"}), nil},
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, kern.NewDict(map[any]any{"a": int64(9), "b": int64(2)}), nil},
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, kern.NewDict(map[any]any{"z": int64(9), "a": int64(1), "b": int64(2)}), nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
/* 12 */ {`m={
"a":1,
//"b":2,
"c":3
}`, map[any]any{"a": 1, "c": 3}, nil},
}`, kern.NewDict(map[any]any{"a": int64(1), "c": int64(3)}), nil},
/* 13 */ {`D={"a":1, "b":2}; D."a"`, int64(1), nil},
/* 14 */ {`D={"a":1, "b":2}; D.a`, int64(1), nil},
/* 15 */ {`D={1:"a", 2:"b", 3:"c"}; D.(1+2)`, "c", nil},
/* 16 */ {`D={1:"a"}; D.2`, nil, `[1:15] key 2 not found`},
/* 17 */ {`D={1:"a"}; D."2"`, nil, `[1:17] key "2" not found`},
/* 18 */ {`D={"a":1, "b":2}; D.a = 10; D.a`, int64(10), nil},
/* 19 */ {`k="a"; D={k: 10}`, kern.NewDict(map[any]any{"a": int64(10)}), nil},
/* 20 */ {`k=[<1,2>]; D={k: 10}`, nil, `[1:16] dict key can be integer or string, got lisked-list`},
/* 21 */ {`f=func(n){"x"+n}; d{f(5):10}`, nil, `[0:0] two adjacent operators: "d" and "{}"`},
/* 22 */ {`f=func(n){"x"+n}; d={f(5):10}`, kern.NewDict(map[any]any{"x5": int64(10)}), nil},
/* 23 */ {`f=func(n){"x"+n}; d={f(5); "z":10}`, nil, "[1:27] expected one of `:`, `}`, got `;`"},
/* 24 */ {`f=func(n){"x"+n}; d={f(5) but "z":10}`, kern.NewDict(map[any]any{"z": int64(10)}), nil},
}
succeeded := 0
failed := 0
// runTestSuiteSpec(t, section, inputs, 23, 24)
runTestSuite(t, section, inputs)
}
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
func TestAccessSubFields(t *testing.T) {
section := "Dict-Assign-Item"
ctx := NewSimpleStore()
ctx.UnsafeSetVar(
"D",
kern.NewDict(map[any]any{
"a": kern.NewDict(map[any]any{"uno": int64(10)}),
"b": kern.NewListA(1, 2, 3),
}),
) // D={"a": {"uno":1}}
for i, input := range inputs {
var expr *scan.Ast
var gotResult any
var gotErr error
ctx := NewSimpleStoreWithoutGlobalContext()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser()
logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := scan.NewScanner(r, scan.DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
inputs := []inputType{
/* 1 */ {`D.a.uno`, int64(10), nil},
/* 2 */ {`D.a.uno = 111; D.a."uno"`, int64(111), nil},
/* 3 */ {`D.a.uno = 111; D["a"]["uno"]`, int64(111), nil},
/* 4 */ {`D.b[1] = 22; D["b"][1]`, int64(22), nil},
}
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
}
}
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
// runCtxTestSuiteSpec(t, ctx, section, inputs, 4)
runCtxTestSuite(t, ctx, section, inputs)
}
func TestDictToStringMultiLine(t *testing.T) {
+1 -2
View File
@@ -31,8 +31,7 @@ func TestExpr(t *testing.T) {
/* 15 */ {`a=3; a*=2)+1; a`, nil, `[1:11] unexpected token ")"`},
/* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), nil},
/* 17 */ {`true ? {"a"} :: {"b"}`, "a", nil},
/* 18 */ {`$$`, kern.NewDict(map[any]any{"variables": kern.NewDict(nil), "functions": kern.NewDict(nil)}), nil},
///* 19 */ {`$$global`, NewDict(map[any]any{"variables": NewDict(nil), "functions": NewDict(nil)}), nil},
/* 18 */ {`$$`, kern.NewDict(map[any]any{"vars": kern.NewDict(nil), "funcs": kern.NewDict(nil)}), nil},
/* 19 */ {`
ds={
"init":func(@end){@current=0 but true},
+43 -39
View File
@@ -5,51 +5,55 @@
package expr
import (
"fmt"
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
)
// TODO The new function param model does not allow this kind of test
// ------------------------------------------------------------------
// func subtract(ctx ExprContext, name string, args map[string]any) (result any, err error) {
// if len(args) != 2 {
// err = fmt.Errorf("%s(): requires exactly two arguments", name)
// return
// }
// x, xok := args[0].(int64)
// y, yok := args[1].(int64)
// if xok && yok {
// result = x - y
// } else {
// err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y)
// }
// return
// }
func subtract(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
if len(args) != 2 {
err = fmt.Errorf("%s(): requires exactly two arguments", name)
return
}
x, xok := args["a"].(int64)
y, yok := args["b"].(int64)
if xok && yok {
result = x - y
} else {
err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y)
}
return
}
// func TestEvalStringA(t *testing.T) {
func TestEvalStringA(t *testing.T) {
// source := `a + b * subtract(4,2)`
// args := []Arg{
// {"a", uint8(1)},
// {"b", int8(2)},
// {"subtract", FuncTemplate2(subtract)},
// // force coverage
// {"a16", uint16(1)},
// {"b16", int16(2)},
// {"a32", uint32(1)},
// {"b32", int32(2)},
// {"a64", uint64(1)},
// {"b64", int64(2)},
// {"f32", float32(1.0)},
// {"f64", float64(1.0)},
// }
source := `a + b * subtract(4,2)`
args := []Arg{
{"a", uint8(1)},
{"b", int8(2)},
{"subtract", kern.FuncTemplate(subtract)},
// force coverage
{"a16", uint16(1)},
{"b16", int16(2)},
{"a32", uint32(1)},
{"b32", int32(2)},
{"a64", uint64(1)},
{"b64", int64(2)},
{"f32", float32(1.0)},
{"f64", float64(1.0)},
{"string", "text"},
{"bool", true},
}
// wantResult := int64(5)
// gotResult, gotErr := EvalStringA(source, args...)
// if value, ok := gotResult.(int64); ok && value != wantResult {
// t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
// t.Errorf("Error: %v", gotErr)
// }
// }
wantResult := int64(5)
gotResult, gotErr := EvalStringA(source, args...)
if value, ok := gotResult.(int64); ok && value != wantResult {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
}
func TestEvalString(t *testing.T) {
+4 -2
View File
@@ -6,19 +6,21 @@ package expr
import (
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
)
func TestCollections(t *testing.T) {
section := "Collection"
section := "Index"
inputs := []inputType{
/* 1 */ {`"abcdef"[1:3]`, "bc", nil},
/* 2 */ {`"abcdef"[:3]`, "abc", nil},
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
/* 5 */ {`"abcdef"[1:2:3]`, nil, `[1:14] invalid range specification`},
/* 6 */ {`"abcdef"[((1>0)?{1}:{0}):3]`, "bc", nil},
/* 7 */ {`"abcdef"[[0,1][0]:1]`, "a", nil},
/* 8 */ {`[0,1,2,3,4][:]`, kern.NewListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
}
t.Setenv("EXPR_PATH", ".")
+2 -2
View File
@@ -13,8 +13,8 @@ import (
func TestIterIterator(t *testing.T) {
section := "Iter-Iter"
inputs := []inputType{
/* 1 */ {`it=$(4); $$($(it) filter ${_}==100)`, kern.NewListA(), nil},
/* 2 */ {`it=$(4); $$($(it, $_) filter ${_}==100)`, kern.NewListA(), nil},
/* 1 */ {`it=$(4); $$($(it) filter ${_}==100)`, kern.NewLinkedListA(), nil},
/* 2 */ {`it=$(4); $$($(it, $_) filter ${_}==100)`, kern.NewLinkedListA(), nil},
/* 3 */ {`it=$(4); $(it, 10+$_, last-1) digest ${_}`, int64(12), nil},
/* 4 */ {`f=func(n){last-n}; it=$(4); $(it, 10+$_, f(-1)) digest ${_}`, int64(14), nil},
}
+88 -10
View File
@@ -8,6 +8,7 @@ import (
"testing"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
)
func TestIteratorParser(t *testing.T) {
@@ -35,17 +36,94 @@ func TestIteratorParser(t *testing.T) {
/* 20 */ {`it=$({1:"one",2:"two",3:"three"}, "default", "value"); it++`, "one", nil},
/* 21 */ {`it=$({1:"one",2:"two",3:"three"}, "desc", "key"); it++`, int64(3), nil},
/* 22 */ {`it=$({1:"one",2:"two",3:"three"}, "asc", "item"); it++`, kern.NewList([]any{int64(1), "one"}), nil},
/* 23 */ {`$$($(1,4,0))`, nil, `step cannot be zero`},
/* 24 */ {`$$($(1,4,-1))`, nil, `step cannot be negative when start < stop`},
/* 25 */ {`$$($(4,1,1))`, nil, `step cannot be positive when start > stop`},
}
// runTestSuiteSpec(t, section, inputs, 1)
// runTestSuiteSpec(t, section, inputs, 25)
runTestSuite(t, section, inputs)
}
func TestCallOpIntIter(t *testing.T) {
section := "IntIterator-CallOp"
if it, err := NewIntIteratorA(int64(1), int64(4), int64(1)); err != nil {
t.Errorf(`%s -- NewIntIteratorA() failed: %v`, section, err)
} else {
testIteratorCallOp(t, section, it)
testIterAttrs(t, section, it, "IntIterator", "$(1..4..1)")
}
}
func TestCallOpIterIter(t *testing.T) {
section := "IterIterator-CallOp"
ctx := NewSimpleStore()
if inner, err := NewIntIteratorA(int64(1), int64(4), int64(1)); err == nil {
if it, err := NewIterIter(inner, ctx, []*scan.Term{}); err != nil {
t.Errorf(`%s -- NewIterIter() failed: %v`, section, err)
} else {
testIteratorCallOp(t, section, it)
testIterAttrs(t, section, it, "IterIter", "$($(1..4..1))")
}
} else {
t.Errorf(`%s -- can't create inner iterator: %v`, section, err)
}
}
func TestCallOpDictIter(t *testing.T) {
section := "DictIterator-CallOp"
inner := kern.NewDict(map[any]any{"a": 1})
if it, err := NewDictIterator(inner, nil); err != nil {
t.Errorf(`%s -- NewIterIter() failed: %v`, section, err)
} else {
testIteratorCallOp(t, section, it)
testIterAttrs(t, section, it, "DictIterator", "$({#1})")
}
}
func TestCallOpLinkedListIter(t *testing.T) {
section := "LinkedListIterator-CallOp"
inner := kern.NewLinkedListA(1, 2, 3)
it := NewLinkedListIterator(inner, nil)
testIteratorCallOp(t, section, it)
// wanted := "$([<#3>])"
// got := it.String()
// if wanted != got {
// t.Errorf(`%s -- LinkedListIterator.String() failed: expected %q, got %q`, section, wanted, got)
// }
// wanted = "LinkedListIterator"
// got = it.TypeName()
// if wanted != got {
// t.Errorf(`%s -- LinkedListIterator.TypeName() failed: expected %q, got %q`, section, wanted, got)
// }
testIterAttrs(t, section, it, "LinkedListIterator", "$([<#3>])")
}
func testIterAttrs(t *testing.T, section string, it kern.Iterator, name, repr string) {
wanted := repr
got := it.String()
if wanted != got {
t.Errorf(`%s -- %s.String() failed: expected %q, got %q`, section, name, wanted, got)
}
wanted = name
got = it.TypeName()
if wanted != got {
t.Errorf(`%s -- %s.TypeName() failed: expected %q, got %q`, section, name, wanted, got)
}
}
func TestFilterIterator(t *testing.T) {
section := "Iterator-Filter"
inputs := []inputType{
/* 1 */ {`$$([1,2,3] filter $_%2==0)`, kern.NewList([]any{int64(2)}), nil},
/* 2 */ {`it = [1,2,3] filter $_%2!=1; $$(it)`, kern.NewList([]any{int64(2)}), nil},
/* 1 */ {`$$([1,2,3] filter $_%2==0)`, kern.NewLinkedListA(2), nil},
/* 2 */ {`it = [1,2,3] filter $_%2!=1; $$(it)`, kern.NewLinkedListA(2), nil},
/* 3 */ {`builtin "os.file"; #$$(fileLineIterator("test-file.txt") filter (#${_} == 2))`, int64(0), nil},
/* 4 */ {`builtin "os.file"; #$$(fileLineIterator("test-file.txt") filter (#${_} == 3))`, int64(2), nil},
}
@@ -69,11 +147,11 @@ func TestDigestIterator(t *testing.T) {
func TestCatIterator(t *testing.T) {
section := "Iterator-Cat"
inputs := []inputType{
/* 1 */ {`$$([1] cat [])`, kern.NewList([]any{int64(1)}), nil},
/* 2 */ {`$$([1] cat [2])`, kern.NewList([]any{int64(1), int64(2)}), nil},
/* 3 */ {`$$([1] cat nil)`, kern.NewList([]any{int64(1)}), nil},
/* 4 */ {`$$(nil cat [2])`, kern.NewList([]any{int64(2)}), nil},
/* 5 */ {`$$(nil cat nil)`, kern.NewList([]any{}), nil},
/* 1 */ {`$$([1] cat [])`, kern.NewLinkedListA(1), nil},
/* 2 */ {`$$([1] cat [2])`, kern.NewLinkedListA(1, 2), nil},
/* 3 */ {`$$([1] cat nil)`, kern.NewLinkedListA(1), nil},
/* 4 */ {`$$(nil cat [2])`, kern.NewLinkedListA(2), nil},
/* 5 */ {`$$(nil cat nil)`, kern.NewLinkedListA(), nil},
/* 6 */ {`$$(["a","b"] cat ["x"-true])`, nil, `[1:23] left operand 'x' [string] and right operand 'true' [bool] are not compatible with operator "-"`},
}
@@ -84,10 +162,10 @@ func TestCatIterator(t *testing.T) {
func TestMapIterator(t *testing.T) {
section := "Iterator-Map"
inputs := []inputType{
/* 1 */ {`$$([3,4,5] map ${_#})`, kern.NewList([]any{int64(1), int64(2), int64(3)}), nil},
/* 1 */ {`$$([3,4,5] map ${_#})`, kern.NewLinkedListA(1, 2, 3), nil},
/* 2 */ {`#$$($(10) map ${_})`, int64(10), nil},
/* 3 */ {`#$$($(10,0) map ${_})`, int64(10), nil},
/* 4 */ {`builtin "os.file"; $$(fileLineIterator("test-file.txt") map ${__})`, kern.NewList([]any{int64(0), int64(1)}), nil},
/* 4 */ {`builtin "os.file"; $$(fileLineIterator("test-file.txt") map ${__})`, kern.NewLinkedListA(0, 1), nil},
/* 5 */ {`$$(["1", "2", "3"] map int())`, nil, `int(): too few params -- expected 1, got 0`},
}
+19 -1
View File
@@ -57,10 +57,28 @@ func TestListParser(t *testing.T) {
/* 41 */ {`[0] << $([1,2,3,4])`, kern.NewListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 42 */ {`L=[]; [1] >> L; L`, kern.NewListA(), nil},
/* 43 */ {`L=[]; L << [1]; L`, kern.NewListA(), nil},
// /* 44 */ {`[0,1,2,3,4][2:3]`, kern.NewListA(int64(20)), nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 42)
// runTestSuiteSpec(t, section, inputs, 44)
runTestSuite(t, section, inputs)
}
func TestLinkedListParser(t *testing.T) {
section := "Linked-List"
inputs := []inputType{
/* 1 */ {`[<1,2>]`, kern.NewLinkedListA(1, 2), nil},
/* 2 */ {`it=$([<1,2,3>]); it++`, int64(1), nil},
/* 3 */ {`it=$([<1,2,3>]); #($$(it))`, int64(3), nil},
/* 4 */ {`[<1,2,3>][0]`, int64(1), nil},
/* 5 */ {`[<1,2,3>][0:2]`, kern.NewLinkedListA(1, 2), nil},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
}
+20 -1
View File
@@ -35,6 +35,12 @@ func TestOperator(t *testing.T) {
/* 20 */ {`a=1; a^=2`, int64(3), nil},
/* 21 */ {`a=1; ++a`, int64(2), nil},
/* 22 */ {`a=1; --a`, int64(0), nil},
/* 23 */ {`a=1; a++`, int64(1), nil},
/* 24 */ {`a=1; a--`, int64(1), nil},
/* 25 */ {`a="1"; ++a`, nil, `[1:9] prefix/postfix operator "++" does not support operand '1' [string]`},
/* 26 */ {`a="1"; --a`, nil, `[1:9] prefix/postfix operator "--" does not support operand '1' [string]`},
/* 27 */ {`a="1"; a++`, nil, `[1:10] prefix/postfix operator "++" does not support operand '1' [string]`},
/* 28 */ {`a="1"; a--`, nil, `[1:10] prefix/postfix operator "--" does not support operand '1' [string]`},
}
// t.Setenv("EXPR_PATH", ".")
@@ -54,7 +60,7 @@ func TestOperatorInsert(t *testing.T) {
/* 6 */ {`[] >> ["a", "b"]`, kern.NewListA("a", "b"), nil},
/* 7 */ {`["a", "b"] << $([1,2,3])`, kern.NewListA("a", "b", int64(1), int64(2), int64(3)), nil},
/* 8 */ {`L=["a", "b"]; L << $([1,2,3])`, kern.NewListA("a", "b", int64(1), int64(2), int64(3)), nil},
/* 9 */ {`L=["a", "b"]; L << $([1,2,3]); L`, kern.NewListA("a", "b", int64(1), int64(2), int64(3)), nil},
/* 9 */ {`L=["a", "b"]; L << $([1,2,3]); L`, kern.NewListA("a", "b"), nil},
/* 10 */ {`L << $([1,2,3])`, nil, `undefined variable or function "L"`},
}
@@ -62,6 +68,19 @@ func TestOperatorInsert(t *testing.T) {
runTestSuite(t, section, inputs)
}
func TestOperatorDeepAssign(t *testing.T) {
section := "Operator-DeepAssign"
inputs := []inputType{
/* 1 */ {`DD={"uno": 1, "due": 2}; D=DD; D["uno"]=11; DD`, kern.NewDict(map[any]any{"uno": int64(11), "due": int64(2)}), nil},
/* 2 */ {`DD={"uno": 1, "due": 2}; D:=DD; D["uno"]=11; DD`, kern.NewDict(map[any]any{"uno": int64(1), "due": int64(2)}), nil},
/* 3 */ {`LL=["a", "b"]; L=LL; L[0]="x"; LL`, kern.NewListA("x", "b"), nil},
/* 4 */ {`LL=["a", "b"]; L:=LL; L[0]="x"; LL`, kern.NewListA("a", "b"), nil},
}
// runTestSuiteSpec(t, section, inputs, 4)
runTestSuite(t, section, inputs)
}
func TestOperatorDigest(t *testing.T) {
section := "Operator-Digest"
inputs := []inputType{
+7
View File
@@ -77,3 +77,10 @@ func ForAll[T, V any](ts []T, fn func(T) V) []V {
}
return result
}
func UnquoteString(src string) string {
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1]
}
return src
}