Compare commits

...

10 Commits

18 changed files with 455 additions and 136 deletions

2
ast.go
View File

@ -60,7 +60,7 @@ func (self *ast) addToken(tk *Token) (err error) {
}
func (self *ast) addToken2(tk *Token) (t *term, err error) {
if t = newTerm(tk, nil); t != nil {
if t = newTerm(tk); t != nil {
err = self.addTerm(t)
} else {
err = tk.Errorf("unexpected token %q", tk.String())

View File

@ -18,6 +18,10 @@ func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
}
func errDivisionByZero(funcName string) error {
return fmt.Errorf("%s() division by zero", funcName)
}
// --- Parameter errors
func errOneParam(funcName string) error {

View File

@ -23,22 +23,9 @@ func TestDictParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1):"one", int64(2):"two", int64(3):"three"}, nil},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
// /* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
// /* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
// /* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// /* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
// /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
// /* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
// /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
// /* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
// /* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
// /* 14 */ {`[1,2,3].1`, int64(2), nil},
// /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
// /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
}
succeeded := 0

View File

@ -194,6 +194,25 @@ Some arithmetic operators can also be used with strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|===
The items of strings can be accessed using the dot `.` operator.
.Item access syntax
[source,bnf]
----
<item> ::= <string-expr>"."<index-expr>
----
.String examples
`>>>` [blue]`s="abc"` [gray]_assign the string to variable s_ +
[green]`abc` +
`>>>` [blue]`s.1` [gray]_char at position 1 (starting from 0)_ +
[green]`b` +
`>>>` [blue]`s.(-1)` [gray]_char at position -1, the rightmost one_ +
[green]`c` +
`>>>` [blue]`\#s` [gray]_number of chars_ +
[gren]`3` +
`>>>` [blue]`#"abc"` [gray]_number of chars_ +
[green]`3` +
=== Boolean
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
@ -278,29 +297,27 @@ The items of array can be accessed using the dot `.` operator.
----
.Items of list
[source,go]
----
`>>>` [blue]`[1,2,3].1`
[green]`2`
`>>>` [blue]`list=[1,2,3]; list.1`
[green]`2`
`>>>` [blue]`["one","two","three"].1`
[green]`two`
`>>>` [blue]`list=["one","two","three"]; list.(2-1)`
[green]`two`
`>>>` [blue]`list.(-1)`
[green]`three`
`>>>` [blue]`list.(-1)`
[green]`three`
`>>>` [blue]`list.(10)`
----
`>>>` [blue]`[1,2,3].1` +
[green]`2` +
`>>>` [blue]`list=[1,2,3]; list.1` +
[green]`2` +
`>>>` [blue]`["one","two","three"].1` +
[green]`two` +
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
[green]`two` +
`>>>` [blue]`list.(-1)` +
[green]`three` +
`>>>` [blue]`list.(10)` +
[red]`Eval Error: [1:9] index 10 out of bounds` +
`>>>` [blue]`#list` +
[green]`3`
== Dictionaries
The _dictionary_ data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_. Dictionary literals are sequences of pairs separated by comma `,`; sequences are enclosed between brace brackets.
.List examples
.Dictionary examples
[source,go]
----
{1:"one", 2:"two"}
@ -311,7 +328,7 @@ The _dictionary_ data-type is set of pairs _key/value_. It is also known as _map
WARNING: Support for dictionaries is still ongoing.
== Variables
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go _ExprContext_ interface, e.g. _SimpleVarStore_ or _SimpleFuncStore_.
.Examples
[source,go]
@ -365,23 +382,30 @@ The _selector operator_ is very similar to the _switch/case/default_ statement a
<multi-expression> ::= <expression> {";" <expression>}
----
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision find a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation.
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision finds a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation.
The match lists are optional. In that case, the position, from left to right, of the `<selector-case>` is used as match-list. Of course, that only works if the select-expression results in an integer.
The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.
.Examples
[source,go]
----
1 ? {"a"} : {"b"} // returns "b"
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
`>>>` [blue]`1 ? {"a"} : {"b"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}`
[green]`c'
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`
[red]`Parse Error: [1:34] case list in default clause`
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"}`
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
----
== Priorities of operators

View File

@ -868,6 +868,28 @@ pre.rouge .ss {
</tr>
</tbody>
</table>
<div class="paragraph">
<p>The items of strings can be accessed using the dot <code>.</code> operator.</p>
</div>
<div class="listingblock">
<div class="title">Item access syntax</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="bnf">&lt;item&gt; ::= &lt;string-expr&gt;"."&lt;index-expr&gt;</code></pre>
</div>
</div>
<div class="paragraph">
<div class="title">String examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">s="abc"</code> <em class="gray">assign the string to variable s</em><br>
<code class="green">abc</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.1</code> <em class="gray">char at position 1 (starting from 0)</em><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.(-1)</code> <em class="gray">char at position -1, the rightmost one</em><br>
<code class="green">c</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#s</code> <em class="gray">number of chars</em><br>
<code class="gren">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#"abc"</code> <em class="gray">number of chars</em><br>
<code class="green">3</code><br></p>
</div>
</div>
<div class="sect2">
<h3 id="_boolean"><a class="anchor" href="#_boolean"></a><a class="link" href="#_boolean">2.4. Boolean</a></h3>
@ -1059,23 +1081,22 @@ pre.rouge .ss {
<pre class="rouge highlight"><code data-lang="bnf">&lt;item&gt; ::= &lt;list-expr&gt;"."&lt;index-expr&gt;</code></pre>
</div>
</div>
<div class="listingblock">
<div class="paragraph">
<div class="title">Items of list</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`[1,2,3].1`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`2`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`list=[1,2,3]; list.1`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`2`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`["one","two","three"].1`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`two`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`list=["one","two","three"]; list.(2-1)`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`two`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`list.(-1)`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`three`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`list.(-1)`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`three`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`list.(10)`</span></code></pre>
</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">[1,2,3].1</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">list=[1,2,3]; list.1</code><br>
<code class="green">2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">["one","two","three"].1</code><br>
<code class="green">two</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">list=["one","two","three"]; list.(2-1)</code><br>
<code class="green">two</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">list.(-1)</code><br>
<code class="green">three</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">list.(10)</code><br>
<code class="red">Eval Error: [1:9] index 10 out of bounds</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#list</code><br>
<code class="green">3</code></p>
</div>
</div>
</div>
@ -1087,7 +1108,7 @@ pre.rouge .ss {
<p>The <em>dictionary</em> data-type is set of pairs <em>key/value</em>. It is also known as <em>map</em> or <em>associative array</em>. Dictionary literals are sequences of pairs separated by comma <code>,</code>; sequences are enclosed between brace brackets.</p>
</div>
<div class="listingblock">
<div class="title">List examples</div>
<div class="title">Dictionary examples</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="p">{</span><span class="m">1</span><span class="o">:</span><span class="s">"one"</span><span class="p">,</span> <span class="m">2</span><span class="o">:</span><span class="s">"two"</span><span class="p">}</span>
<span class="p">{</span><span class="s">"one"</span><span class="o">:</span><span class="m">1</span><span class="p">,</span> <span class="s">"two"</span><span class="o">:</span> <span class="m">2</span><span class="p">}</span>
@ -1112,7 +1133,7 @@ Support for dictionaries is still ongoing.
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">4. Variables</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>A variable is an identifier with an assigned value. Variables are stored in the object that implements the <em>ExprContext</em> interface.</p>
<p>A variable is an identifier with an assigned value. Variables are stored in the object that implements the Go <em>ExprContext</em> interface, e.g. <em>SimpleVarStore</em> or <em>SimpleFuncStore</em>.</p>
</div>
<div class="listingblock">
<div class="title">Examples</div>
@ -1202,7 +1223,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
</div>
<div class="paragraph">
<p>In other words, the selector operator evaluates the expression (<code>&lt;select-expression&gt;</code>) on the left-hand side of the <code>?</code> symbol; it then compares the result obtained with the values listed in the <code>&lt;match-list&gt;&#8217;s. If the comparision find a match with a value in a match-list, the associated `&lt;case-multi-expression&gt;</code> is evaluted, and its result will be the final result of the selection operation.</p>
<p>In other words, the selector operator evaluates the expression (<code>&lt;select-expression&gt;</code>) on the left-hand side of the <code>?</code> symbol; it then compares the result obtained with the values listed in the <code>&lt;match-list&gt;&#8217;s. If the comparision finds a match with a value in a match-list, the associated `&lt;case-multi-expression&gt;</code> is evaluted, and its result will be the final result of the selection operation.</p>
</div>
<div class="paragraph">
<p>The match lists are optional. In that case, the position, from left to right, of the <code>&lt;selector-case&gt;</code> is used as match-list. Of course, that only works if the select-expression results in an integer.</p>
@ -1213,13 +1234,21 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<div class="listingblock">
<div class="title">Examples</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="go"><span class="m">1</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="c">// returns "b"</span>
<span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span> <span class="c">// returns "c"</span>
<span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span> <span class="c">// returns "b"</span>
<span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span> <span class="c">// error: "... case list in default clause"</span>
<span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span> <span class="n">but</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span> <span class="c">// returns "b"</span>
<span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span><span class="p">;</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span> <span class="c">// returns "b"</span>
<span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="c">// error: "... no case catches the value (10) of the selection expression</span></code></pre>
<pre class="rouge highlight"><code data-lang="go"><span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`1 ? {"a"} : {"b"}`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`b`</span>
<span class="s">`&gt;&gt;&gt;`</span> <span class="p">[</span><span class="n">blue</span><span class="p">]</span><span class="s">`10 ? {"a"} : {"b"} :: {"c"}`</span>
<span class="p">[</span><span class="n">green</span><span class="p">]</span><span class="s">`c'
[green]`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[green]`</span><span class="n">b</span><span class="s">`
`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="no">true</span><span class="p">,</span> <span class="m">2</span><span class="o">+</span><span class="m">8</span><span class="p">]</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span> <span class="o">::</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[red]`</span><span class="n">Parse</span> <span class="n">Error</span><span class="o">:</span> <span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="m">34</span><span class="p">]</span> <span class="k">case</span> <span class="n">list</span> <span class="n">in</span> <span class="k">default</span> <span class="n">clause</span><span class="s">`
[green]`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span> <span class="n">but</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[green]`</span><span class="n">b</span><span class="s">`
`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span><span class="p">[</span><span class="m">10</span><span class="p">]</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="s">"b"</span><span class="p">;</span> <span class="n">x</span><span class="p">}</span> <span class="o">::</span> <span class="p">{</span><span class="s">"c"</span><span class="p">}</span><span class="s">`
[green]`</span><span class="n">b</span><span class="s">`
`</span><span class="o">&gt;&gt;&gt;</span><span class="s">` [blue]`</span><span class="m">10</span> <span class="err">?</span> <span class="p">{</span><span class="s">"a"</span><span class="p">}</span> <span class="o">:</span> <span class="p">{</span><span class="s">"b"</span><span class="p">}</span><span class="s">`
[red]`</span><span class="n">Eval</span> <span class="n">Error</span><span class="o">:</span> <span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">]</span> <span class="n">no</span> <span class="k">case</span> <span class="n">catches</span> <span class="n">the</span> <span class="n">value</span> <span class="p">(</span><span class="m">10</span><span class="p">)</span> <span class="n">of</span> <span class="n">the</span> <span class="n">selection</span> <span class="n">expression</span><span class="s">`
</span></code></pre>
</div>
</div>
</div>
@ -1490,7 +1519,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
<div id="footer">
<div id="footer-text">
Last updated 2024-05-10 06:38:03 +0200
Last updated 2024-05-11 20:13:17 +0200
</div>
</div>
</body>

View File

@ -43,6 +43,11 @@ func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err e
return
}
func isRationalFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsRational(args[0])
return
}
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsList(args[0])
return
@ -53,30 +58,97 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
switch v := args[0].(type) {
case int64:
result = v
case float64:
result = int64(math.Trunc(v))
case bool:
if v {
result = int64(1)
} else {
result = int64(0)
}
case string:
var i int
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
switch v := args[0].(type) {
case int64:
result = v
case float64:
result = int64(math.Trunc(v))
case bool:
if v {
result = int64(1)
} else {
result = int64(0)
}
} else {
err = errOneParam(name)
case string:
var i int
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
}
return
}
func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
result = float64(v)
case float64:
result = v
case bool:
if v {
result = float64(1)
} else {
result = float64(0)
}
case string:
var f float64
if f, err = strconv.ParseFloat(v, 64); err == nil {
result = f
}
case *fraction:
result = v.toFloat()
default:
err = errCantConvert(name, v, "float")
}
return
}
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
var den int64 = 1
if len(args) > 1 {
var ok bool
if den, ok = args[1].(int64); !ok {
err = errExpectedGot(name, "integer", args[1])
} else if den == 0 {
err = errDivisionByZero(name)
}
}
if err == nil {
result = newFraction(v, den)
}
case float64:
result, err = float64ToFraction(v)
// var n, d int64
// if n, d, err = float64ToFraction(v); err == nil {
// result = newFraction(n, d)
// }
case bool:
if v {
result = newFraction(1, 1)
} else {
result = newFraction(0, 1)
}
case string:
result, err = makeGeneratingFraction(v)
// var f float64
// // TODO temporary implementation
// if f, err = strconv.ParseFloat(v, 64); err == nil {
// var n, d int64
// if n, d, err = float64ToFraction(f); err == nil {
// result = newFraction(n, d)
// }
// } else {
// errors.New("convertion from string to float is ongoing")
// }
case *fraction:
result = v
default:
err = errCantConvert(name, v, "float")
}
return
}
@ -93,10 +165,13 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
}
func init() {

View File

@ -57,16 +57,21 @@ func TestFuncs(t *testing.T) {
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
/* 45 */ {`isInt(2+1)`, true, nil},
/* 46 */ {`isInt(3.1)`, false, nil},
/* 46 */ {`isFloat(3.1)`, true, nil},
/* 47 */ {`isString("3.1")`, true, nil},
/* 48 */ {`isString("3" + 1)`, true, nil},
/* 49 */ {`isList(["3", 1])`, true, nil},
/* 50 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 51 */ {`isFract(3|1)`, true, nil},
/* 47 */ {`isFloat(3.1)`, true, nil},
/* 48 */ {`isString("3.1")`, true, nil},
/* 49 */ {`isString("3" + 1)`, true, nil},
/* 50 */ {`isList(["3", 1])`, true, nil},
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 52 */ {`isFract(1|3)`, true, nil},
/* 53 */ {`isFract(3|1)`, false, nil},
/* 54 */ {`isRational(3|1)`, true, nil},
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTest(t, "Func", inputs[25:26])
//parserTest(t, "Func", inputs[54:55])
parserTest(t, "Func", inputs)
}

54
global-context.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// global-context.go
package expr
import "path/filepath"
var globalCtx *SimpleFuncStore
func ImportInContext(name string) (exists bool) {
var mod *module
if mod, exists = moduleRegister[name]; exists {
mod.importFunc(globalCtx)
mod.imported = true
}
return
}
func ImportInContextByGlobPattern(pattern string) (count int, err error) {
var matched bool
for name, mod := range moduleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
mod.importFunc(globalCtx)
mod.imported = true
}
} else {
break
}
}
return
}
func GetVar(ctx ExprContext, name string) (value any, exists bool) {
if value, exists = ctx.GetVar(name); !exists {
value, exists = globalCtx.GetVar(name)
}
return
}
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
if item, exists = ctx.GetFuncInfo(name); exists {
ownerCtx = ctx
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
ownerCtx = globalCtx
}
return
}
func init() {
globalCtx = NewSimpleFuncStore()
}

View File

@ -39,6 +39,7 @@ func TestListParser(t *testing.T) {
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},

View File

@ -6,7 +6,6 @@ package expr
import (
"fmt"
"path/filepath"
)
type module struct {
@ -31,30 +30,30 @@ func registerImport(name string, importFunc func(ExprContext), description strin
moduleRegister[name] = newModule(importFunc, description)
}
func ImportInContext(ctx ExprContext, name string) (exists bool) {
var mod *module
if mod, exists = moduleRegister[name]; exists {
mod.importFunc(ctx)
mod.imported = true
}
return
}
// func ImportInContext(ctx ExprContext, name string) (exists bool) {
// var mod *module
// if mod, exists = moduleRegister[name]; exists {
// mod.importFunc(ctx)
// mod.imported = true
// }
// return
// }
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool
for name, mod := range moduleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
mod.importFunc(ctx)
mod.imported = true
}
} else {
break
}
}
return
}
// func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
// var matched bool
// for name, mod := range moduleRegister {
// if matched, err = filepath.Match(pattern, name); err == nil {
// if matched {
// count++
// mod.importFunc(ctx)
// mod.imported = true
// }
// } else {
// break
// }
// }
// return
// }
func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil {

View File

@ -23,7 +23,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
// -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
if info, exists := ctx.GetFuncInfo(name); exists {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
if info.MinArgs() > len(params) {
if info.MaxArgs() < 0 {
err = fmt.Errorf("too few params -- expected %d or more, got %d", info.MinArgs(), len(params))
@ -31,9 +31,12 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
err = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
}
}
if info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
}
if err == nil && owner != ctx {
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}

View File

@ -24,8 +24,8 @@ func newVarTerm(tk *Token) *term {
func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool
name := self.source()
if v, exists = ctx.GetVar(name); !exists {
if info, exists := ctx.GetFuncInfo(name); exists {
if v, exists = GetVar(ctx, name); !exists {
if info, exists, _ := GetFuncInfo(ctx, name); exists {
v = info.Functor()
} else {
err = fmt.Errorf("undefined variable or function %q", name)

View File

@ -28,13 +28,13 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
count := 0
if IsString(childValue) {
module, _ := childValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module)
count, err = ImportInContextByGlobPattern(module)
} else {
var moduleSpec any
it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) {
if ImportInContext(module) {
count++
} else {
err = self.Errorf("unknown module %q", module)

View File

@ -7,6 +7,7 @@ package expr
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
@ -24,6 +25,134 @@ func newFraction(num, den int64) *fraction {
return &fraction{num, den}
}
func float64ToFraction(f float64) (fract *fraction, err error) {
var sign string
intPart, decPart := math.Modf(f)
if decPart < 0.0 {
sign="-"
intPart = -intPart
decPart = -decPart
}
dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
// fmt.Printf("S: '%s'\n",s)
return makeGeneratingFraction(s)
}
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
/*
func _float64ToFraction(f float64) (num, den int64, err error) {
const expMask = 1<<11 - 1
bits := math.Float64bits(f)
mantissa := bits & (1<<52 - 1)
exp := int((bits >> 52) & expMask)
switch exp {
case expMask: // non-finite
err = errors.New("infite")
return
case 0: // denormal
exp -= 1022
default: // normal
mantissa |= 1 << 52
exp -= 1023
}
shift := 52 - exp
// Optimization (?): partially pre-normalise.
for mantissa&1 == 0 && shift > 0 {
mantissa >>= 1
shift--
}
if f < 0 {
num = -int64(mantissa)
} else {
num = int64(mantissa)
}
den = int64(1)
if shift > 0 {
den = den << shift
} else {
num = num << (-shift)
}
return
}
*/
func makeGeneratingFraction(s string) (f *fraction, err error) {
var num, den int64
var sign int64 = 1
var parts []string
if len(s) == 0 {
goto exit
}
if s[0] == '-' {
sign=int64(-1)
s = s[1:]
} else if s[0] == '+' {
s = s[1:]
}
parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return
}
if len(parts) == 1 {
f = newFraction(sign*num, 1)
} else if len(parts) == 2 {
subParts := strings.SplitN(parts[1], "(", 2)
if len(subParts) == 1 {
den = 1
dec := parts[1]
lsd := len(dec)
for i:=lsd-1; i>= 0 && dec[i]=='0'; i-- {
lsd--
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
}
f = newFraction(sign*num, den)
} else if len(subParts) == 2 {
sub := num
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
mul *= 10
}
if len(subParts) == 2 {
if s[len(s)-1] != ')' {
goto exit
}
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
}
den *= mul
}
num -= sub
f = newFraction(sign*num, den)
}
}
exit:
if f == nil {
err = errors.New("bad syntax")
}
return
}
func (f *fraction) toFloat() float64 {
return float64(f.num) / float64(f.den)
}
@ -146,12 +275,12 @@ func lcm(a, b int64) (l int64) {
func sumFract(f1, f2 *fraction) (sum *fraction) {
m := lcm(f1.den, f2.den)
sum = newFraction(f1.num*(m/f1.den) + f2.num*(m/f2.den), m)
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
return
}
func mulFract(f1, f2 *fraction) (prod *fraction) {
prod = newFraction(f1.num * f2.num, f1.den * f2.den)
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
return
}

View File

@ -24,11 +24,14 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
}
if IsList(childValue) {
list, _ := childValue.([]any)
v = int64(len(list))
ls, _ := childValue.(*ListType)
v = int64(len(*ls))
} else if IsString(childValue) {
s, _ := childValue.(string)
v = int64(len(s))
} else if IsDict(childValue) {
m, _ := childValue.(map[any]any)
v = int64(len(m))
} else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)

View File

@ -119,7 +119,7 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next()
if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk, nil)
param := newTerm(tk)
args = append(args, param)
tk = scanner.Next()
} else if itemExpected {

View File

@ -17,11 +17,10 @@ func registerTermConstructor(sym Symbol, constructor termContructor) {
constructorRegistry[sym] = constructor
}
func newTerm(tk *Token, parent *term) (inst *term) {
func newTerm(tk *Token) (inst *term) {
if constructorRegistry != nil {
if construct, exists := constructorRegistry[tk.Sym]; exists {
inst = construct(tk)
inst.setParent(parent)
}
}
return

View File

@ -44,6 +44,13 @@ func IsFract(v any) (ok bool) {
return ok
}
func IsRational(v any) (ok bool) {
if _, ok = v.(*fraction); !ok {
_, ok = v.(int64)
}
return ok
}
func IsNumber(v any) (ok bool) {
return IsFloat(v) || IsInteger(v)
}