Compare commits

...

21 Commits

Author SHA1 Message Date
camoroso f028485caa added functions to get the fenerating fraction of a decimal number 2024-05-13 14:24:37 +02:00
camoroso ab07405cda common-errors.go: added errDivisionByZero() 2024-05-13 14:23:21 +02:00
camoroso 47be0c66cf Doc: some typo and better examples 2024-05-11 20:14:54 +02:00
camoroso 50e7168214 Since now builtin functions are registared in a new global context. This reduces the effort to copy the whole set of builtin functions in the context of a function call; only the called function will be copied, if it is global. 2024-05-11 10:45:38 +02:00
camoroso 0a9543543d Remove the unused 'parent' param from the function newTerm(). 2024-05-11 06:41:06 +02:00
camoroso 775751c67b Doc: string examples 2024-05-11 06:35:32 +02:00
camoroso 4aaffd6c44 dict: implemented length of dict 2024-05-10 09:25:18 +02:00
camoroso 924051fbcd extended funcs and list tests 2024-05-10 09:18:32 +02:00
camoroso 5a9b6525a2 new function isRational() that return true is the passed value is integere or fraction 2024-05-10 09:17:51 +02:00
camoroso 8c66d90532 operator-length.go: fixed length of list 2024-05-10 09:16:31 +02:00
camoroso 4aa0113c6a new string test file 2024-05-10 07:33:59 +02:00
camoroso d035fa0d5e operator-dot.go: fixed string index 2024-05-10 07:32:45 +02:00
camoroso cf73b5c98d Doc: add link to dev-expr download page 2024-05-10 06:39:48 +02:00
camoroso 8346e28340 Doc: fixed some typo and added some list details 2024-05-10 04:49:03 +02:00
camoroso 9c2eca40d7 utils.go: added IsBool() and IsFract() 2024-05-10 04:48:13 +02:00
camoroso c3198e4c79 parser_test.go: did not show the correct section name 2024-05-10 04:47:34 +02:00
camoroso 99e0190b9c operator-dot.go: fixed the index range checking 2024-05-10 04:45:51 +02:00
camoroso d4f63a3837 funcs_test.go: fixed and extended 2024-05-10 04:44:08 +02:00
camoroso 389d48b646 func-base.go: added functions to check data-type of a value 2024-05-10 04:43:14 +02:00
camoroso 1f7b9131fc The 'last' variable now also receives the value of the final expression 2024-05-10 04:41:26 +02:00
camoroso dce49fd2b7 Doc: fractions and dictionaries 2024-05-09 07:20:22 +02:00
21 changed files with 767 additions and 186 deletions
+2 -1
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())
@@ -128,6 +128,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
}
if err == nil {
result, err = self.root.compute(ctx)
ctx.setVar(ControlLastResult, result)
}
// } else {
// err = errors.New("empty expression")
+4
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 {
+2 -15
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
+123 -21
View File
@@ -22,17 +22,23 @@ Expressions calculator
toc::[]
#TODO: Work in progress (last update on 2024/05/07, 07:15 am)#
#TODO: Work in progress (last update on 2024/05/10, 06:52 a.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology
#TODO#
image::expression-diagram.png[]
=== `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, beyond in additon to the automatic test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
It cat work as a REPL, *R*ead-*E*xecute-*P*rint-*L*oop, or it can process expression acquired from files or standard input.
It cat work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
The program in located in the _tools_ directory.
Here are some examples of execution.
.Run `dev-expr` in REPL mode and ask for help
@@ -103,11 +109,6 @@ Here are some examples of execution.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
=== Concepts and terminology
#TODO#
image::expression-diagram.png[]
== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
@@ -140,7 +141,44 @@ Numbers can be integers (GO int64) or float (GO float64). In mixed operations in
|===
=== String
=== Fractions
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
.Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` +
[green]`1|2` +
`>>>` [blue]`4|6` +
[green]`2|3` [gray]_Fractions are always reduced to their lowest terms_ +
`>>>` [blue]`1|2 + 2|3` +
[green]`7|6` +
`>>>` [blue]`1|2 * 2|3` +
[green]`1|3` +
`>>>` [blue]`1|2 / 1|3` +
[green]`3|2` +
`>>>` [blue]`1|2 ./ 1|3` [gray]_Force decimal division_ +
[green]`1.5` +
`>>>` [blue]`-1|2` +
[green]`-1|2` +
`>>>` [blue]`1|-2` [gray]_Wrong sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
`>>>` [blue]`1|(-2)` +
[green]`-1|2`
// ----
Fractions can be used together with integers and floats in expressions.
`>>>` [blue]`1|2 + 5` +
[green]`11|2` +
`>>>` [blue]`4 - 1|2` +
[green]`7|2` +
`>>>` [blue]`1.0 + 1|2` +
[green]`1.5` +
=== Strings
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
Some arithmetic operators can also be used with strings.
@@ -156,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.
@@ -208,7 +265,7 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
====
=== List
=== Lists
_Expr_ supports list of mixed-type values, also specified by normal expressions.
.List examples
@@ -221,7 +278,6 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
[ [1,"one"], [2,"two"]] // List of lists
----
.List operators
[cols="^2,^2,5,4"]
|===
@@ -232,8 +288,47 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|===
The items of array can be accessed using the dot `.` operator.
.Item access syntax
[source,bnf]
----
<item> ::= <list-expr>"."<index-expr>
----
.Items of list
`>>>` [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.
.Dictionary examples
[source,go]
----
{1:"one", 2:"two"}
{"one":1, "two": 2}
{"sum":1+2+3, "prod":1*2*3}
----
WARNING: Support for dictionaries is still ongoing.
== Variables
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
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]
@@ -287,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
+186 -60
View File
@@ -535,38 +535,40 @@ pre.rouge .ss {
<ul class="sectlevel1">
<li><a href="#_expr">1. Expr</a>
<ul class="sectlevel2">
<li><a href="#_dev_expr_test_tool">1.1. <code>dev-expr</code> test tool</a></li>
<li><a href="#_concepts_and_terminology">1.2. Concepts and terminology</a></li>
<li><a href="#_concepts_and_terminology">1.1. Concepts and terminology</a></li>
<li><a href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></li>
</ul>
</li>
<li><a href="#_data_types">2. Data types</a>
<ul class="sectlevel2">
<li><a href="#_numbers">2.1. Numbers</a></li>
<li><a href="#_string">2.2. String</a></li>
<li><a href="#_boolean">2.3. Boolean</a></li>
<li><a href="#_list">2.4. List</a></li>
<li><a href="#_fractions">2.2. Fractions</a></li>
<li><a href="#_strings">2.3. Strings</a></li>
<li><a href="#_boolean">2.4. Boolean</a></li>
<li><a href="#_lists">2.5. Lists</a></li>
</ul>
</li>
<li><a href="#_variables">3. Variables</a></li>
<li><a href="#_other_operations">4. Other operations</a>
<li><a href="#_dictionaries">3. Dictionaries</a></li>
<li><a href="#_variables">4. Variables</a></li>
<li><a href="#_other_operations">5. Other operations</a>
<ul class="sectlevel2">
<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="#_operator">5.1. <code class="blue">;</code> operator</a></li>
<li><a href="#_but_operator">5.2. <code class="blue">but</code> operator</a></li>
<li><a href="#_assignment_operator">5.3. Assignment operator <code class="blue">=</code></a></li>
<li><a href="#_selector_operator">5.4. Selector operator <code class="blue">? : ::</code></a></li>
</ul>
</li>
<li><a href="#_priorities_of_operators">5. Priorities of operators</a></li>
<li><a href="#_functions">6. Functions</a>
<li><a href="#_priorities_of_operators">6. Priorities of operators</a></li>
<li><a href="#_functions">7. Functions</a>
<ul class="sectlevel2">
<li><a href="#_function_calls">6.1. Function calls</a></li>
<li><a href="#_function_definitions">6.2. Function definitions</a></li>
<li><a href="#_function_calls">7.1. Function calls</a></li>
<li><a href="#_function_definitions">7.2. Function definitions</a></li>
</ul>
</li>
<li><a href="#_builtins">7. Builtins</a>
<li><a href="#_builtins">8. Builtins</a>
<ul class="sectlevel2">
<li><a href="#_builtin_functions">7.1. Builtin functions</a></li>
<li><a href="#_import">7.2. <em class="blue">import()</em></a></li>
<li><a href="#_builtin_functions">8.1. Builtin functions</a></li>
<li><a href="#_import">8.2. <em class="blue">import()</em></a></li>
</ul>
</li>
</ul>
@@ -577,7 +579,7 @@ pre.rouge .ss {
<div class="sectionbody">
<!-- toc disabled -->
<div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2024/05/07, 07:15 am)</mark></p>
<p><mark>TODO: Work in progress (last update on 2024/05/10, 06:52 a.m.)</mark></p>
</div>
</div>
</div>
@@ -588,16 +590,29 @@ pre.rouge .ss {
<p><em>Expr</em> is a GO package capable of analysing, interpreting and calculating expressions.</p>
</div>
<div class="sect2">
<h3 id="_dev_expr_test_tool"><a class="anchor" href="#_dev_expr_test_tool"></a><a class="link" href="#_dev_expr_test_tool">1.1. <code>dev-expr</code> test tool</a></h3>
<h3 id="_concepts_and_terminology"><a class="anchor" href="#_concepts_and_terminology"></a><a class="link" href="#_concepts_and_terminology">1.1. Concepts and terminology</a></h3>
<div class="paragraph">
<p><mark>TODO</mark></p>
</div>
<div class="imageblock">
<div class="content">
<img src="expression-diagram.png" alt="expression diagram">
</div>
</div>
</div>
<div class="sect2">
<h3 id="_dev_expr_test_tool"><a class="anchor" href="#_dev_expr_test_tool"></a><a class="link" href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></h3>
<div class="paragraph">
<p><code>dev-expr</code> is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, beyond in additon to the automatic test suite based on the Go test framework, <code>dev-expr</code> provides an important aid for quickly testing of new features during their development.</p>
</div>
<div class="paragraph">
<p>It cat work as a REPL, *R*ead-*E*xecute-*P*rint-*L*oop, or it can process expression acquired from files or standard input.</p>
<p>It cat work as a <em>REPL</em>, <em><strong>R</strong>ead-<strong>E</strong>xecute-<strong>P</strong>rint-<strong>L</strong>oop</em>, or it can process expression acquired from files or standard input.</p>
</div>
<div class="paragraph">
<p>The program in located in the <em>tools</em> directory.
Here are some examples of execution.</p>
<p>The program can be downloaded from <a href="https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/">dev-expr</a>.</p>
</div>
<div class="paragraph">
<p>Here are some examples of execution.</p>
</div>
<div class="listingblock">
<div class="title">Run <code>dev-expr</code> in REPL mode and ask for help</div>
@@ -695,17 +710,6 @@ Here are some examples of execution.</p>
</table>
</div>
</div>
<div class="sect2">
<h3 id="_concepts_and_terminology"><a class="anchor" href="#_concepts_and_terminology"></a><a class="link" href="#_concepts_and_terminology">1.2. Concepts and terminology</a></h3>
<div class="paragraph">
<p><mark>TODO</mark></p>
</div>
<div class="imageblock">
<div class="content">
<img src="expression-diagram.png" alt="expression diagram">
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
@@ -787,7 +791,45 @@ Here are some examples of execution.</p>
</table>
</div>
<div class="sect2">
<h3 id="_string"><a class="anchor" href="#_string"></a><a class="link" href="#_string">2.2. String</a></h3>
<h3 id="_fractions"><a class="anchor" href="#_fractions"></a><a class="link" href="#_fractions">2.2. Fractions</a></h3>
<div class="paragraph">
<p><em>Expr</em> also supports fractions. Fraction literals are made with two integers separated by a vertical bar <code>|</code>.</p>
</div>
<div class="paragraph">
<div class="title">Examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">1 | 2</code><br>
<code class="green">1|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">4|6</code><br>
<code class="green">2|3</code> <em class="gray">Fractions are always reduced to their lowest terms</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1|2 + 2|3</code><br>
<code class="green">7|6</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1|2 * 2|3</code><br>
<code class="green">1|3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1|2 / 1|3</code><br>
<code class="green">3|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1|2 ./ 1|3</code> <em class="gray">Force decimal division</em><br>
<code class="green">1.5</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">-1|2</code><br>
<code class="green">-1|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1|-2</code> <em class="gray">Wrong sign specification</em><br>
<em class="red">Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1</em><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1|(-2)</code><br>
<code class="green">-1|2</code></p>
</div>
<div class="paragraph">
<p>Fractions can be used together with integers and floats in expressions.</p>
</div>
<div class="paragraph">
<p><code>&gt;&gt;&gt;</code> <code class="blue">1|2 + 5</code><br>
<code class="green">11|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">4 - 1|2</code><br>
<code class="green">7|2</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">1.0 + 1|2</code><br>
<code class="green">1.5</code><br></p>
</div>
</div>
<div class="sect2">
<h3 id="_strings"><a class="anchor" href="#_strings"></a><a class="link" href="#_strings">2.3. Strings</a></h3>
<div class="paragraph">
<p>Strings are character sequences enclosed between two double quote <code class="blue">"</code>. Example: <code class="blue">"I&#8217;m a string"</code>.</p>
</div>
@@ -826,9 +868,31 @@ Here are some examples of execution.</p>
</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.3. Boolean</a></h3>
<h3 id="_boolean"><a class="anchor" href="#_boolean"></a><a class="link" href="#_boolean">2.4. Boolean</a></h3>
<div class="paragraph">
<p>Boolean data type has two values only: <em>true</em> and <em>false</em>. Relational and Boolean expressions produce Boolean values.</p>
</div>
@@ -963,7 +1027,7 @@ Here are some examples of execution.</p>
</div>
</div>
<div class="sect2">
<h3 id="_list"><a class="anchor" href="#_list"></a><a class="link" href="#_list">2.4. List</a></h3>
<h3 id="_lists"><a class="anchor" href="#_lists"></a><a class="link" href="#_lists">2.5. Lists</a></h3>
<div class="paragraph">
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions.</p>
</div>
@@ -1008,14 +1072,68 @@ Here are some examples of execution.</p>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>The items of array 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;list-expr&gt;"."&lt;index-expr&gt;</code></pre>
</div>
</div>
<div class="paragraph">
<div class="title">Items of list</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>
</div>
<div class="sect1">
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
<h2 id="_dictionaries"><a class="anchor" href="#_dictionaries"></a><a class="link" href="#_dictionaries">3. Dictionaries</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>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">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>
<span class="p">{</span><span class="s">"sum"</span><span class="o">:</span><span class="m">1</span><span class="o">+</span><span class="m">2</span><span class="o">+</span><span class="m">3</span><span class="p">,</span> <span class="s">"prod"</span><span class="o">:</span><span class="m">1</span><span class="o">*</span><span class="m">2</span><span class="o">*</span><span class="m">3</span><span class="p">}</span></code></pre>
</div>
</div>
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
Support for dictionaries is still ongoing.
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<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 Go <em>ExprContext</em> interface, e.g. <em>SimpleVarStore</em> or <em>SimpleFuncStore</em>.</p>
</div>
<div class="listingblock">
<div class="title">Examples</div>
@@ -1028,10 +1146,10 @@ Here are some examples of execution.</p>
</div>
</div>
<div class="sect1">
<h2 id="_other_operations"><a class="anchor" href="#_other_operations"></a><a class="link" href="#_other_operations">4. Other operations</a></h2>
<h2 id="_other_operations"><a class="anchor" href="#_other_operations"></a><a class="link" href="#_other_operations">5. Other operations</a></h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_operator"><a class="anchor" href="#_operator"></a><a class="link" href="#_operator">4.1. <code class="blue">;</code> operator</a></h3>
<h3 id="_operator"><a class="anchor" href="#_operator"></a><a class="link" href="#_operator">5.1. <code class="blue">;</code> operator</a></h3>
<div class="paragraph">
<p>The semicolon operator <code class="blue">;</code> is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.</p>
</div>
@@ -1067,7 +1185,7 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
</div>
</div>
<div class="sect2">
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">4.2. <code class="blue">but</code> operator</a></h3>
<h3 id="_but_operator"><a class="anchor" href="#_but_operator"></a><a class="link" href="#_but_operator">5.2. <code class="blue">but</code> operator</a></h3>
<div class="paragraph">
<p><code class="blue">but</code> is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: <code class="blue">5 but 2</code> returns 2, <code class="blue">x=2*3 but x-1</code> returns 5.</p>
</div>
@@ -1076,7 +1194,7 @@ Technically <code class="blue">;</code> is not treated as a real operator. It ac
</div>
</div>
<div class="sect2">
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">4.3. Assignment operator <code class="blue">=</code></a></h3>
<h3 id="_assignment_operator"><a class="anchor" href="#_assignment_operator"></a><a class="link" href="#_assignment_operator">5.3. Assignment operator <code class="blue">=</code></a></h3>
<div class="paragraph">
<p>The assignment operator <code class="blue">=</code> is used to define variables in the evaluation context or to change their value (see <em>ExprContext</em>).
The value on the left side of <code class="blue">=</code> must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.</p>
@@ -1089,7 +1207,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
</div>
<div class="sect2">
<h3 id="_selector_operator"><a class="anchor" href="#_selector_operator"></a><a class="link" href="#_selector_operator">4.4. Selector operator <code class="blue">? : ::</code></a></h3>
<h3 id="_selector_operator"><a class="anchor" href="#_selector_operator"></a><a class="link" href="#_selector_operator">5.4. Selector operator <code class="blue">? : ::</code></a></h3>
<div class="paragraph">
<p>The <em>selector operator</em> is very similar to the <em>switch/case/default</em> statement available in many programming languages.</p>
</div>
@@ -1105,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>
@@ -1116,20 +1234,28 @@ 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>
</div>
</div>
<div class="sect1">
<h2 id="_priorities_of_operators"><a class="anchor" href="#_priorities_of_operators"></a><a class="link" href="#_priorities_of_operators">5. Priorities of operators</a></h2>
<h2 id="_priorities_of_operators"><a class="anchor" href="#_priorities_of_operators"></a><a class="link" href="#_priorities_of_operators">6. Priorities of operators</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>The table below shows all supported operators by decreasing priorities.</p>
@@ -1350,7 +1476,7 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
</div>
<div class="sect1">
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2>
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">7. Functions</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>Functions in <em>Expr</em> are very similar to functions in many programming languages.</p>
@@ -1359,13 +1485,13 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
<p>In <em>Expr</em> functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator <code class="blue">@</code> it is possibile to export local definition to the calling context.</p>
</div>
<div class="sect2">
<h3 id="_function_calls"><a class="anchor" href="#_function_calls"></a><a class="link" href="#_function_calls">6.1. Function calls</a></h3>
<h3 id="_function_calls"><a class="anchor" href="#_function_calls"></a><a class="link" href="#_function_calls">7.1. Function calls</a></h3>
<div class="paragraph">
<p><mark>TODO: function calls operations</mark></p>
</div>
</div>
<div class="sect2">
<h3 id="_function_definitions"><a class="anchor" href="#_function_definitions"></a><a class="link" href="#_function_definitions">6.2. Function definitions</a></h3>
<h3 id="_function_definitions"><a class="anchor" href="#_function_definitions"></a><a class="link" href="#_function_definitions">7.2. Function definitions</a></h3>
<div class="paragraph">
<p><mark>TODO: function definitions operations</mark></p>
</div>
@@ -1373,17 +1499,17 @@ The value on the left side of <code class="blue">=</code> must be an identifier.
</div>
</div>
<div class="sect1">
<h2 id="_builtins"><a class="anchor" href="#_builtins"></a><a class="link" href="#_builtins">7. Builtins</a></h2>
<h2 id="_builtins"><a class="anchor" href="#_builtins"></a><a class="link" href="#_builtins">8. Builtins</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><mark>TODO: builtins</mark></p>
</div>
<div class="sect2">
<h3 id="_builtin_functions"><a class="anchor" href="#_builtin_functions"></a><a class="link" href="#_builtin_functions">7.1. Builtin functions</a></h3>
<h3 id="_builtin_functions"><a class="anchor" href="#_builtin_functions"></a><a class="link" href="#_builtin_functions">8.1. Builtin functions</a></h3>
</div>
<div class="sect2">
<h3 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import">7.2. <em class="blue">import()</em></a></h3>
<h3 id="_import"><a class="anchor" href="#_import"></a><a class="link" href="#_import">8.2. <em class="blue">import()</em></a></h3>
<div class="paragraph">
<p><em class="blue">import(<span class="grey">&lt;source-file&gt;</span>)</em> loads the multi-expression contained in the specified source and returns its value.</p>
</div>
@@ -1393,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-08 07:50:05 +0200
Last updated 2024-05-11 20:13:17 +0200
</div>
</div>
</body>
+143 -23
View File
@@ -18,29 +18,137 @@ func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error)
return
}
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsInteger(args[0])
return
}
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsFloat(args[0])
return
}
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsBool(args[0])
return
}
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsString(args[0])
return
}
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsFract(args[0])
return
}
func 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
}
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = IsDict(args[0])
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
switch v := args[0].(type) {
case int64:
result = v
case float64:
result = int64(math.Trunc(v))
case bool:
if v {
result = int64(1)
} else {
result = int64(0)
}
case string:
var i int
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
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
}
@@ -50,8 +158,20 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("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() {
+16 -5
View File
@@ -20,7 +20,7 @@ func TestFuncs(t *testing.T) {
/* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
/* 12 */ {`two=func(){2}; two()`, int64(2), nil},
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
@@ -54,13 +54,24 @@ func TestFuncs(t *testing.T) {
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one","two","three"), nil},
/* 45 */ {`["a", "b", "c"]`, newListA("a","b","c"), nil},
/* 46 */ {`["a", "b", "c"]`, newList([]any{"a","b","c"}), nil},
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
/* 45 */ {`isInt(2+1)`, true, nil},
/* 46 */ {`isInt(3.1)`, false, nil},
/* 47 */ {`isFloat(3.1)`, true, nil},
/* 48 */ {`isString("3.1")`, true, nil},
/* 49 */ {`isString("3" + 1)`, true, nil},
/* 50 */ {`isList(["3", 1])`, true, nil},
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 52 */ {`isFract(1|3)`, true, nil},
/* 53 */ {`isFract(3|1)`, false, nil},
/* 54 */ {`isRational(3|1)`, true, nil},
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTest(t, "Func", inputs[25:26])
//parserTest(t, "Func", inputs[54:55])
parserTest(t, "Func", inputs)
}
+54
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()
}
+20 -16
View File
@@ -20,22 +20,26 @@ func TestListParser(t *testing.T) {
}
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 14 */ {`[1,2,3].1`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 14 */ {`[1,2,3].1`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 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},
+23 -24
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 {
+5 -2
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)
}
+2 -2
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)
+2 -2
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)
+4 -3
View File
@@ -22,10 +22,11 @@ func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err
var indexValue any
if indexValue, err = indexTerm.compute(ctx); err == nil {
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
if v >= 0 && v < maxValue {
index = v
} else if index >= -maxValue {
index = maxValue + v
} else {
err = indexTerm.Errorf("index %d out of bounds", v)
}
@@ -56,7 +57,7 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
case string:
var index int
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
v = unboxedValue[index]
v = string(unboxedValue[index])
}
case map[any]any:
var ok bool
+131 -2
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
}
+5 -2
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)
+1 -1
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 {
+5 -5
View File
@@ -35,10 +35,10 @@ func TestGeneralParser(t *testing.T) {
/* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* *17 */ {`"uno" + "due"`, `unodue`, nil},
/* *18 */ {`"uno" + 2`, `uno2`, nil},
/* *19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* *20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil},
@@ -177,7 +177,7 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
ctx := NewSimpleFuncStore()
parser := NewParser(ctx)
logTest(t, i+1, "Iterator", input.source, input.wantResult, input.wantErr)
logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// strings_test.go
package expr
import (
"testing"
)
func TestStringsParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
/* 2 */ {`"uno" + 2`, `uno2`, nil},
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 5 */ {`"abc".1`, `b`, nil},
/* 5 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
}
+1 -2
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
+17
View File
@@ -24,6 +24,11 @@ func IsFloat(v any) (ok bool) {
return ok
}
func IsBool(v any) (ok bool) {
_, ok = v.(bool)
return ok
}
func IsList(v any) (ok bool) {
_, ok = v.(*ListType)
return ok
@@ -34,6 +39,18 @@ func IsDict(v any) (ok bool) {
return ok
}
func IsFract(v any) (ok bool) {
_, ok = v.(*fraction)
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)
}