6289 lines
237 KiB
Plaintext
6289 lines
237 KiB
Plaintext
C++ Mathematical Expression Toolkit Library Documentation
|
|
|
|
Section 00 - Introduction
|
|
Section 01 - Capabilities
|
|
Section 02 - Example Expressions
|
|
Section 03 - Copyright Notice
|
|
Section 04 - Downloads & Updates
|
|
Section 05 - Installation
|
|
Section 06 - Compilation
|
|
Section 07 - Compiler Compatibility
|
|
Section 08 - Built-In Operations & Functions
|
|
Section 09 - Fundamental Types
|
|
Section 10 - Components
|
|
Section 11 - Compilation Options
|
|
Section 12 - Expression Structures
|
|
Section 13 - Variable, Vector & String Definition
|
|
Section 14 - Vector Processing
|
|
Section 15 - User Defined Functions
|
|
Section 16 - Expression Dependents
|
|
Section 17 - Hierarchies Of Symbol Tables
|
|
Section 18 - Unknown Unknowns
|
|
Section 19 - Enabling & Disabling Features
|
|
Section 20 - Expression Return Values
|
|
Section 21 - Compilation Errors
|
|
Section 22 - Runtime Library Packages
|
|
Section 23 - Helpers & Utils
|
|
Section 24 - Runtime Checks
|
|
Section 25 - Benchmarking
|
|
Section 26 - Exprtk Notes
|
|
Section 27 - Simple Exprtk Example
|
|
Section 28 - Build Options
|
|
Section 29 - Files
|
|
Section 30 - Language Structure
|
|
|
|
|
|
[SECTION 00 - INTRODUCTION]
|
|
The C++ Mathematical Expression Toolkit Library (ExprTk) is a simple
|
|
to use, easy to integrate and extremely efficient run-time
|
|
mathematical expression parsing and evaluation engine. The parsing
|
|
engine supports numerous forms of functional and logic processing
|
|
semantics and is easily extensible.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 01 - CAPABILITIES]
|
|
The ExprTk expression evaluator supports the following fundamental
|
|
arithmetic operations, functions and processes:
|
|
|
|
(00) Types: Scalar, Vector, String
|
|
|
|
(01) Basic operators: +, -, *, /, %, ^
|
|
|
|
(02) Assignment: :=, +=, -=, *=, /=, %=
|
|
|
|
(03) Equalities &
|
|
Inequalities: =, ==, <>, !=, <, <=, >, >=
|
|
|
|
(04) Logic operators: and, mand, mor, nand, nor, not, or, shl, shr,
|
|
xnor, xor, true, false
|
|
|
|
(05) Functions: abs, avg, ceil, clamp, equal, erf, erfc, exp,
|
|
expm1, floor, frac, log, log10, log1p, log2,
|
|
logn, max, min, mul, ncdf, not_equal, root,
|
|
round, roundn, sgn, sqrt, sum, swap, trunc
|
|
|
|
(06) Trigonometry: acos, acosh, asin, asinh, atan, atanh, atan2,
|
|
cos, cosh, cot, csc, sec, sin, sinc, sinh,
|
|
tan, tanh, hypot, rad2deg, deg2grad, deg2rad,
|
|
grad2deg
|
|
|
|
(07) Control
|
|
structures: if-then-else, ternary conditional, switch-case,
|
|
return-statement
|
|
|
|
(08) Loop statements: while, for, repeat-until, break, continue
|
|
|
|
(09) String
|
|
processing: in, like, ilike, concatenation
|
|
|
|
(10) Optimisations: constant-folding, simple strength reduction and
|
|
dead code elimination
|
|
|
|
(11) Runtime checks: vector bounds, string bounds, loop iteration,
|
|
execution-time bounds and compilation process
|
|
checkpointing, assert statements
|
|
|
|
(12) Calculus: numerical integration and differentiation
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 02 - EXAMPLE EXPRESSIONS]
|
|
The following is a short listing of infix format based mathematical
|
|
expressions that can be parsed and evaluated using the ExprTk library.
|
|
|
|
(01) sqrt(1 - (3 / x^2))
|
|
(02) clamp(-1, sin(2 * pi * x) + cos(y / 2 * pi), +1)
|
|
(03) sin(2.34e-3 * x)
|
|
(04) if(((x[2] + 2) == 3) and ((y + 5) <= 9),1 + w, 2 / z)
|
|
(05) inrange(-2,m,+2) == if(({-2 <= m} and [m <= +2]),1,0)
|
|
(06) ({1/1}*[1/2]+(1/3))-{1/4}^[1/5]+(1/6)-({1/7}+[1/8]*(1/9))
|
|
(07) a * exp(2.2 / 3.3 * t) + c
|
|
(08) z := x + sin(2.567 * pi / y)
|
|
(09) u := 2.123 * {pi * z} / (w := x + cos(y / pi))
|
|
(10) 2x + 3y + 4z + 5w == 2 * x + 3 * y + 4 * z + 5 * w
|
|
(11) 3(x + y) / 2.9 + 1.234e+12 == 3 * (x + y) / 2.9 + 1.234e+12
|
|
(12) (x + y)3.3 + 1 / 4.5 == [x + y] * 3.3 + 1 / 4.5
|
|
(13) (x + y[i])z + 1.1 / 2.7 == (x + y[i]) * z + 1.1 / 2.7
|
|
(14) (sin(x / pi) cos(2y) + 1) == (sin(x / pi) * cos(2 * y) + 1)
|
|
(15) 75x^17 + 25.1x^5 - 35x^4 - 15.2x^3 + 40x^2 - 15.3x + 1
|
|
(16) (avg(x,y) <= x + y ? x - y : x * y) + 2.345 * pi / x
|
|
(17) while (x <= 100) { x -= 1; }
|
|
(18) x <= 'abc123' and (y in 'AString') or ('1x2y3z' != z)
|
|
(19) ((x + 'abc') like '*123*') or ('a123b' ilike y)
|
|
(20) sgn(+1.2^3.4z / -5.6y) <= {-7.8^9 / -10.11x }
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 03 - COPYRIGHT NOTICE]
|
|
Free use of the C++ Mathematical Expression Toolkit Library is
|
|
permitted under the guidelines and in accordance with the most current
|
|
version of the MIT License.
|
|
|
|
(1) https://www.opensource.org/licenses/MIT
|
|
(2) SPDX-License-Identifier: MIT
|
|
(3) SPDX-FileCopyrightText : Copyright (C) 1999-2024 Arash Partow
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 04 - DOWNLOADS & UPDATES]
|
|
The most recent version of the C++ Mathematical Expression Toolkit
|
|
Library including all updates and tests can be found at the following
|
|
locations:
|
|
|
|
(1) Download: https://www.partow.net/programming/exprtk/index.html
|
|
(2) Mirror Repository: https://github.com/ArashPartow/exprtk
|
|
https://github.com/ArashPartow/exprtk-extras
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 05 - INSTALLATION]
|
|
The header file exprtk.hpp should be placed in a project or system
|
|
include path (e.g: /usr/include/).
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 06 - COMPILATION]
|
|
The ExprTk package contains the ExprTk header, a set of simple
|
|
examples and a benchmark and unit test suite. The following is a list
|
|
of commands to build the various components:
|
|
|
|
(a) For a complete build: make clean all
|
|
(b) For a PGO build: make clean pgo
|
|
(c) To strip executables: make strip_bin
|
|
(d) Execute valgrind check: make valgrind_check
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 07 - COMPILER COMPATIBILITY]
|
|
ExprTk has been built error and warning free using the following set
|
|
of C++ compilers:
|
|
|
|
(*) GNU Compiler Collection (3.5+)
|
|
(*) Clang/LLVM (1.1+)
|
|
(*) Microsoft Visual Studio C++ Compiler (7.1+)
|
|
(*) Intel C++ Compiler (8.x+)
|
|
(*) AMD Optimizing C++ Compiler (1.2+)
|
|
(*) Nvidia C++ Compiler (19.x+)
|
|
(*) PGI C++ (10.x+)
|
|
(*) Circle C++ (circa: b81c37d2bb227c)
|
|
(*) IBM XL C/C++ (9.x+)
|
|
(*) C++ Builder (XE4+)
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 08 - BUILT-IN OPERATIONS & FUNCTIONS]
|
|
|
|
(0) Arithmetic & Assignment Operators
|
|
+----------+---------------------------------------------------------+
|
|
| OPERATOR | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| + | Addition between x and y. (eg: x + y) |
|
|
+----------+---------------------------------------------------------+
|
|
| - | Subtraction between x and y. (eg: x - y) |
|
|
+----------+---------------------------------------------------------+
|
|
| * | Multiplication between x and y. (eg: x * y) |
|
|
+----------+---------------------------------------------------------+
|
|
| / | Division between x and y. (eg: x / y) |
|
|
+----------+---------------------------------------------------------+
|
|
| % | Modulus of x with respect to y. (eg: x % y) |
|
|
+----------+---------------------------------------------------------+
|
|
| ^ | x to the power of y. (eg: x ^ y) |
|
|
+----------+---------------------------------------------------------+
|
|
| := | Assign the value of x to y. Where y is either a variable|
|
|
| | or vector type. (eg: y := x) |
|
|
+----------+---------------------------------------------------------+
|
|
| += | Increment x by the value of the expression on the right |
|
|
| | hand side. Where x is either a variable or vector type. |
|
|
| | (eg: x += abs(y - z)) |
|
|
+----------+---------------------------------------------------------+
|
|
| -= | Decrement x by the value of the expression on the right |
|
|
| | hand side. Where x is either a variable or vector type. |
|
|
| | (eg: x[i] -= abs(y + z)) |
|
|
+----------+---------------------------------------------------------+
|
|
| *= | Assign the multiplication of x by the value of the |
|
|
| | expression on the righthand side to x. Where x is either|
|
|
| | a variable or vector type. |
|
|
| | (eg: x *= abs(y / z)) |
|
|
+----------+---------------------------------------------------------+
|
|
| /= | Assign the division of x by the value of the expression |
|
|
| | on the right-hand side to x. Where x is either a |
|
|
| | variable or vector type. (eg: x[i + j] /= abs(y * z)) |
|
|
+----------+---------------------------------------------------------+
|
|
| %= | Assign x modulo the value of the expression on the right|
|
|
| | hand side to x. Where x is either a variable or vector |
|
|
| | type. (eg: x[2] %= y ^ 2) |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
(1) Equalities & Inequalities
|
|
+----------+---------------------------------------------------------+
|
|
| OPERATOR | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| == or = | True only if x is strictly equal to y. (eg: x == y) |
|
|
+----------+---------------------------------------------------------+
|
|
| <> or != | True only if x does not equal y. (eg: x <> y or x != y) |
|
|
+----------+---------------------------------------------------------+
|
|
| < | True only if x is less than y. (eg: x < y) |
|
|
+----------+---------------------------------------------------------+
|
|
| <= | True only if x is less than or equal to y. (eg: x <= y) |
|
|
+----------+---------------------------------------------------------+
|
|
| > | True only if x is greater than y. (eg: x > y) |
|
|
+----------+---------------------------------------------------------+
|
|
| >= | True only if x greater than or equal to y. (eg: x >= y) |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
(2) Boolean Operations
|
|
+----------+---------------------------------------------------------+
|
|
| OPERATOR | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| true | True state or any value other than zero (typically 1). |
|
|
+----------+---------------------------------------------------------+
|
|
| false | False state, value of exactly zero. |
|
|
+----------+---------------------------------------------------------+
|
|
| and | Logical AND, True only if x and y are both true. |
|
|
| | (eg: x and y) |
|
|
+----------+---------------------------------------------------------+
|
|
| mand | Multi-input logical AND, True only if all inputs are |
|
|
| | true. Left to right short-circuiting of expressions. |
|
|
| | (eg: mand(x > y, z < w, u or v, w and x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| mor | Multi-input logical OR, True if at least one of the |
|
|
| | inputs are true. Left to right short-circuiting of |
|
|
| | expressions. (eg: mor(x > y, z < w, u or v, w and x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| nand | Logical NAND, True only if either x or y is false. |
|
|
| | (eg: x nand y) |
|
|
+----------+---------------------------------------------------------+
|
|
| nor | Logical NOR, True only if the result of x or y is false |
|
|
| | (eg: x nor y) |
|
|
+----------+---------------------------------------------------------+
|
|
| not | Logical NOT, Negate the logical sense of the input. |
|
|
| | (eg: not(x and y) == x nand y) |
|
|
+----------+---------------------------------------------------------+
|
|
| or | Logical OR, True if either x or y is true. (eg: x or y) |
|
|
+----------+---------------------------------------------------------+
|
|
| xor | Logical XOR, True only if the logical states of x and y |
|
|
| | differ. (eg: x xor y) |
|
|
+----------+---------------------------------------------------------+
|
|
| xnor | Logical XNOR, True iff the biconditional of x and y is |
|
|
| | satisfied. (eg: x xnor y) |
|
|
+----------+---------------------------------------------------------+
|
|
| & | Similar to AND but with left to right expression short |
|
|
| | circuiting optimisation. (eg: (x & y) == (y and x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| | | Similar to OR but with left to right expression short |
|
|
| | circuiting optimisation. (eg: (x | y) == (y or x)) |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
(3) General Purpose Functions
|
|
+----------+---------------------------------------------------------+
|
|
| FUNCTION | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| abs | Absolute value of x. (eg: abs(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| avg | Average of all the inputs. |
|
|
| | (eg: avg(x,y,z,w,u,v) == (x + y + z + w + u + v) / 6) |
|
|
+----------+---------------------------------------------------------+
|
|
| ceil | Smallest integer that is greater than or equal to x. |
|
|
+----------+---------------------------------------------------------+
|
|
| clamp | Clamp x in range between r0 and r1, where r0 < r1. |
|
|
| | (eg: clamp(r0,x,r1)) |
|
|
+----------+---------------------------------------------------------+
|
|
| equal | Equality test between x and y using normalised epsilon |
|
|
+----------+---------------------------------------------------------+
|
|
| erf | Error function of x. (eg: erf(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| erfc | Complimentary error function of x. (eg: erfc(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| exp | e to the power of x. (eg: exp(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| expm1 | e to the power of x minus 1, where x is very small. |
|
|
| | (eg: expm1(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| floor | Largest integer that is less than or equal to x. |
|
|
| | (eg: floor(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| frac | Fractional portion of x. (eg: frac(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| hypot | Hypotenuse of x and y (eg: hypot(x,y) = sqrt(x*x + y*y))|
|
|
+----------+---------------------------------------------------------+
|
|
| iclamp | Inverse-clamp x outside of the range r0 and r1. Where |
|
|
| | r0 < r1. If x is within the range it will snap to the |
|
|
| | closest bound. (eg: iclamp(r0,x,r1) |
|
|
+----------+---------------------------------------------------------+
|
|
| inrange | In-range returns 'true' when x is within the range r0 |
|
|
| | and r1. Where r0 < r1. (eg: inrange(r0,x,r1) |
|
|
+----------+---------------------------------------------------------+
|
|
| log | Natural logarithm of x. (eg: log(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| log10 | Base 10 logarithm of x. (eg: log10(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| log1p | Natural logarithm of 1 + x, where x is very small. |
|
|
| | (eg: log1p(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| log2 | Base 2 logarithm of x. (eg: log2(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| logn | Base N logarithm of x. where n is a positive integer. |
|
|
| | (eg: logn(x,8)) |
|
|
+----------+---------------------------------------------------------+
|
|
| max | Largest value of all the inputs. (eg: max(x,y,z,w,u,v)) |
|
|
+----------+---------------------------------------------------------+
|
|
| min | Smallest value of all the inputs. (eg: min(x,y,z,w,u)) |
|
|
+----------+---------------------------------------------------------+
|
|
| mul | Product of all the inputs. |
|
|
| | (eg: mul(x,y,z,w,u,v,t) == (x * y * z * w * u * v * t)) |
|
|
+----------+---------------------------------------------------------+
|
|
| ncdf | Normal cumulative distribution function. (eg: ncdf(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| not_equal| Not-equal test between x and y using normalised epsilon |
|
|
+----------+---------------------------------------------------------+
|
|
| pow | x to the power of y. (eg: pow(x,y) == x ^ y) |
|
|
+----------+---------------------------------------------------------+
|
|
| root | Nth-Root of x. where n is a positive integer. |
|
|
| | (eg: root(x,3) == x^(1/3)) |
|
|
+----------+---------------------------------------------------------+
|
|
| round | Round x to the nearest integer. (eg: round(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| roundn | Round x to n decimal places (eg: roundn(x,3)) |
|
|
| | where n > 0 and is an integer. |
|
|
| | (eg: roundn(1.2345678,4) == 1.2346) |
|
|
+----------+---------------------------------------------------------+
|
|
| sgn | Sign of x, -1 where x < 0, +1 where x > 0, else zero. |
|
|
| | (eg: sgn(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| sqrt | Square root of x, where x >= 0. (eg: sqrt(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| sum | Sum of all the inputs. |
|
|
| | (eg: sum(x,y,z,w,u,v,t) == (x + y + z + w + u + v + t)) |
|
|
+----------+---------------------------------------------------------+
|
|
| swap | Swap the values of the variables x and y and return the |
|
|
| <=> | current value of y. (eg: swap(x,y) or x <=> y) |
|
|
+----------+---------------------------------------------------------+
|
|
| trunc | Integer portion of x. (eg: trunc(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
(4) Trigonometry Functions
|
|
+----------+---------------------------------------------------------+
|
|
| FUNCTION | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| acos | Arc cosine of x expressed in radians. Interval [-1,+1] |
|
|
| | (eg: acos(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| acosh | Inverse hyperbolic cosine of x expressed in radians. |
|
|
| | (eg: acosh(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| asin | Arc sine of x expressed in radians. Interval [-1,+1] |
|
|
| | (eg: asin(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| asinh | Inverse hyperbolic sine of x expressed in radians. |
|
|
| | (eg: asinh(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| atan | Arc tangent of x expressed in radians. Interval [-1,+1] |
|
|
| | (eg: atan(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| atan2 | Arc tangent of (x / y) expressed in radians. [-pi,+pi] |
|
|
| | eg: atan2(x,y) |
|
|
+----------+---------------------------------------------------------+
|
|
| atanh | Inverse hyperbolic tangent of x expressed in radians. |
|
|
| | (eg: atanh(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| cos | Cosine of x. (eg: cos(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| cosh | Hyperbolic cosine of x. (eg: cosh(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| cot | Cotangent of x. (eg: cot(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| csc | Cosecant of x. (eg: csc(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| sec | Secant of x. (eg: sec(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| sin | Sine of x. (eg: sin(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| sinc | Sine cardinal of x. (eg: sinc(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| sinh | Hyperbolic sine of x. (eg: sinh(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| tan | Tangent of x. (eg: tan(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| tanh | Hyperbolic tangent of x. (eg: tanh(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| deg2rad | Convert x from degrees to radians. (eg: deg2rad(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| deg2grad | Convert x from degrees to gradians. (eg: deg2grad(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| rad2deg | Convert x from radians to degrees. (eg: rad2deg(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
| grad2deg | Convert x from gradians to degrees. (eg: grad2deg(x)) |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
(5) String Processing
|
|
+----------+---------------------------------------------------------+
|
|
| FUNCTION | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| = , == | All common equality/inequality operators are applicable |
|
|
| !=, <> | to strings and are applied in a case sensitive manner. |
|
|
| <=, >= | In the following example x, y and z are of type string. |
|
|
| < , > | (eg: not((x <= 'AbC') and ('1x2y3z' <> y)) or (z == x) |
|
|
+----------+---------------------------------------------------------+
|
|
| in | True only if x is a substring of y. |
|
|
| | (eg: x in y or 'abc' in 'abcdefgh') |
|
|
+----------+---------------------------------------------------------+
|
|
| like | True only if the string x matches the pattern y. |
|
|
| | Available wildcard characters are '*' and '?' denoting |
|
|
| | zero or more and zero or one matches respectively. |
|
|
| | (eg: x like y or 'abcdefgh' like 'a?d*h') |
|
|
+----------+---------------------------------------------------------+
|
|
| ilike | True only if the string x matches the pattern y in a |
|
|
| | case insensitive manner. Available wildcard characters |
|
|
| | are '*' and '?' denoting zero or more and zero or one |
|
|
| | matches respectively. |
|
|
| | (eg: x ilike y or 'a1B2c3D4e5F6g7H' ilike 'a?d*h') |
|
|
+----------+---------------------------------------------------------+
|
|
| [r0:r1] | The closed interval[r0,r1] of the specified string. |
|
|
| | eg: Given a string x with a value of 'abcdefgh' then: |
|
|
| | 1. x[1:4] == 'bcde' |
|
|
| | 2. x[ :4] == x[:8 / 2] == 'abcde' |
|
|
| | 3. x[2 + 1: ] == x[3:] =='defgh' |
|
|
| | 4. x[ : ] == x[:] == 'abcdefgh' |
|
|
| | 5. x[4/2:3+1] == x[2:4] == 'cde' |
|
|
| | |
|
|
| | Note: Both r0 and r1 are assumed to be integers, where |
|
|
| | r0 <= r1. They may also be the result of an expression, |
|
|
| | in the event they have fractional components truncation |
|
|
| | shall be performed. (eg: 1.67 --> 1) |
|
|
+----------+---------------------------------------------------------+
|
|
| := | Assign the value of x to y. Where y is a mutable string |
|
|
| | or string range and x is either a string or a string |
|
|
| | range. eg: |
|
|
| | 1. y := x |
|
|
| | 2. y := 'abc' |
|
|
| | 3. y := x[:i + j] |
|
|
| | 4. y := '0123456789'[2:7] |
|
|
| | 5. y := '0123456789'[2i + 1:7] |
|
|
| | 6. y := (x := '0123456789'[2:7]) |
|
|
| | 7. y[i:j] := x |
|
|
| | 8. y[i:j] := (x + 'abcdefg'[8 / 4:5])[m:n] |
|
|
| | |
|
|
| | Note: For options 7 and 8 the shorter of the two ranges |
|
|
| | will denote the number characters that are to be copied.|
|
|
+----------+---------------------------------------------------------+
|
|
| + | Concatenation of x and y. Where x and y are strings or |
|
|
| | string ranges. eg |
|
|
| | 1. x + y |
|
|
| | 2. x + 'abc' |
|
|
| | 3. x + y[:i + j] |
|
|
| | 4. x[i:j] + y[2:3] + '0123456789'[2:7] |
|
|
| | 5. 'abc' + x + y |
|
|
| | 6. 'abc' + '1234567' |
|
|
| | 7. (x + 'a1B2c3D4' + y)[i:2j] |
|
|
+----------+---------------------------------------------------------+
|
|
| += | Append to x the value of y. Where x is a mutable string |
|
|
| | and y is either a string or a string range. eg: |
|
|
| | 1. x += y |
|
|
| | 2. x += 'abc' |
|
|
| | 3. x += y[:i + j] + 'abc' |
|
|
| | 4. x += '0123456789'[2:7] |
|
|
+----------+---------------------------------------------------------+
|
|
| <=> | Swap the values of x and y. Where x and y are mutable |
|
|
| | strings. (eg: x <=> y) |
|
|
+----------+---------------------------------------------------------+
|
|
| [] | The string size operator returns the size of the string |
|
|
| | being actioned. |
|
|
| | eg: |
|
|
| | 1. 'abc'[] == 3 |
|
|
| | 2. var max_str_length := max(s0[], s1[], s2[], s3[]) |
|
|
| | 3. ('abc' + 'd')[] == 6 |
|
|
| | 4. (('abc' + 'xyz')[1:4])[] == 4 |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
(6) Control Structures
|
|
+----------+---------------------------------------------------------+
|
|
|STRUCTURE | DEFINITION |
|
|
+----------+---------------------------------------------------------+
|
|
| if | If x is true then return y else return z. |
|
|
| | eg: |
|
|
| | 1. if (x, y, z) |
|
|
| | 2. if ((x + 1) > 2y, z + 1, w / v) |
|
|
| | 3. if (x > y) z; |
|
|
| | 4. if (x <= 2*y) { z + w }; |
|
|
+----------+---------------------------------------------------------+
|
|
| if-else | The if-else/else-if statement. Subject to the condition |
|
|
| | branch the statement will return either the value of the|
|
|
| | consequent or the alternative branch. |
|
|
| | eg: |
|
|
| | 1. if (x > y) z; else w; |
|
|
| | 2. if (x > y) z; else if (w != u) v; |
|
|
| | 3. if (x < y) { z; w + 1; } else u; |
|
|
| | 4. if ((x != y) and (z > w)) |
|
|
| | { |
|
|
| | y := sin(x) / u; |
|
|
| | z := w + 1; |
|
|
| | } |
|
|
| | else if (x > (z + 1)) |
|
|
| | { |
|
|
| | w := abs (x - y) + z; |
|
|
| | u := (x + 1) > 2y ? 2u : 3u; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| switch | The first true case condition that is encountered will |
|
|
| | determine the result of the switch. If none of the case |
|
|
| | conditions hold true, the default action is assumed as |
|
|
| | the final return value. This is sometimes also known as |
|
|
| | a multi-way branch mechanism. |
|
|
| | eg: |
|
|
| | switch |
|
|
| | { |
|
|
| | case x > (y + z) : 2 * x / abs(y - z); |
|
|
| | case x < 3 : sin(x + y); |
|
|
| | default : 1 + x; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| while | The structure will repeatedly evaluate the internal |
|
|
| | statement(s) 'while' the condition is true. The final |
|
|
| | statement in the final iteration shall be used as the |
|
|
| | return value of the loop. |
|
|
| | eg: |
|
|
| | while ((x -= 1) > 0) |
|
|
| | { |
|
|
| | y := x + z; |
|
|
| | w := u + y; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| repeat/ | The structure will repeatedly evaluate the internal |
|
|
| until | statement(s) 'until' the condition is true. The final |
|
|
| | statement in the final iteration shall be used as the |
|
|
| | return value of the loop. |
|
|
| | eg: |
|
|
| | repeat |
|
|
| | y := x + z; |
|
|
| | w := u + y; |
|
|
| | until ((x += 1) > 100) |
|
|
+----------+---------------------------------------------------------+
|
|
| for | The structure will repeatedly evaluate the internal |
|
|
| | statement(s) while the condition is true. On each loop |
|
|
| | iteration, an 'incrementing' expression is evaluated. |
|
|
| | The conditional is mandatory whereas the initialiser |
|
|
| | and incrementing expressions are optional. |
|
|
| | eg: |
|
|
| | for (var x := 0; (x < n) and (x != y); x += 1) |
|
|
| | { |
|
|
| | y := y + x / 2 - z; |
|
|
| | w := u + y; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| break | Break terminates the execution of the nearest enclosed |
|
|
| break[] | loop, allowing for the execution to continue on external|
|
|
| | to the loop. The default break statement will set the |
|
|
| | return value of the loop to NaN, where as the return |
|
|
| | based form will set the value to that of the break |
|
|
| | expression. |
|
|
| | eg: |
|
|
| | while ((i += 1) < 10) |
|
|
| | { |
|
|
| | if (i < 5) |
|
|
| | j -= i + 2; |
|
|
| | else if (i % 2 == 0) |
|
|
| | break; |
|
|
| | else |
|
|
| | break[2i + 3]; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| continue | Continue results in the remaining portion of the nearest|
|
|
| | enclosing loop body to be skipped. |
|
|
| | eg: |
|
|
| | for (var i := 0; i < 10; i += 1) |
|
|
| | { |
|
|
| | if (i < 5) |
|
|
| | continue; |
|
|
| | j -= i + 2; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| return | Return immediately from within the current expression. |
|
|
| | With the option of passing back a variable number of |
|
|
| | values (scalar, vector or string). eg: |
|
|
| | 1. return [1]; |
|
|
| | 2. return [x, 'abx']; |
|
|
| | 3. return [x, x + y,'abx']; |
|
|
| | 4. return []; |
|
|
| | 5. if (x < y) |
|
|
| | return [x, x - y, 'result-set1', 123.456]; |
|
|
| | else |
|
|
| | return [y, x + y, 'result-set2']; |
|
|
+----------+---------------------------------------------------------+
|
|
| ?: | Ternary conditional statement, similar to that of the |
|
|
| | above denoted if-statement. |
|
|
| | eg: |
|
|
| | 1. x ? y : z |
|
|
| | 2. x + 1 > 2y ? z + 1 : (w / v) |
|
|
| | 3. min(x,y) > z ? (x < y + 1) ? x : y : (w * v) |
|
|
+----------+---------------------------------------------------------+
|
|
| ~ | Evaluate each sub-expression, then return as the result |
|
|
| | the value of the last sub-expression. This is sometimes |
|
|
| | known as multiple sequence point evaluation. |
|
|
| | eg: |
|
|
| | ~(i := x + 1, j := y / z, k := sin(w/u)) == (sin(w/u))) |
|
|
| | ~{i := x + 1; j := y / z; k := sin(w/u)} == (sin(w/u))) |
|
|
+----------+---------------------------------------------------------+
|
|
| [*] | Evaluate any consequent for which its case statement is |
|
|
| | true. The return value will be either zero or the result|
|
|
| | of the last consequent to have been evaluated. |
|
|
| | eg: |
|
|
| | [*] |
|
|
| | { |
|
|
| | case (x + 1) > (y - 2) : x := z / 2 + sin(y / pi); |
|
|
| | case (x + 2) < abs(y + 3) : w / 4 + min(5y,9); |
|
|
| | case (x + 3) == (y * 4) : y := abs(z / 6) + 7y; |
|
|
| | } |
|
|
+----------+---------------------------------------------------------+
|
|
| [] | The vector size operator returns the size of the vector |
|
|
| | being actioned. |
|
|
| | eg: |
|
|
| | 1. v[] |
|
|
| | 2. max_size := max(v0[],v1[],v2[],v3[]) |
|
|
+----------+---------------------------------------------------------+
|
|
|
|
Note01: In the tables above, the symbols x, y, z, w, u and v where
|
|
appropriate may represent any of one the following:
|
|
|
|
1. Literal numeric/string value
|
|
2. A variable
|
|
3. A vector element
|
|
4. A vector
|
|
5. A string
|
|
6. An expression comprised of [1], [2] or [3] (eg: 2 + x / vec[3])
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 09 - FUNDAMENTAL TYPES]
|
|
ExprTk supports three fundamental types which can be used freely in
|
|
expressions. The types are as follows:
|
|
|
|
(1) Scalar
|
|
(2) Vector
|
|
(3) String
|
|
|
|
|
|
(1) Scalar Type
|
|
The scalar type is a singular numeric value. The underlying type is
|
|
that used to specialise the ExprTk components (float, double, long
|
|
double, MPFR et al).
|
|
|
|
|
|
(2) Vector Type
|
|
The vector type is a fixed size sequence of contiguous scalar values.
|
|
A vector can be indexed resulting in a scalar value. Operations
|
|
between a vector and scalar will result in a vector with a size equal
|
|
to that of the original vector, whereas operations between vectors
|
|
will result in a vector of size equal to that of the smaller of the
|
|
two. In both mentioned cases, the operations will occur element-wise.
|
|
|
|
|
|
(3) String Type
|
|
The string type is a variable length sequence of 8-bit chars. Strings
|
|
can be assigned and concatenated to one another, they can also be
|
|
manipulated via sub-ranges using the range definition syntax. Strings
|
|
however can not interact with scalar or vector types.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 10 - COMPONENTS]
|
|
There are three primary components, that are specialised upon a given
|
|
numeric type, which make up the core of ExprTk. The components are as
|
|
follows:
|
|
|
|
(1) Symbol Table exprtk::symbol_table<NumericType>
|
|
(2) Expression exprtk::expression<NumericType>
|
|
(3) Parser exprtk::parser<NumericType>
|
|
|
|
|
|
(1) Symbol Table
|
|
A structure that is used to store references to variables, constants
|
|
and functions that are to be used within expressions. Furthermore in
|
|
the context of composited recursive functions the symbol table can
|
|
also be thought of as a simple representation of a stack specific for
|
|
the expression(s) that reference it. The following is a list of the
|
|
types a symbol table can handle:
|
|
|
|
(a) Numeric variables
|
|
(b) Numeric constants
|
|
(c) Numeric vector elements
|
|
(d) String variables
|
|
(e) String constants
|
|
(f) Functions
|
|
(g) Vararg functions
|
|
|
|
|
|
During the compilation process if an expression is found to require
|
|
any of the elements noted above, the expression's associated
|
|
symbol_table will be queried for the element and if present a
|
|
reference to the element will be embedded within the expression's AST.
|
|
This allows for the original element to be modified independently of
|
|
the expression instance and to also allow the expression to be
|
|
evaluated using the current value of the element.
|
|
|
|
The example below demonstrates the relationship between variables,
|
|
symbol_table and expression. Note the variables are modified as they
|
|
normally would in a program, and when the expression is evaluated the
|
|
current values assigned to the variables shall be used.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
|
|
double x = 0;
|
|
double y = 0;
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
std::string expression_string = "x * y + 3";
|
|
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser.compile(expression_string,expression);
|
|
|
|
x = 1.0;
|
|
y = 2.0;
|
|
expression.value(); // 1 * 2 + 3
|
|
|
|
x = 3.7;
|
|
expression.value(); // 3.7 * 2 + 3
|
|
|
|
y = -9.0;
|
|
expression.value(); // 3.7 * -9 + 3
|
|
|
|
// 'x * -9 + 3' for x in range of [0,100) in steps of 0.0001
|
|
for (x = 0.0; x < 100.0; x += 0.0001)
|
|
{
|
|
expression.value(); // x * -9 + 3
|
|
}
|
|
|
|
|
|
Note02: Any variable reference provided to a given symbol_table
|
|
instance, must have a lifetime at least as long as the lifetime of the
|
|
symbol_table instance. In the event the variable reference is
|
|
invalidated before the symbol_table or any dependent expression
|
|
instances have been destructed, then any associated expression
|
|
evaluations or variable referencing via the symbol_table instance will
|
|
result in undefined behaviour.
|
|
|
|
The following bit of code instantiates a symbol_table and expression
|
|
instance, then proceeds to demonstrate various ways in which
|
|
references to variables can be added to the symbol_table, and how
|
|
those references are subsequently invalidated resulting in various
|
|
forms of undefined behaviour.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
|
|
std::deque<double > y {1.1, 2.2, 3.3};
|
|
std::vector<double> z {4.4, 5.5, 6.6};
|
|
double* w = new double(123.456);
|
|
|
|
{
|
|
double x = 123.4567;
|
|
symbol_table.add_variable("x", x);
|
|
} // Reference to variable x has been invalidated
|
|
|
|
|
|
symbol_table.add_variable("y", y.back());
|
|
|
|
y.pop_back(); // Reference to variable y has been invalidated
|
|
|
|
|
|
symbol_table.add_variable("z", z.front());
|
|
|
|
z.erase(z.begin());
|
|
// Reference to variable z has been invalidated
|
|
|
|
symbol_table.add_variable("w", *w);
|
|
|
|
delete w; // Reference to variable w has been invalidated
|
|
|
|
const std::string expression_string = "x + y / z * w";
|
|
|
|
// Compilation of expression will succeed
|
|
parser.compile(expression_string,expression);
|
|
|
|
expression.value();
|
|
// Evaluation will result in undefined behaviour
|
|
// due to 'x' and 'w' having been destroyed.
|
|
|
|
symbol_table.get_variable("x")->ref() = 135.791;
|
|
// Assignment will result in undefined behaviour
|
|
|
|
|
|
A compiled expression that references variables from a symbol_table is
|
|
dependent on that symbol_table instance and the variables it holds
|
|
being valid.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
|
|
double x = 123.456;
|
|
|
|
symbol_table.add_variable("x", x);
|
|
|
|
const std::string expression_string = "(x + 1) / 2";
|
|
|
|
// Compilation of the expression will succeed
|
|
parser.compile(expression_string,expression);
|
|
|
|
// Clear all variables from symbol_table
|
|
symbol_table.clear();
|
|
|
|
expression.value();
|
|
// Evaluation will result in undefined behaviour
|
|
// because the reference to 'x' having been destroyed
|
|
// during the clearing of the symbol_table
|
|
|
|
|
|
In the above example, an expression is compiled that references
|
|
variable "x". As part of the compilation process the node holding the
|
|
variable "x" is obtained from the symbol_table and embedded in the AST
|
|
of the expression - in short the expression is now referencing the
|
|
node that holds the variable "x". The following diagram depicts the
|
|
dependencies between the variable x, the symbol table and the
|
|
expression:
|
|
|
|
+--[Symbol Table]--+
|
|
| |
|
|
| +- ------+ |
|
|
| | x-node | |
|
|
| +-----A--+ | +--[Expression]--+
|
|
+---|---|----------+ | +---------+ |
|
|
v | | | A.S.T | |
|
|
| +--------<--------[.] | |
|
|
+-----+ | +---------+ |
|
|
| +----------------+
|
|
+-v-[variable]---+
|
|
| x: 123.456 |
|
|
+----------------+
|
|
|
|
|
|
When the clear method is called on the symbol table the X-Node is
|
|
destroyed, so now the expression is referencing a node that has been
|
|
destroyed. From this point onwards any attempts to reference the
|
|
expression instance will result in undefined behaviour. Simply put the
|
|
above example violates the requirement that the lifetime of any
|
|
objects referenced by expressions should exceed the lifetime of the
|
|
expression instance.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
|
|
double x = 123.456;
|
|
|
|
symbol_table.add_variable("x", x);
|
|
|
|
const std::string expression_string = "(x + 1) / 2";
|
|
|
|
// Compilation of the expression will succeed
|
|
parser.compile(expression_string,expression);
|
|
|
|
expression.value();
|
|
|
|
// Release the expression and its dependents
|
|
expression.release();
|
|
|
|
// Clear all variables from symbol_table
|
|
symbol_table.clear();
|
|
|
|
expression.value();
|
|
// Will return null_node value of NaN
|
|
|
|
|
|
In the above example the expression is released before the associated
|
|
symbol_table is cleared of its variables, which resolves the undefined
|
|
behaviour issue noted in the previous example.
|
|
|
|
Note03: It is possible to register multiple symbol_tables with a
|
|
single expression object. In the event an expression has multiple
|
|
symbol tables, and where there exists conflicts between symbols, the
|
|
compilation stage will resolve the conflicts based on the order of
|
|
registration of the symbol_tables to the expression. For a more
|
|
expansive discussion please review section [17 - Hierarchies Of Symbol
|
|
Tables]
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
|
|
symbol_table_t symbol_table0;
|
|
symbol_table_t symbol_table1;
|
|
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
double x0 = 123.0;
|
|
double x1 = 678.0;
|
|
|
|
std::string expression_string = "x + 1";
|
|
|
|
symbol_table0.add_variable("x",x0);
|
|
symbol_table1.add_variable("x",x1);
|
|
|
|
expression.register_symbol_table(symbol_table0);
|
|
expression.register_symbol_table(symbol_table1);
|
|
|
|
parser.compile(expression_string,expression);
|
|
|
|
expression.value(); // 123 + 1
|
|
|
|
|
|
The symbol table supports adding references to external instances of
|
|
types that can be accessed within expressions via the following
|
|
methods:
|
|
|
|
1. bool add_variable (const std::string& name, scalar_t& )
|
|
2. bool add_constant (const std::string& name, const scalar_t& )
|
|
3. bool add_stringvar (const std::string& name, std::string& )
|
|
4. bool add_vector (const std::string& name, vector_type& )
|
|
5. bool add_function (const std::string& name, function_t& )
|
|
6. bool create_stringvar(const std::string& name,const std::string&)
|
|
7. bool create_variable (const std::string& name, const T& )
|
|
|
|
|
|
Note04: The 'vector' type must be comprised from a contiguous array of
|
|
scalars with a size that is larger than zero. The vector type itself
|
|
can be any one of the following:
|
|
|
|
1. std::vector<scalar_t>
|
|
2. scalar_t(&v)[N]
|
|
3. scalar_t* and array size
|
|
4. exprtk::vector_view<scalar_t>
|
|
|
|
|
|
When registering a variable, vector, string or function with an
|
|
instance of a symbol_table, the call to 'add_...' may fail and return
|
|
a false result due to one or more of the following reasons:
|
|
|
|
1. Variable name contains invalid characters or is ill-formed
|
|
2. Variable name conflicts with a reserved word (eg: 'while')
|
|
3. Variable name conflicts with a previously registered variable
|
|
4. A vector of size (length) zero is being registered
|
|
5. A free function exceeding fifteen parameters is being registered
|
|
6. The symbol_table instance is in an invalid state
|
|
|
|
|
|
Note05: The symbol_table has a method called clear, which when invoked
|
|
will clear all variables, vectors, strings and functions registered
|
|
with the symbol_table instance. If this method is to be called, then
|
|
one must make sure that all compiled expression instances that
|
|
reference variables belonging to that symbol_table instance are
|
|
released (aka call release method on expression) before calling the
|
|
clear method on the symbol_table instance, otherwise undefined
|
|
behaviours will occur.
|
|
|
|
A further property of symbol tables is that they can be classified at
|
|
instantiation as either being mutable (by default) or immutable. This
|
|
property determines if variables, vectors or strings registered with
|
|
the symbol table can undergo modifications within expressions that
|
|
reference them. The following demonstrates construction of an
|
|
immutable symbol table instance:
|
|
|
|
symbol_table_t immutable_symbol_table
|
|
(symbol_table_t::symtab_mutability_type::e_immutable);
|
|
|
|
|
|
When a symbol table, that has been constructed as being immutable, is
|
|
registered with an expression, any statements in the expression string
|
|
that modify the variables that are managed by the immutable symbol
|
|
table will result in a compilation error. The operations that trigger
|
|
the mutability constraint are the following assignment operators:
|
|
|
|
1. Assignment: :=
|
|
2. Assign operation: +=, -=, *=, /= , %=
|
|
|
|
const std::string expression_str = "x += x + 123.456";
|
|
|
|
symbol_table_t immutable_symbol_table
|
|
(symbol_table_t::symtab_mutability_type::e_immutable);
|
|
|
|
T x = 0.0;
|
|
|
|
immutable_symbol_table.add_variable("x" , x);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(immutable_symbol_table);
|
|
|
|
parser_t parser;
|
|
|
|
parser.compile(expression_str, expression);
|
|
// Compile error because of assignment to variable x
|
|
|
|
|
|
In the above example, variable x is registered to an immutable symbol
|
|
table, making it an immutable variable within the context of any
|
|
expressions that reference it. The expression string being compiled
|
|
uses the addition assignment operator which will modify the value of
|
|
variable x. The compilation process detects this semantic violation
|
|
and proceeds to halt compilation and return the appropriate error.
|
|
|
|
One of the main reasons for this functionality is that, one may want
|
|
the immutability properties that come with constness of a variable
|
|
such as scalars, vectors and strings, but not necessarily the
|
|
accompanying compile time const-folding optimisations, that would
|
|
result in the value of the variables being retrieved only once at
|
|
compile time, causing external updates to the variables to not be part
|
|
of the expression evaluation.
|
|
|
|
symbol_table_t immutable_symbol_table
|
|
(symbol_table_t::symtab_mutability_type::e_immutable);
|
|
|
|
T x = 0.0;
|
|
|
|
const std::string expression_str = "x + (y + y)";
|
|
|
|
immutable_symbol_table.add_variable("x" , x );
|
|
immutable_symbol_table.add_constant("y" , 123.0);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(immutable_symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_str, expression);
|
|
|
|
for (; x < 10.0; ++x)
|
|
{
|
|
const auto expected_value = x + (123.0 + 123.0);
|
|
const auto result_value = expression.value();
|
|
assert(expression.value() != expected_value);
|
|
}
|
|
|
|
|
|
In the above example, there are two variables X and Y. Where Y is a
|
|
constant and X is a normal variable. Both are registered with a symbol
|
|
table that is immutable. The expression when compiled will result in
|
|
the "(y + y)" part being const-folded at compile time to the literal
|
|
value of 246. Whereas the current value of X, being updated via the
|
|
for-loop, externally to the expression and the symbol table shall be
|
|
observable to the expression upon each evaluation.
|
|
|
|
|
|
(2) Expression
|
|
A structure that holds an Abstract Syntax Tree or AST for a specified
|
|
expression and is used to evaluate said expression. Evaluation of the
|
|
expression is accomplished by performing a post-order traversal of the
|
|
AST. If a compiled Expression uses variables or user defined
|
|
functions, it will have an associated Symbol Table, which will contain
|
|
references to said variables, functions or strings. An example AST
|
|
structure for the denoted expression is as follows:
|
|
|
|
Expression: z := (x + y^-2.345) * sin(pi / min(w - 7.3,v))
|
|
|
|
[Root]
|
|
|
|
|
[Assignment]
|
|
________/ \_____
|
|
/ \
|
|
Variable(z) [Multiplication]
|
|
____________/ \___________
|
|
/ \
|
|
/ [Unary-Function(sin)]
|
|
[Addition] |
|
|
____/ \____ [Division]
|
|
/ \ ___/ \___
|
|
Variable(x) [Exponentiation] / \
|
|
______/ \______ Constant(pi) [Binary-Function(min)]
|
|
/ \ ____/ \___
|
|
Variable(y) [Negation] / \
|
|
| / Variable(v)
|
|
Constant(2.345) /
|
|
/
|
|
[Subtraction]
|
|
____/ \____
|
|
/ \
|
|
Variable(w) Constant(7.3)
|
|
|
|
|
|
The above denoted AST shall be evaluated in the following order:
|
|
|
|
(01) Load Variable (z) (10) Load Constant (7.3)
|
|
(02) Load Variable (x) (11) Subtraction (09 & 10)
|
|
(03) Load Variable (y) (12) Load Variable (v)
|
|
(04) Load Constant (2.345) (13) Min (11 & 12)
|
|
(05) Negation (04) (14) Division (08 & 13)
|
|
(06) Exponentiation (03 & 05) (15) Sin (14)
|
|
(07) Addition (02 & 06) (16) Multiplication (07 & 15)
|
|
(08) Load Constant (pi) (17) Assignment (01 & 16)
|
|
(09) Load Variable (w)
|
|
|
|
|
|
Generally an expression in ExprTk can be thought of as a free function
|
|
similar to those found in imperative languages. This form of pseudo
|
|
function will have a name, it may have a set of one or more inputs and
|
|
will return at least one value as its result. Furthermore the function
|
|
when invoked, may cause a side-effect that changes the state of the
|
|
host program.
|
|
|
|
As an example the following is a pseudo-code definition of a free
|
|
function that performs a computation taking four inputs, modifying one
|
|
of them and returning a value based on some arbitrary calculation:
|
|
|
|
ResultType foo(InputType x, InputType y, InputType z, InputType w)
|
|
{
|
|
w = 2 * x^y + z; // Side-Effect
|
|
return abs(x - y) / z; // Return Result
|
|
}
|
|
|
|
|
|
Given the above definition the following is a functionally equivalent
|
|
version using ExprTk:
|
|
|
|
const std::string foo_str =
|
|
" w := 2 * x^y + z; "
|
|
" abs(x - y) / z; ";
|
|
|
|
T x, y, z, w;
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
symbol_table.add_variable("z",z);
|
|
symbol_table.add_variable("w",w);
|
|
|
|
expression_t foo;
|
|
foo.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
if (!parser.compile(foo_str,foo))
|
|
{
|
|
// Error in expression...
|
|
return;
|
|
}
|
|
|
|
T result = foo.value();
|
|
|
|
|
|
(3) Parser
|
|
A component which takes as input a string representation of an
|
|
expression and attempts to compile said input with the result being an
|
|
instance of Expression. If an error is encountered during the
|
|
compilation process, the parser will stop compiling and return an
|
|
error status code, with a more detailed description of the error(s)
|
|
and its location within the input provided by the 'get_error'
|
|
interface.
|
|
|
|
Note06: The exprtk::expression and exprtk::symbol_table components are
|
|
reference counted entities. Copy constructing or assigning to or from
|
|
either component will result in a shallow copy and a reference count
|
|
increment, rather than a complete replication. Furthermore the
|
|
expression and symbol_table components being Default-Constructible,
|
|
Copy-Constructible and Copy-Assignable make them compatible with
|
|
various C++ standard library containers and adaptors such as
|
|
std::vector, std::map, std::stack etc.
|
|
|
|
The following is an example of two unique expressions, after having
|
|
been instantiated and compiled, one expression is assigned to the
|
|
other. The diagrams depict their initial and post assignment states,
|
|
including which control block each expression references and their
|
|
associated reference counts.
|
|
|
|
|
|
exprtk::expression e0; // constructed expression, eg: x + 1
|
|
exprtk::expression e1; // constructed expression, eg: 2z + y
|
|
|
|
+-----[ e0 cntrl block]----+ +-----[ e1 cntrl block]-----+
|
|
| 1. Expression Node 'x+1' | | 1. Expression Node '2z+y' |
|
|
| 2. Ref Count: 1 |<-+ | 2. Ref Count: 1 |<-+
|
|
+--------------------------+ | +---------------------------+ |
|
|
| |
|
|
+--[ e0 expression]--+ | +--[ e1 expression]--+ |
|
|
| 1. Reference to ]------+ | 1. Reference to ]-------+
|
|
| e0 Control Block | | e1 Control Block |
|
|
+--------------------+ +--------------------+
|
|
|
|
|
|
e0 = e1; // e0 and e1 are now 2z+y
|
|
|
|
+-----[ e1 cntrl block]-----+
|
|
| 1. Expression Node '2z+y' |
|
|
+----------->| 2. Ref Count: 2 |<----------+
|
|
| +---------------------------+ |
|
|
| |
|
|
| +--[ e0 expression]--+ +--[ e1 expression]--+ |
|
|
+---[ 1. Reference to | | 1. Reference to ]---+
|
|
| e1 Control Block | | e1 Control Block |
|
|
+--------------------+ +--------------------+
|
|
|
|
The reason for the above complexity and restrictions of deep copies
|
|
for the expression and symbol_table components is because expressions
|
|
may include user defined variables or functions. These are embedded as
|
|
references into the expression's AST. When copying an expression, said
|
|
references need to also be copied. If the references are blindly
|
|
copied, it will then result in two or more identical expressions
|
|
utilising the exact same references for variables. This obviously is
|
|
not the default assumed scenario and will give rise to non-obvious
|
|
behaviours when using the expressions in various contexts such as
|
|
multi-threading et al.
|
|
|
|
The prescribed method for cloning an expression is to compile it from
|
|
its string form. Doing so will allow the 'user' to properly consider
|
|
the exact source of user defined variables and functions.
|
|
|
|
Note07: The exprtk::parser is a non-copyable and non-thread safe
|
|
component, and should only be shared via either a reference, a shared
|
|
pointer or a std::ref mechanism, and considerations relating to
|
|
synchronisation taken into account where appropriate. The parser
|
|
represents an object factory, specifically a factory of expressions,
|
|
and generally should not be instantiated solely on a per expression
|
|
compilation basis.
|
|
|
|
The following diagram and example depicts the flow of data and
|
|
operations for compiling multiple expressions via the parser and
|
|
inserting the newly minted exprtk::expression instances into a
|
|
std::vector.
|
|
|
|
+----[exprtk::parser]---+
|
|
| Expression Factory |
|
|
| parser_t::compile(...)|
|
|
+--> ~.~.~.~.~.~.~.~.~.~ ->--+
|
|
| +-----------------------+ |
|
|
Expressions in | | Expressions as
|
|
string form ^ V exprtk::expression
|
|
| | instances
|
|
[s0:'x+1']--->--+ | | +-[e0: x+1]
|
|
| | | |
|
|
[s1:'2z+y']-->--+--+ +->+-[e1: 2z+y]
|
|
| |
|
|
[s2:'sin(k+w)']-+ +-[e2: sin(k+w)]
|
|
|
|
|
|
const std::string expression_str[3] =
|
|
{
|
|
"x + 1",
|
|
"2x + y",
|
|
"sin(k + w)"
|
|
};
|
|
|
|
std::vector<expression_t> expression_list;
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
symbol_table_t symbol_table;
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
for (std::size_t i = 0; i < 3; ++i)
|
|
{
|
|
if (parser.compile(expression_str[i],expression))
|
|
{
|
|
expression_list.push_back(expression);
|
|
}
|
|
else
|
|
std::cout << "Error in " << expression_str[i] << "\n";
|
|
}
|
|
|
|
for (auto& e : expression_list)
|
|
{
|
|
e.value();
|
|
}
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 11 - COMPILATION OPTIONS]
|
|
The exprtk::parser when being instantiated takes as input a set of
|
|
options to be used during the compilation process of expressions.
|
|
An example instantiation of exprtk::parser where only the joiner,
|
|
commutative and strength reduction options are enabled is as follows:
|
|
|
|
typedef exprtk::parser<NumericType>::settings_t settings_t;
|
|
|
|
const std::size_t compile_options =
|
|
settings_t::e_joiner +
|
|
settings_t::e_commutative_check +
|
|
settings_t::e_strength_reduction;
|
|
|
|
parser_t parser(compile_options);
|
|
|
|
|
|
Currently eight types of compile time options are supported, and
|
|
enabled by default. The options and their explanations are as follows:
|
|
|
|
(1) Replacer
|
|
(2) Joiner
|
|
(3) Numeric Check
|
|
(4) Bracket Check
|
|
(5) Sequence Check
|
|
(6) Commutative Check
|
|
(7) Strength Reduction Check
|
|
(8) Stack And Node Depth Check
|
|
(9) Vector Size Check
|
|
|
|
|
|
(1) Replacer (e_replacer)
|
|
Enable replacement of specific tokens with other tokens. For example
|
|
the token "true" of type symbol shall be replaced with the numeric
|
|
token of value one.
|
|
|
|
(a) (x < y) == true ---> (x < y) == 1
|
|
(b) false == (x > y) ---> 0 == (x > y)
|
|
|
|
|
|
(2) Joiner (e_joiner)
|
|
Enable joining of multi-character operators that may have been
|
|
incorrectly disjoint in the string representation of the specified
|
|
expression. For example the consecutive tokens of ">" "=" will become
|
|
">=" representing the "greater than or equal to" operator. If not
|
|
properly resolved the original form will cause a compilation error.
|
|
The following is a listing of the scenarios that the joiner can
|
|
handle:
|
|
|
|
(a) '>' '=' ---> '>=' (gte)
|
|
(b) '<' '=' ---> '<=' (lte)
|
|
(c) '=' '=' ---> '==' (equal)
|
|
(d) '!' '=' ---> '!=' (not-equal)
|
|
(e) '<' '>' ---> '<>' (not-equal)
|
|
(f) ':' '=' ---> ':=' (assignment)
|
|
(g) '+' '=' ---> '+=' (addition assignment)
|
|
(h) '-' '=' ---> '-=' (subtraction assignment)
|
|
(i) '*' '=' ---> '*=' (multiplication assignment)
|
|
(j) '/' '=' ---> '/=' (division assignment)
|
|
(k) '%' '=' ---> '%=' (modulo assignment)
|
|
(l) '+' '-' ---> '-' (subtraction)
|
|
(m) '-' '+' ---> '-' (subtraction)
|
|
(n) '-' '-' ---> '+' (addition)
|
|
(o) '<=' '>' ---> '<=>' (swap)
|
|
|
|
|
|
An example of the transformation that takes place is as follows:
|
|
|
|
(a) (x > = y) and (z ! = w) ---> (x >= y) and (z != w)
|
|
|
|
|
|
(3) Numeric Check (e_numeric_check)
|
|
Enable validation of tokens representing numeric types so as to catch
|
|
any errors prior to the costly process of the main compilation step
|
|
commencing.
|
|
|
|
|
|
(4) Bracket Check (e_bracket_check)
|
|
Enable the check for validating the ordering of brackets in the
|
|
specified expression.
|
|
|
|
|
|
(5) Sequence Check (e_sequence_check)
|
|
Enable the check for validating that sequences of either pairs or
|
|
triplets of tokens make sense. For example the following sequence of
|
|
tokens when encountered will raise an error:
|
|
|
|
(a) (x + * 3) ---> sequence error
|
|
|
|
|
|
(6) Commutative Check (e_commutative_check)
|
|
Enable the check that will transform sequences of pairs of tokens that
|
|
imply a multiplication operation. The following are some examples of
|
|
such transformations:
|
|
|
|
(a) 2x ---> 2 * x
|
|
(b) 25x^3 ---> 25 * x^3
|
|
(c) 3(x + 1) ---> 3 * (x + 1)
|
|
(d) (x + 1)4 ---> (x + 1) * 4
|
|
(e) 5foo(x,y) ---> 5 * foo(x,y)
|
|
(f) foo(x,y)6 + 1 ---> foo(x,y) * 6 + 1
|
|
(g) (4((2x)3)) ---> 4 * ((2 * x) * 3)
|
|
(h) w / (x - y)z ---> w / (x - y) * z
|
|
|
|
|
|
(7) Strength Reduction Check (e_strength_reduction)
|
|
Enable the use of strength reduction optimisations during the
|
|
compilation process. In ExprTk strength reduction optimisations
|
|
predominantly involve transforming sub-expressions into other forms
|
|
that are algebraically equivalent yet less costly to compute. The
|
|
following are examples of the various transformations that can occur:
|
|
|
|
(a) (x / y) / z ---> x / (y * z)
|
|
(b) (x / y) / (z / w) ---> (x * w) / (y * z)
|
|
(c) (2 * x) - (2 * y) ---> 2 * (x - y)
|
|
(d) (2 / x) / (3 / y) ---> (2 / 3) / (x * y)
|
|
(e) (2 * x) * (3 * y) ---> 6 * (x * y)
|
|
(f) (2 * x) * (2 - 4 / 2) ---> 0
|
|
(g) (3 - 6 / 2) / (2 * x) ---> 0
|
|
(h) avg(x,y,z) * (2 - 4 / 2) ---> 0
|
|
|
|
|
|
Note08: When using strength reduction in conjunction with expressions
|
|
whose inputs or sub-expressions may result in values nearing either of
|
|
the bounds of the underlying numeric type (eg: double), there may be
|
|
the possibility of a decrease in the precision of results.
|
|
|
|
In the following example the given expression which represents an
|
|
attempt at computing the average between x and y will be transformed
|
|
as follows:
|
|
|
|
(0.5 * x) + (y * 0.5) ---> 0.5 * (x + y)
|
|
|
|
There may be situations where the above transformation will cause
|
|
numerical overflows and that the original form of the expression is
|
|
desired over the strength reduced form. In these situations it is best
|
|
to turn off strength reduction optimisations or to use a type with a
|
|
larger numerical bound.
|
|
|
|
|
|
(8) Stack And Node Depth Check
|
|
ExprTk incorporates a recursive descent parser. When parsing
|
|
expressions comprising inner sub-expressions, the recursive nature of
|
|
the parsing process causes the stack to grow. If the expression causes
|
|
the stack to grow beyond the stack size limit, this would lead to a
|
|
stackoverflow and its associated stack corruption and security
|
|
vulnerability issues.
|
|
|
|
Similarly to parsing, evaluating an expression may cause the stack to
|
|
grow. Such things like user defined functions, composite functions and
|
|
the general nature of the AST being evaluated can cause the stack to
|
|
grow, and may result in potential stackoverflow issues as denoted
|
|
above.
|
|
|
|
ExprTk provides a set of checks that prevent both of the above denoted
|
|
problems at compile time. These checks rely on two specific limits
|
|
being set on the parser settings instance, these limits are:
|
|
|
|
1. max_stack_depth (default: 400 )
|
|
2. max_node_depth (default: 10000)
|
|
|
|
|
|
The following demonstrates how these two parser parameters can be set:
|
|
|
|
parser_t parser;
|
|
|
|
parser.settings().set_max_stack_depth(100);
|
|
parser.settings().set_max_node_depth(200);
|
|
|
|
|
|
In the above code, during parsing if the stack depth reaches or
|
|
exceeds 100 levels, the parsing process will immediately halt and
|
|
return with a failure. Similarly, during synthesizing the AST nodes,
|
|
if the compilation process detects an AST tree depth exceeding 200
|
|
levels the parsing process will halt and return a parsing failure.
|
|
|
|
|
|
(9) Expression Size Check
|
|
ExprTk allows for expression local variables, vectors and strings to
|
|
be defined. As such the amount of data in terms of bytes consumed by
|
|
the expression locals can also be limited, so as to prevent
|
|
expressions once compiled from consuming large amounts of data.
|
|
|
|
The maximum number of bytes an expression's locally defined variables
|
|
may use can be set via the parser settings prior to compilation as
|
|
follows:
|
|
|
|
using expression_t = exprtk::expression<double>;
|
|
using parser_t = exprtk::parser<double>;
|
|
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
parser.settings().set_max_total_local_symbol_size_bytes(16);
|
|
|
|
const std::string expression1 = "var x := 1; var y := 1;";
|
|
const std::string expression2 = "var x := 1; var v[2] := [1];";
|
|
|
|
expression_t expression;
|
|
|
|
parser.compile(expression1, expression); // compilation success
|
|
parser.compile(expression2, expression); // compilation error
|
|
|
|
In the above example, the expression's max total local symbol size has
|
|
been set to 16 bytes. The numeric type being used is double which is 8
|
|
bytes per instance. The first expression (expression1) compiles
|
|
successfully because the total local symbol size is 16 bytes, 8 bytes
|
|
for each of the variables x and y.
|
|
|
|
However the second expression (expression2) fails to compile. This is
|
|
because the total number of bytes needed for the expression is 24
|
|
bytes, 8 bytes for the variable x and 16 bytes for the vector v, as
|
|
such exceeding the previously set limit of 16 bytes. The error
|
|
diagnostic generated by the parser on compilation failure will look
|
|
like the following:
|
|
|
|
ERR161 - Adding vector 'v' of size 2 bytes will exceed max total
|
|
local symbol size of: 16 bytes, current total size: 8 bytes
|
|
|
|
|
|
(10) Vector Size Check
|
|
When defining an expression local vector, ExprTk uses a default
|
|
maximum vector size of two billion elements. One may want to limit the
|
|
maximum vector size to be either smaller or larger than the specified
|
|
default value. The maximum size value can be changed via the parser
|
|
settings.
|
|
|
|
parser_t parser;
|
|
|
|
parser.settings().set_max_local_vector_size(1000000);
|
|
|
|
std::string expression1 = "var v[1e6] := [123]";
|
|
std::string expression2 = "var v[1e9] := [123]";
|
|
|
|
expression_t expression;
|
|
|
|
parser.compile(expression1, expression); // compilation success
|
|
parser.compile(expression2, expression); // compilation error
|
|
|
|
|
|
In the above code, the maximum local vector size is set to one million
|
|
elements. During compilation of an expression if there is a vector
|
|
definition where the vector's size exceeds the maximum allowed vector
|
|
size a compilation error shall be emitted. The error diagnostic
|
|
generated by the parser on compilation failure will look like the
|
|
following:
|
|
|
|
ERR160 - Invalid vector size. Must be an integer in the
|
|
range [0,1000000], size: 1000000000
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 12 - EXPRESSION STRUCTURES]
|
|
Exprtk supports mathematical expressions in numerous forms based on a
|
|
simple imperative programming model. This section will cover the
|
|
following topics related to general structure and programming of
|
|
expressions using ExprTk:
|
|
|
|
(1) Multi-Statement Expressions
|
|
(2) Statements And Side-Effects
|
|
(3) Conditional Statements
|
|
(4) Special Functions
|
|
|
|
|
|
(1) Multi-Statement Expressions
|
|
Expressions in ExprTk can be comprised of one or more statements, which
|
|
may sometimes be called sub-expressions. The following are two
|
|
examples of expressions stored in std::string variables, the first a
|
|
single statement and the second a multi-statement expression:
|
|
|
|
std::string single_statement = " z := x + y ";
|
|
|
|
std::string multi_statement = " var temp := x; "
|
|
" x := y + z; "
|
|
" y := temp; ";
|
|
|
|
|
|
In a multi-statement expression, the final statement will determine
|
|
the overall result of the expression. In the following multi-statement
|
|
expression, the result of the expression when evaluated will be '2.3',
|
|
which will also be the value stored in the 'y' variable.
|
|
|
|
z := x + y;
|
|
y := 2.3;
|
|
|
|
|
|
As demonstrated in the expression above, statements within an
|
|
expression are separated using the semi-colon ';' operator. In the
|
|
event two statements are not separated by a semi-colon, and the
|
|
implied multiplication feature is active (enabled by default), the
|
|
compiler will assume a multiplication operation between the two
|
|
statements.
|
|
|
|
In the following example we have a multi-statement expression composed
|
|
of two variable definitions and initialisations for variables x and y
|
|
and two seemingly separate mathematical operations.
|
|
|
|
var x:= 2;
|
|
var y:= 3;
|
|
x + 1
|
|
y * 2
|
|
|
|
|
|
However the result of the expression will not be 6 as may have been
|
|
assumed based on the calculation of 'y * 2', but rather the result
|
|
will be 8. This is because the compiler will have conjoined the two
|
|
mathematical statements into one via a multiplication operation. The
|
|
expression when compiled will actually evaluate as the following:
|
|
|
|
var x:= 2;
|
|
var y:= 3;
|
|
x + 1 * y * 2; // 2 + 1 * 3 * 2 == 8
|
|
|
|
|
|
In ExprTk any valid statement will itself return a value. This value
|
|
can further be used in conjunction with other statements. This
|
|
includes language structures such as if-statements, loops (for, while)
|
|
and the switch statement. Typically the last statement executed in the
|
|
given construct (conditional, loop etc), will be the value that is
|
|
returned.
|
|
|
|
In the following example, the return value of the expression will be
|
|
11, which is the sum of the variable 'x' and the final value computed
|
|
within the loop body upon its last iteration:
|
|
|
|
var x := 1;
|
|
x + for (var i := x; i < 10; i += 1)
|
|
{
|
|
i / 2;
|
|
i + 1;
|
|
}
|
|
|
|
|
|
(2) Statements And Side-Effects
|
|
Statements themselves may have side effects, which in-turn affect the
|
|
proceeding statements in multi-statement expressions.
|
|
|
|
A statement is said to have a side-effect if it causes the state of
|
|
the expression to change in some way - this includes but is not
|
|
limited to the modification of the state of external variables used
|
|
within the expression. Currently the following actions being present
|
|
in a statement will cause it to have a side-effect:
|
|
|
|
(a) Assignment operation (explicit or potentially)
|
|
(b) Invoking a user-defined function that has side-effects
|
|
|
|
The following are examples of expressions where the side-effect status
|
|
of the statements (sub-expressions) within the expressions have been
|
|
noted:
|
|
|
|
+-+----------------------+------------------------------+
|
|
|#| Expression | Side Effect Status |
|
|
+-+----------------------+------------------------------+
|
|
|0| x + y | False |
|
|
+-+----------------------+------------------------------+
|
|
|1| z := x + y | True - Due to assignment |
|
|
+-+----------------------+------------------------------+
|
|
|2| abs(x - y) | False |
|
|
+-+----------------------+------------------------------+
|
|
|3| abs(x - y); | False |
|
|
| | z := (x += y); | True - Due to assignments |
|
|
+-+----------------------+------------------------------+
|
|
|4| abs(x - y); | False |
|
|
| | z := (x += y); | True - Due to assignments |
|
|
+-+----------------------+------------------------------+
|
|
|5| var t := abs(x - y); | True - Due to initialisation |
|
|
| | t + x; | False |
|
|
| | z := (x += y); | True - Due to assignments |
|
|
+-+----------------------+------------------------------+
|
|
|6| foo(x - y) | True - user defined function |
|
|
+-+----------------------+------------------------------+
|
|
|
|
|
|
Note09: In example 6 from the above set, it is assumed the user
|
|
defined function foo has been registered as having a side-effect. By
|
|
default all user defined functions are assumed to have side-effects,
|
|
unless they are configured in their constructors to not have side-
|
|
effects using the 'disable_has_side_effects' free function. For more
|
|
information review Section 15 - User Defined Functions sub-section 7
|
|
Function Side-Effects.
|
|
|
|
At this point we can see that there will be expressions composed of
|
|
certain kinds of statements that when executed will not affect the
|
|
nature of the expression's result. These statements are typically
|
|
called 'dead code'. These statements though not affecting the final
|
|
result will still be executed and as such they will consume processing
|
|
time that could otherwise be saved. As such ExprTk attempts to detect
|
|
and remove such statements from expressions.
|
|
|
|
The 'Dead Code Elimination' (DCE) optimisation process, which is
|
|
enabled by default, will remove any statements that are determined to
|
|
not have a side-effect in a multi-statement expression, excluding the
|
|
final or last statement.
|
|
|
|
By default the final statement in an expression will always be present
|
|
regardless of its side-effect status, as it is the statement whose
|
|
value shall be used as the result of the expression.
|
|
|
|
In order to further explain the actions taken during the DCE process,
|
|
lets review the following expression:
|
|
|
|
var x := 2; // Statement 1
|
|
var y := x + 2; // Statement 2
|
|
x + y; // Statement 3
|
|
y := x + 3y; // Statement 4
|
|
x - y; // Statement 5
|
|
|
|
|
|
The above expression has five statements. Three of them (1, 2 and 4)
|
|
actively have side-effects. The first two are variable declaration and
|
|
initialisations, where as the third is due to an assignment operation.
|
|
There are two statements (3 and 5), that do not explicitly have
|
|
side-effects, however the latter, statement 5, is the final statement
|
|
in the expression and hence will be assumed to have a side-effect.
|
|
|
|
During compilation when the DCE optimisation is applied to the above
|
|
expression, statement 3 will be removed from the expression, as it has
|
|
no bearing on the final result of expression, the rest of the
|
|
statements will all remain. The optimised form of the expression is as
|
|
follows:
|
|
|
|
var x := 2; // Statement 1
|
|
var y := x + 2; // Statement 2
|
|
y := x + 3y; // Statement 3
|
|
x - y; // Statement 4
|
|
|
|
|
|
(3) Conditional Statements (If-Then-Else)
|
|
ExprTk supports two forms of conditional branching or otherwise known
|
|
as if-statements. The first form, is a simple function based
|
|
conditional statement, that takes exactly three input expressions:
|
|
condition, consequent and alternative. The following is an example
|
|
expression that utilises the function based if-statement.
|
|
|
|
x := if (y < z, y + 1, 2 * z)
|
|
|
|
|
|
In the example above, if the condition 'y < z' is true, then the
|
|
consequent 'y + 1' will be evaluated, its value shall be returned and
|
|
subsequently assigned to the variable 'x'. Otherwise the alternative
|
|
'2 * z' will be evaluated and its value will be returned. This is
|
|
essentially the simplest form of an if-then-else statement. A simple
|
|
variation of the expression where the value of the if-statement is
|
|
used within another statement is as follows:
|
|
|
|
x := 3 * if (y < z, y + 1, 2 * z) / 2
|
|
|
|
|
|
The second form of if-statement resembles the standard syntax found in
|
|
most imperative languages. There are two variations of the statement:
|
|
|
|
(a) If-Statement
|
|
(b) If-Then-Else Statement
|
|
|
|
|
|
(a) If-Statement
|
|
This version of the conditional statement returns the value of the
|
|
consequent expression when the condition expression is true, else it
|
|
will return a quiet NaN value as its result.
|
|
|
|
Example 1:
|
|
x := if (y < z) y + 3;
|
|
|
|
Example 2:
|
|
x := if (y < z)
|
|
{
|
|
y + 3
|
|
};
|
|
|
|
The two example expressions above are equivalent. If the condition
|
|
'y < z' is true, the 'x' variable shall be assigned the value of the
|
|
consequent 'y + 3', otherwise it will be assigned the value of quiet
|
|
NaN. As previously discussed, if-statements are value returning
|
|
constructs, and if not properly terminated using a semi-colon, will
|
|
end-up combining with the next statement via a multiplication
|
|
operation. The following example will NOT result in the expected value
|
|
of 'w + x' being returned:
|
|
|
|
x := if (y < z) y + 3 // missing semi-colon ';'
|
|
w + x
|
|
|
|
|
|
When the above supposed multi-statement expression is compiled, the
|
|
expression will have a multiplication inserted between the two
|
|
'intended' statements resulting in the unanticipated expression:
|
|
|
|
x := (if (y < z) y + 3) * w + x
|
|
|
|
|
|
The solution to the above situation is to simply terminate the
|
|
conditional statement with a semi-colon as follows:
|
|
|
|
x := if (y < z) y + 3;
|
|
w + x
|
|
|
|
|
|
(b) If-Then-Else Statement
|
|
The second variation of the if-statement is to allow for the use of
|
|
Else and Else-If cascading statements. Examples of such statements are
|
|
as follows:
|
|
|
|
Example 1: Example 2: Example 3:
|
|
if (x < y) if (x < y) if (x > y + 1)
|
|
z := x + 3; { y := abs(x - z);
|
|
else y := z + x; else
|
|
y := x - z; z := x + 3; {
|
|
} y := z + x;
|
|
else z := x + 3;
|
|
y := x - z; };
|
|
|
|
|
|
Example 4: Example 5: Example 6:
|
|
if (2 * x < max(y,3)) if (x < y) if (x < y or (x + z) > y)
|
|
{ z := x + 3; {
|
|
y := z + x; else if (2y != z) z := x + 3;
|
|
z := x + 3; { y := x - z;
|
|
} z := x + 3; }
|
|
else if (2y - z) y := x - z; else if (abs(2y - z) >= 3)
|
|
y := x - z; } y := x - z;
|
|
else else
|
|
x * x; {
|
|
z := abs(x * x);
|
|
x * y * z;
|
|
};
|
|
|
|
|
|
In the case where there is no final else statement and the flow
|
|
through the conditional arrives at this final point, the same rules
|
|
apply to this form of if-statement as to the previous. That is a quiet
|
|
NaN shall be returned as the result of the if-statement. Furthermore
|
|
the same requirements of terminating the statement with a semi-colon
|
|
apply.
|
|
|
|
(4) Special Functions
|
|
The purpose of special functions in ExprTk is to provide compiler
|
|
generated equivalents of common mathematical expressions which can be
|
|
invoked by using the 'special function' syntax (eg: $f12(x,y,z) or
|
|
$f82(x,y,z,w)).
|
|
|
|
Special functions dramatically decrease the total evaluation time of
|
|
expressions which would otherwise have been written using the common
|
|
form by reducing the total number of nodes in the evaluation tree of
|
|
an expression and by also leveraging the compiler's ability to
|
|
correctly optimise such expressions for a given architecture.
|
|
|
|
3-Parameter 4-Parameter
|
|
+-------------+-------------+ +--------------+------------------+
|
|
| Prototype | Operation | | Prototype | Operation |
|
|
+-------------+-------------+ +--------------+------------------+
|
|
$f00(x,y,z) | (x + y) / z $f48(x,y,z,w) | x + ((y + z) / w)
|
|
$f01(x,y,z) | (x + y) * z $f49(x,y,z,w) | x + ((y + z) * w)
|
|
$f02(x,y,z) | (x + y) - z $f50(x,y,z,w) | x + ((y - z) / w)
|
|
$f03(x,y,z) | (x + y) + z $f51(x,y,z,w) | x + ((y - z) * w)
|
|
$f04(x,y,z) | (x - y) + z $f52(x,y,z,w) | x + ((y * z) / w)
|
|
$f05(x,y,z) | (x - y) / z $f53(x,y,z,w) | x + ((y * z) * w)
|
|
$f06(x,y,z) | (x - y) * z $f54(x,y,z,w) | x + ((y / z) + w)
|
|
$f07(x,y,z) | (x * y) + z $f55(x,y,z,w) | x + ((y / z) / w)
|
|
$f08(x,y,z) | (x * y) - z $f56(x,y,z,w) | x + ((y / z) * w)
|
|
$f09(x,y,z) | (x * y) / z $f57(x,y,z,w) | x - ((y + z) / w)
|
|
$f10(x,y,z) | (x * y) * z $f58(x,y,z,w) | x - ((y + z) * w)
|
|
$f11(x,y,z) | (x / y) + z $f59(x,y,z,w) | x - ((y - z) / w)
|
|
$f12(x,y,z) | (x / y) - z $f60(x,y,z,w) | x - ((y - z) * w)
|
|
$f13(x,y,z) | (x / y) / z $f61(x,y,z,w) | x - ((y * z) / w)
|
|
$f14(x,y,z) | (x / y) * z $f62(x,y,z,w) | x - ((y * z) * w)
|
|
$f15(x,y,z) | x / (y + z) $f63(x,y,z,w) | x - ((y / z) / w)
|
|
$f16(x,y,z) | x / (y - z) $f64(x,y,z,w) | x - ((y / z) * w)
|
|
$f17(x,y,z) | x / (y * z) $f65(x,y,z,w) | ((x + y) * z) - w
|
|
$f18(x,y,z) | x / (y / z) $f66(x,y,z,w) | ((x - y) * z) - w
|
|
$f19(x,y,z) | x * (y + z) $f67(x,y,z,w) | ((x * y) * z) - w
|
|
$f20(x,y,z) | x * (y - z) $f68(x,y,z,w) | ((x / y) * z) - w
|
|
$f21(x,y,z) | x * (y * z) $f69(x,y,z,w) | ((x + y) / z) - w
|
|
$f22(x,y,z) | x * (y / z) $f70(x,y,z,w) | ((x - y) / z) - w
|
|
$f23(x,y,z) | x - (y + z) $f71(x,y,z,w) | ((x * y) / z) - w
|
|
$f24(x,y,z) | x - (y - z) $f72(x,y,z,w) | ((x / y) / z) - w
|
|
$f25(x,y,z) | x - (y / z) $f73(x,y,z,w) | (x * y) + (z * w)
|
|
$f26(x,y,z) | x - (y * z) $f74(x,y,z,w) | (x * y) - (z * w)
|
|
$f27(x,y,z) | x + (y * z) $f75(x,y,z,w) | (x * y) + (z / w)
|
|
$f28(x,y,z) | x + (y / z) $f76(x,y,z,w) | (x * y) - (z / w)
|
|
$f29(x,y,z) | x + (y + z) $f77(x,y,z,w) | (x / y) + (z / w)
|
|
$f30(x,y,z) | x + (y - z) $f78(x,y,z,w) | (x / y) - (z / w)
|
|
$f31(x,y,z) | x * y^2 + z $f79(x,y,z,w) | (x / y) - (z * w)
|
|
$f32(x,y,z) | x * y^3 + z $f80(x,y,z,w) | x / (y + (z * w))
|
|
$f33(x,y,z) | x * y^4 + z $f81(x,y,z,w) | x / (y - (z * w))
|
|
$f34(x,y,z) | x * y^5 + z $f82(x,y,z,w) | x * (y + (z * w))
|
|
$f35(x,y,z) | x * y^6 + z $f83(x,y,z,w) | x * (y - (z * w))
|
|
$f36(x,y,z) | x * y^7 + z $f84(x,y,z,w) | x*y^2 + z*w^2
|
|
$f37(x,y,z) | x * y^8 + z $f85(x,y,z,w) | x*y^3 + z*w^3
|
|
$f38(x,y,z) | x * y^9 + z $f86(x,y,z,w) | x*y^4 + z*w^4
|
|
$f39(x,y,z) | x * log(y)+z $f87(x,y,z,w) | x*y^5 + z*w^5
|
|
$f40(x,y,z) | x * log(y)-z $f88(x,y,z,w) | x*y^6 + z*w^6
|
|
$f41(x,y,z) | x * log10(y)+z $f89(x,y,z,w) | x*y^7 + z*w^7
|
|
$f42(x,y,z) | x * log10(y)-z $f90(x,y,z,w) | x*y^8 + z*w^8
|
|
$f43(x,y,z) | x * sin(y)+z $f91(x,y,z,w) | x*y^9 + z*w^9
|
|
$f44(x,y,z) | x * sin(y)-z $f92(x,y,z,w) | (x and y) ? z : w
|
|
$f45(x,y,z) | x * cos(y)+z $f93(x,y,z,w) | (x or y) ? z : w
|
|
$f46(x,y,z) | x * cos(y)-z $f94(x,y,z,w) | (x < y) ? z : w
|
|
$f47(x,y,z) | x ? y : z $f95(x,y,z,w) | (x <= y) ? z : w
|
|
$f96(x,y,z,w) | (x > y) ? z : w
|
|
$f97(x,y,z,w) | (x >= y) ? z : w
|
|
$f98(x,y,z,w) | (x == y) ? z : w
|
|
$f99(x,y,z,w) | x*sin(y)+z*cos(w)
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 13 - VARIABLE, VECTOR & STRING DEFINITION]
|
|
ExprTk supports the definition of expression local variables, vectors
|
|
and strings. The definitions must be unique as shadowing is not
|
|
allowed and object lifetimes are based on scope. Definitions use the
|
|
following general form:
|
|
|
|
var <name> := <initialiser>;
|
|
|
|
(1) Variable Definition
|
|
Variables are of numeric type denoting a single value. They can be
|
|
explicitly initialised to a value, otherwise they will be defaulted to
|
|
zero. The following are examples of variable definitions:
|
|
|
|
(a) Initialise x to zero
|
|
var x;
|
|
|
|
(b) Initialise y to three
|
|
var y := 3;
|
|
|
|
(c) Initialise z to the expression
|
|
var z := if (max(1, x + y) > 2, w, v);
|
|
|
|
(d) Initialise const literal n
|
|
var n := 12 / 3;
|
|
|
|
|
|
(2) Vector Definition
|
|
Vectors are arrays of a common numeric type. The elements in a vector
|
|
can be explicitly initialised, otherwise they will all be defaulted to
|
|
zero. The following are examples of vector definitions:
|
|
|
|
(a) Initialise all values to zero
|
|
var x[3];
|
|
|
|
(b) Initialise all values to zero
|
|
var x[3] := {};
|
|
|
|
(c) Initialise all values to given value or expression
|
|
var x[3] := [ 42 ];
|
|
var y[x[]] := [ 123 + 3y + sin(w / z) ];
|
|
|
|
(d) Initialise all values iota style
|
|
var v[4] := [ 0 : +1]; // 0, 1, 2, 3
|
|
var v[5] := [-3 : -2]; // -3, -5, -7, -9, -11
|
|
|
|
(e) Initialise the first two values, all other elements to zero
|
|
var x[3] := { (1 + x[2]) / x[], (sin(y[0] / x[]) + 3) / x[] };
|
|
|
|
(f) Initialise the first three (all) values
|
|
const var size := 3;
|
|
var x[size] := { 1, 2, 3 };
|
|
|
|
(g) Initialise vector from a vector
|
|
var x[4] := { 1, 2, 3, 4 };
|
|
var y[3] := x;
|
|
var w[5] := { 1, 2 }; // 1, 2, 0, 0, 0
|
|
|
|
(h) Initialise vector from a smaller vector
|
|
var x[3] := { 1, 2, 3 };
|
|
var y[5] := x; // 1, 2, 3, ??, ??
|
|
|
|
(i) Non-initialised vector
|
|
var x[3] := null; // ?? ?? ??
|
|
|
|
(j) Error as there are too many initialisers
|
|
var x[3] := { 1, 2, 3, 4 };
|
|
|
|
(k) Error as a vector of size zero is not allowed.
|
|
var x[0];
|
|
|
|
|
|
(3) String Definition
|
|
Strings are sequences comprised of 8-bit characters. They can only be
|
|
defined with an explicit initialisation value. The following are
|
|
examples of string variable definitions:
|
|
|
|
(a) Initialise to a string
|
|
var x := 'abc';
|
|
|
|
(b) Initialise to an empty string
|
|
var x := '';
|
|
|
|
(c) Initialise to a string expression
|
|
var x := 'abc' + '123';
|
|
|
|
(d) Initialise to a string range
|
|
var x := 'abc123'[2:4];
|
|
|
|
(e) Initialise to another string variable
|
|
var x := 'abc';
|
|
var y := x;
|
|
|
|
(f) Initialise to another string variable range
|
|
var x := 'abc123';
|
|
var y := x[2:4];
|
|
|
|
(g) Initialise to a string expression
|
|
var x := 'abc';
|
|
var y := x + '123';
|
|
|
|
(h) Initialise to a string expression range
|
|
var x := 'abc';
|
|
var y := (x + '123')[1:3];
|
|
|
|
|
|
(4) Return Value
|
|
Variable and vector definitions have a return value. In the case of
|
|
variable definitions, the value to which the variable is initialised
|
|
will be returned. Where as for vectors, the value of the first element
|
|
(eg: v[0]) shall be returned.
|
|
|
|
8 == ((var x := 7;) + 1)
|
|
4 == (var y[3] := {4, 5, 6};)
|
|
|
|
|
|
(5) Variable/Vector Assignment
|
|
The value of a variable can be assigned to a vector and a vector or a
|
|
vector expression can be assigned to a variable.
|
|
|
|
(a) Variable To Vector:
|
|
Every element of the vector is assigned the value of the variable
|
|
or expression.
|
|
var x := 3;
|
|
var y[3] := { 1, 2, 3 };
|
|
y := x + 1;
|
|
|
|
(b) Vector To Variable:
|
|
The variable is assigned the value of the first element of the
|
|
vector (aka vec[0])
|
|
var x := 3;
|
|
var y[3] := { 1, 2, 3 };
|
|
x := y + 1;
|
|
|
|
|
|
Note10: During the expression compilation phase, tokens are classified
|
|
based on the following priorities:
|
|
|
|
(a) Reserved keywords or operators (+, -, and, or, etc)
|
|
(b) Base functions (abs, sin, cos, min, max etc)
|
|
(c) Symbol table variables
|
|
(d) Expression local defined variables
|
|
(e) Symbol table functions
|
|
(f) Unknown symbol resolver based variables
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 14 - VECTOR PROCESSING]
|
|
ExprTk provides support for various forms of vector oriented
|
|
arithmetic, inequalities and processing. The various supported pairs
|
|
are as follows:
|
|
|
|
(a) vector and vector (eg: v0 + v1)
|
|
(b) vector and scalar (eg: v + 33)
|
|
(c) scalar and vector (eg: 22 * v)
|
|
|
|
The following is a list of operations that can be used in conjunction
|
|
with vectors:
|
|
|
|
(a) Arithmetic: +, -, *, /, %
|
|
(b) Exponentiation: vector ^ scalar
|
|
(c) Assignment: :=, +=, -=, *=, /=, %=, <=>
|
|
(d) Inequalities: <, <=, >, >=, ==, =, equal
|
|
(e) Boolean logic: and, nand, nor, or, xnor, xor
|
|
(f) Unary operations:
|
|
abs, acos, acosh, asin, asinh, atan, atanh, ceil, cos, cosh,
|
|
cot, csc, deg2grad, deg2rad, erf, erfc, exp, expm1, floor,
|
|
frac, grad2deg, log, log10, log1p, log2, rad2deg, round, sec,
|
|
sgn, sin, sinc, sinh, sqrt, swap, tan, tanh, trunc,
|
|
thresholding
|
|
(g) Aggregate and Reduce operations:
|
|
avg, max, min, mul, dot, dotk, sum, sumk, count, all_true,
|
|
all_false, any_true, any_false
|
|
(h) Transformation operations:
|
|
copy, diff, reverse, rotate-left/right, shift-left/right, sort,
|
|
nth_element
|
|
(i) BLAS-L1:
|
|
axpy, axpby, axpyz, axpbyz, axpbz
|
|
|
|
Note11: When one of the above described operations is being performed
|
|
between two vectors, the operation will only span the size of the
|
|
smallest vector. The elements of the larger vector outside of the
|
|
range will not be included. The operation itself will be processed
|
|
element-wise over values of the smaller of the two ranges.
|
|
|
|
The following simple example demonstrates the vector processing
|
|
capabilities by computing the dot-product of the vectors v0 and v1 and
|
|
then assigning it to the variable v0dotv1:
|
|
|
|
var v0[3] := { 1, 2, 3 };
|
|
var v1[3] := { 4, 5, 6 };
|
|
var v0dotv1 := sum(v0 * v1);
|
|
|
|
|
|
The following is a for-loop based implementation that is equivalent to
|
|
the previously mentioned dot-product computation expression:
|
|
|
|
var v0[3] := { 1, 2, 3 };
|
|
var v1[3] := { 4, 5, 6 };
|
|
var v0dotv1;
|
|
|
|
for (var i := 0; i < min(v0[],v1[]); i += 1)
|
|
{
|
|
v0dotv1 += (v0[i] * v1[i]);
|
|
}
|
|
|
|
|
|
Note12: When the aggregate or reduction operations denoted above are
|
|
used in conjunction with a vector or vector expression, the return
|
|
value is not a vector but rather a single value.
|
|
|
|
var x[3] := { 1, 2, 3 };
|
|
|
|
sum(x) == 6
|
|
sum(1 + 2x) == 15
|
|
avg(3x + 1) == 7
|
|
min(1 / x) == (1 / 3)
|
|
max(x / 2) == (3 / 2)
|
|
sum(x > 0 and x < 5) == x[]
|
|
|
|
|
|
When utilising external user defined vectors via the symbol table as
|
|
opposed to expression local defined vectors, the typical 'add_vector'
|
|
method from the symbol table will register the entirety of the vector
|
|
that is passed. The following example attempts to evaluate the sum of
|
|
elements of the external user defined vector within a typical yet
|
|
trivial expression:
|
|
|
|
const std::string reduce_program = " sum(2 * v + 1) ";
|
|
|
|
std::vector<T> v0 { T(1.1), T(2.2), ..... , T(99.99) };
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_vector("v",v);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(reduce_program,expression);
|
|
|
|
T sum = expression.value();
|
|
|
|
|
|
For the most part, this is a very common use-case. However there may
|
|
be situations where one may want to evaluate the same vector oriented
|
|
expression many times over, but using different vectors or sub ranges
|
|
of the same vector of the same size to that of the original upon every
|
|
evaluation.
|
|
|
|
The usual solution is to either recompile the expression for the new
|
|
vector instance, or to copy the contents from the new vector to the
|
|
symbol table registered vector and then perform the evaluation. When
|
|
the vectors are large or the re-evaluation attempts are numerous,
|
|
these solutions can become rather time consuming and generally
|
|
inefficient.
|
|
|
|
std::vector<T> v1 { T(2.2), T(2.2), ..... , T(2.2) };
|
|
std::vector<T> v2 { T(3.3), T(3.3), ..... , T(3.3) };
|
|
std::vector<T> v3 { T(4.4), T(4.4), ..... , T(4.4) };
|
|
|
|
std::vector<std::vector<T>> vv { v1, v2, v3 };
|
|
...
|
|
T sum = T(0);
|
|
|
|
for (auto& new_vec : vv)
|
|
{
|
|
v = new_vec; // update vector
|
|
sum += expression.value();
|
|
}
|
|
|
|
|
|
A solution to the above 'efficiency' problem, is to use the
|
|
exprtk::vector_view object. The vector_view is instantiated with a
|
|
size and backing based upon a vector. Upon evaluations if the backing
|
|
needs to be 'updated' to either another vector or sub-range, the
|
|
vector_view instance can be efficiently rebased, and the expression
|
|
evaluated as normal.
|
|
|
|
exprtk::vector_view<T> view = exprtk::make_vector_view(v,v.size());
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_vector("v",view);
|
|
|
|
...
|
|
|
|
T sum = T(0);
|
|
|
|
for (auto& new_vec : vv)
|
|
{
|
|
view.rebase(new_vec.data()); // update vector
|
|
sum += expression.value();
|
|
}
|
|
|
|
|
|
Another useful feature of exprtk::vector_view is that all such vectors
|
|
can have their sizes modified (or "resized"). The resizing of the
|
|
associated vectors can happen either between or during evaluations.
|
|
|
|
std::vector<T> v = { 1, 2, 3, 4, 5, 6, 7, 8 };
|
|
exprtk::vector_view<T> view = exprtk::make_vector_view(v,v.size());
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_vector("v",view);
|
|
|
|
const std::string expression_string = "v[]";
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string, expression);
|
|
|
|
for (std::size_t i = 1; i <= v.size(); ++i)
|
|
{
|
|
vv.set_size(i);
|
|
expression.value();
|
|
}
|
|
|
|
|
|
In the example above, a vector_view is instantiated with a std::vector
|
|
instance with eight elements and registered to the given symbol_table.
|
|
An expression is then compiled, which in this case simply returns the
|
|
size of the vector at that point in time. The expression is evaluated
|
|
eight times (size of vector times), where upon each iteration the size
|
|
of the vector is changed with values ranging from one to eight.
|
|
|
|
Note13: When modifying the size of a vector, the new size must be at
|
|
least one or larger and must not exceed the original size of the
|
|
vector_view when it was instantiated.
|
|
|
|
Note14: The lifetime of any parser, symbol_table or expression
|
|
instance must not exceed that of any vector_view instance that has
|
|
been registered with it. Furthermore the lifetime of a vector_view
|
|
must not exceed that of the underlying vector instance it is
|
|
associated with.
|
|
|
|
Note15: In a multi-threaded context the rebase function should not be
|
|
called during associated expression evaluation, as this will lead to
|
|
undefined behaviour (eg: torn reads and writes).
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 15 - USER DEFINED FUNCTIONS]
|
|
ExprTk provides a means whereby custom functions can be defined and
|
|
utilised within expressions. The concept requires the user to
|
|
provide a reference to the function coupled with an associated name
|
|
that will be invoked within expressions. Functions may take numerous
|
|
inputs but will always return a single value of the underlying numeric
|
|
type.
|
|
|
|
During expression compilation when required the reference to the
|
|
function shall be obtained from the associated symbol_table and be
|
|
embedded into the expression.
|
|
|
|
There are five types of function interface:
|
|
|
|
+---+----------------------+--------------+----------------------+
|
|
| # | Name | Return Type | Input Types |
|
|
+---+----------------------+--------------+----------------------+
|
|
| 1 | ifunction | Scalar | Scalar |
|
|
| 2 | ivararg_function | Scalar | Scalar |
|
|
| 3 | igeneric_function | Scalar | Scalar,Vector,String |
|
|
| 4 | igeneric_function II | String | Scalar,Vector,String |
|
|
| 5 | igeneric_function III| String/Scalar| Scalar,Vector,String |
|
|
| 6 | function_compositor | Scalar | Scalar |
|
|
+---+----------------------+--------------+----------------------+
|
|
|
|
(1) ifunction
|
|
This interface supports zero to 20 input parameters of only the scalar
|
|
type (numbers). The usage requires a custom function be derived from
|
|
ifunction and to override one of the 21 function operators. As part of
|
|
the constructor the custom function will define how many parameters it
|
|
expects to handle. The following example defines a 3 parameter
|
|
function called 'foo':
|
|
|
|
template <typename T>
|
|
struct foo final : public exprtk::ifunction<T>
|
|
{
|
|
foo() : exprtk::ifunction<T>(3)
|
|
{}
|
|
|
|
T operator()(const T& v1, const T& v2, const T& v3) override
|
|
{
|
|
return T(1) + (v1 * v2) / T(v3);
|
|
}
|
|
};
|
|
|
|
|
|
(2) ivararg_function
|
|
This interface supports a variable number of scalar arguments as input
|
|
into the function. The function operator interface uses a std::vector
|
|
specialised upon type T to facilitate parameter passing. The following
|
|
example defines a vararg function called 'boo':
|
|
|
|
template <typename T>
|
|
struct boo final : public exprtk::ivararg_function<T>
|
|
{
|
|
inline T operator()(const std::vector<T>& arglist) override
|
|
{
|
|
T result = T(0);
|
|
|
|
for (std::size_t i = 0; i < arglist.size(); ++i)
|
|
{
|
|
result += arglist[i] / arglist[i > 0 ? (i - 1) : 0];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
|
|
(3) igeneric_function
|
|
This interface supports a variable number of arguments and types as
|
|
input into the function. The function operator interface uses a
|
|
std::vector specialised upon the type_store type to facilitate
|
|
parameter passing.
|
|
|
|
Scalar <-- function(i_0, i_1, i_2....., i_N)
|
|
|
|
|
|
The fundamental types that can be passed into the function as
|
|
parameters and their views are as follows:
|
|
|
|
(1) Scalar - scalar_view
|
|
(2) Vector - vector_view
|
|
(3) String - string_view
|
|
|
|
|
|
The above denoted type views provide non-const reference-like access
|
|
to each parameter, as such modifications made to the input parameters
|
|
will persist after the function call has completed. The following
|
|
example defines a generic function called 'too':
|
|
|
|
template <typename T>
|
|
struct too final : public exprtk::igeneric_function<T>
|
|
{
|
|
typedef typename exprtk::igeneric_function<T>::parameter_list_t
|
|
parameter_list_t;
|
|
|
|
too()
|
|
{}
|
|
|
|
inline T operator()(parameter_list_t parameters) override
|
|
{
|
|
for (std::size_t i = 0; i < parameters.size(); ++i)
|
|
{
|
|
...
|
|
}
|
|
|
|
return T(0);
|
|
}
|
|
};
|
|
|
|
|
|
In the example above, the input 'parameters' to the function operator,
|
|
parameter_list_t, is a type of std::vector of type_store. Each
|
|
type_store instance has a member called 'type' which holds the
|
|
enumeration pertaining to the underlying type of the type_store. There
|
|
are three type enumerations:
|
|
|
|
(1) e_scalar - literals, variables, vector elements, expressions
|
|
eg: 123.456, x, vec[3x + 1], 2x + 3
|
|
|
|
(2) e_vector - vectors, vector expressions
|
|
eg: vec1, 2 * vec1 + vec2 / 3
|
|
|
|
(3) e_string - strings, string literals and range variants of both
|
|
eg: 'AString', s0, 'AString'[x:y], s1[1 + x:] + 'AString'
|
|
|
|
|
|
Each of the parameters can be accessed using its designated view.
|
|
A typical loop for processing the parameters is as follows:
|
|
|
|
inline T operator()(parameter_list_t parameters)
|
|
{
|
|
typedef typename exprtk::igeneric_function<T>::generic_type
|
|
generic_type;
|
|
|
|
typedef typename generic_type::scalar_view scalar_t;
|
|
typedef typename generic_type::vector_view vector_t;
|
|
typedef typename generic_type::string_view string_t;
|
|
|
|
for (std::size_t i = 0; i < parameters.size(); ++i)
|
|
{
|
|
generic_type& gt = parameters[i];
|
|
|
|
if (generic_type::e_scalar == gt.type)
|
|
{
|
|
scalar_t x(gt);
|
|
...
|
|
}
|
|
else if (generic_type::e_vector == gt.type)
|
|
{
|
|
vector_t vector(gt);
|
|
...
|
|
}
|
|
else if (generic_type::e_string == gt.type)
|
|
{
|
|
string_t string(gt);
|
|
...
|
|
}
|
|
}
|
|
|
|
return T(0);
|
|
}
|
|
|
|
|
|
Most often than not a custom generic function will require a specific
|
|
sequence of parameters, rather than some arbitrary sequence of types.
|
|
In those situations, ExprTk can perform compile-time type checking to
|
|
validate that function invocations are carried out using the correct
|
|
sequence of parameters. Furthermore performing the checks at compile
|
|
-time rather than at run-time (aka every time the function is invoked)
|
|
will result in expression evaluation performance gains.
|
|
|
|
Compile-time type checking of input parameters can be requested by
|
|
passing a string to the constructor of the igeneric_function that
|
|
represents the required sequence of parameter types. When no parameter
|
|
sequence is provided, it is implied the function can accept a variable
|
|
number of parameters comprised of any of the fundamental types.
|
|
|
|
Each fundamental type has an associated character. The following is a
|
|
listing of said characters and their meanings:
|
|
|
|
(1) T - Scalar
|
|
(2) V - Vector
|
|
(3) S - String
|
|
(4) Z - Zero or no parameters
|
|
(5) ? - Any type (Scalar, Vector or String)
|
|
(6) * - Wildcard operator
|
|
(7) | - Parameter sequence delimiter
|
|
|
|
|
|
No other characters other than the seven denoted above may be included
|
|
in the parameter sequence definition. If any such invalid characters
|
|
do exist, registration of the associated generic function to a symbol
|
|
table ('add_function' method) will fail. If the parameter sequence is
|
|
modified resulting in it becoming invalid after having been added to
|
|
the symbol table but before the compilation step, a compilation error
|
|
will be incurred.
|
|
|
|
The following example demonstrates a simple generic function
|
|
implementation with a user specified parameter sequence:
|
|
|
|
template <typename T>
|
|
struct moo final : public exprtk::igeneric_function<T>
|
|
{
|
|
typedef typename exprtk::igeneric_function<T>::parameter_list_t
|
|
parameter_list_t;
|
|
|
|
moo()
|
|
: exprtk::igeneric_function<T>("SVTT")
|
|
{}
|
|
|
|
inline T operator()(parameter_list_t parameters) override
|
|
{
|
|
...
|
|
}
|
|
};
|
|
|
|
|
|
In the example above the generic function 'moo' expects exactly four
|
|
parameters in the following sequence:
|
|
|
|
(1) String
|
|
(2) Vector
|
|
(3) Scalar
|
|
(4) Scalar
|
|
|
|
Note16: The 'Z' or no parameter option may not be used in conjunction
|
|
with any other type option in a parameter sequence. When incorporated
|
|
in the parameter sequence list, the 'No Parameter' option indicates
|
|
that the function may be invoked without any parameters being passed.
|
|
For more information refer to the section: 'Zero Parameter Functions'
|
|
|
|
|
|
(4) igeneric_function II
|
|
This interface is identical to the igeneric_function, in that in can
|
|
consume an arbitrary number of parameters of varying type, but the
|
|
difference being that the function returns a string and as such is
|
|
treated as a string when invoked within expressions. As a result the
|
|
function call can alias a string and interact with other strings in
|
|
situations such as concatenation and equality operations.
|
|
|
|
String <-- function(i_0, i_1, i_2....., i_N)
|
|
|
|
|
|
The following example defines a generic function named 'toupper' with
|
|
the string return type function operator being explicitly overridden:
|
|
|
|
template <typename T>
|
|
struct toupper final : public exprtk::igeneric_function<T>
|
|
{
|
|
typedef exprtk::igeneric_function<T> igenfunct_t;
|
|
typedef typename igenfunct_t::generic_type generic_t;
|
|
typedef typename igenfunct_t::parameter_list_t parameter_list_t;
|
|
typedef typename generic_t::string_view string_t;
|
|
|
|
toupper()
|
|
: exprtk::igeneric_function<T>("S",igenfunct_t::e_rtrn_string)
|
|
{}
|
|
|
|
inline T operator()(std::string& result,
|
|
parameter_list_t parameters) override
|
|
{
|
|
result.clear();
|
|
|
|
string_t string(parameters[0]);
|
|
|
|
for (std::size_t i = 0; i < string.size(); ++i)
|
|
{
|
|
result += std::toupper(string[i]);
|
|
}
|
|
|
|
return T(0);
|
|
}
|
|
};
|
|
|
|
|
|
In the example above the generic function 'toupper' expects only one
|
|
input parameter of type string, as noted by the parameter sequence
|
|
string passed during the constructor. Furthermore a second parameter
|
|
is passed to the constructor indicating that it should be treated as a
|
|
string returning function - by default it is assumed to be a scalar
|
|
returning function.
|
|
|
|
When executed, the function will return as a result a copy of the
|
|
input string converted to uppercase form. An example expression using
|
|
the toupper function registered as the symbol 'toupper' is as follows:
|
|
|
|
"'ABCDEF' == toupper('aBc') + toupper('DeF')"
|
|
|
|
|
|
Note17: When adding a string type returning generic function to a
|
|
symbol table the 'add_function' is invoked. The example below
|
|
demonstrates how this can be done:
|
|
|
|
toupper<T> tu;
|
|
|
|
exprtk::symbol_table<T> symbol_table;
|
|
|
|
symbol_table.add_function("toupper",tu);
|
|
|
|
|
|
Note18: Two further refinements to the type checking facility are the
|
|
possibilities of a variable number of common types which can be
|
|
accomplished by using a wildcard '*' and a special 'any type' which is
|
|
done using the '?' character. It should be noted that the wildcard
|
|
operator is associated with the previous type in the sequence and
|
|
implies one or more of that type.
|
|
|
|
template <typename T>
|
|
struct zoo final : public exprtk::igeneric_function<T>
|
|
{
|
|
typedef typename exprtk::igeneric_function<T>::parameter_list_t
|
|
parameter_list_t;
|
|
|
|
zoo()
|
|
: exprtk::igeneric_function<T>("SVT*V?")
|
|
{}
|
|
|
|
inline T operator()(parameter_list_t parameters) override
|
|
{
|
|
...
|
|
}
|
|
};
|
|
|
|
|
|
In the example above the generic function 'zoo' expects at least five
|
|
parameters in the following sequence:
|
|
|
|
(1) String
|
|
(2) Vector
|
|
(3) One or more Scalars
|
|
(4) Vector
|
|
(5) Any type (one type of either a scalar, vector or string)
|
|
|
|
|
|
A final piece of type checking functionality is available for the
|
|
scenarios where a single function name is intended to be used for
|
|
multiple distinct parameter sequences, another name for this feature
|
|
is function overloading. The parameter sequences are passed to the
|
|
constructor as a single string delimited by the pipe '|' character.
|
|
Two specific overrides of the function operator are provided one for
|
|
standard generic functions and one for string returning functions. The
|
|
overrides are as follows:
|
|
|
|
// Scalar <-- function(psi,i_0,i_1,....,i_N)
|
|
inline T operator()(const std::size_t& ps_index,
|
|
parameter_list_t parameters)
|
|
{
|
|
...
|
|
}
|
|
|
|
// String <-- function(psi,i_0,i_1,....,i_N)
|
|
inline T operator()(const std::size_t& ps_index,
|
|
std::string& result,
|
|
parameter_list_t parameters)
|
|
{
|
|
...
|
|
}
|
|
|
|
|
|
When the function operator is invoked the 'ps_index' parameter will
|
|
have as its value the index of the parameter sequence that matches the
|
|
specific invocation. This way complex and time consuming type checking
|
|
conditions need not be executed in the function itself but rather a
|
|
simple and efficient dispatch to a specific implementation for that
|
|
particular parameter sequence can be performed.
|
|
|
|
template <typename T>
|
|
struct roo final : public exprtk::igeneric_function<T>
|
|
{
|
|
typedef typename exprtk::igeneric_function<T>::parameter_list_t
|
|
parameter_list_t;
|
|
|
|
moo()
|
|
: exprtk::igeneric_function<T>("SVTT|SS|TTV|S?V*S")
|
|
{}
|
|
|
|
inline T operator()(const std::size_t& ps_index,
|
|
parameter_list_t parameters) override
|
|
{
|
|
...
|
|
}
|
|
};
|
|
|
|
|
|
In the example above there are four distinct parameter sequences that
|
|
can be processed by the generic function 'roo'. Any other parameter
|
|
sequences will cause a compilation error. The four valid sequences are
|
|
as follows:
|
|
|
|
Sequence-0 Sequence-1 Sequence-2 Sequence-3
|
|
'SVTT' 'SS' 'TTV' 'S?V*S'
|
|
(1) String (1) String (1) Scalar (1) String
|
|
(2) Vector (2) String (2) Scalar (2) Any Type
|
|
(3) Scalar (3) Vector (3) One or more Vectors
|
|
(4) Scalar (4) String
|
|
|
|
|
|
(5) igeneric_function III
|
|
In this section we will discuss an extension of the igeneric_function
|
|
interface that will allow for the overloading of a user defined custom
|
|
function, where by it can return either a scalar or string value type
|
|
depending on the input parameter sequence with which the function is
|
|
invoked.
|
|
|
|
template <typename T>
|
|
struct foo final : public exprtk::igeneric_function<T>
|
|
{
|
|
typedef typename exprtk::igeneric_function<T>::parameter_list_t
|
|
parameter_list_t;
|
|
|
|
foo()
|
|
: exprtk::igeneric_function<T>
|
|
(
|
|
"T:T|S:TS",
|
|
igfun_t::e_rtrn_overload
|
|
)
|
|
{}
|
|
|
|
// Scalar value returning invocations
|
|
inline T operator()(const std::size_t& ps_index,
|
|
parameter_list_t parameters) override
|
|
{
|
|
...
|
|
}
|
|
|
|
// String value returning invocations
|
|
inline T operator()(const std::size_t& ps_index,
|
|
std::string& result,
|
|
parameter_list_t& parameters) override
|
|
{
|
|
...
|
|
}
|
|
};
|
|
|
|
|
|
In the example above the custom user defined function "foo" can be
|
|
invoked by using either one of two input parameter sequences, which
|
|
are defined as follows:
|
|
|
|
Sequence-0 Sequence-1
|
|
'T' -> T 'TS' -> S
|
|
(1) Scalar (1) Scalar
|
|
(2) String
|
|
|
|
|
|
The parameter sequence definitions are identical to the previously
|
|
defined igeneric_function, with the exception of the inclusion of the
|
|
return type - which can only be either a scalar T or a string S.
|
|
|
|
|
|
(6) function_compositor
|
|
The function compositor is a factory that allows one to define and
|
|
construct a function using ExprTk syntax. The functions are limited to
|
|
returning a single scalar value and consuming up to six parameters as
|
|
input.
|
|
|
|
All composited functions are registered with a symbol table, allowing
|
|
them to call other functions and use variables that have been
|
|
registered with the symbol table instance. Furthermore the functions
|
|
can be recursive in nature due to the inherent function prototype
|
|
forwarding that occurs during construction. The following example
|
|
defines, by using two different methods, composited functions and
|
|
implicitly registering the functions with the denoted symbol table.
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::function_compositor<T> compositor_t;
|
|
typedef typename compositor_t::function function_t;
|
|
|
|
T avogadro = T(6.022e23);
|
|
|
|
symbol_table_t symbol_table;
|
|
|
|
symbol_table.add_constant("avogadro", avogadro);
|
|
|
|
compositor_t compositor(symbol_table);
|
|
|
|
// Define function koo0(v1, v2) { ... }
|
|
compositor.add(
|
|
function_t("koo0"),
|
|
.vars("v1", "v2")
|
|
.expression
|
|
(
|
|
" 1 + cos(v1 * v2) / avogadro; "
|
|
));
|
|
|
|
// Define function koo1(x, y, z) { ... }
|
|
compositor.add(
|
|
function_t()
|
|
.name("koo1")
|
|
.var("x").var("y").var("z")
|
|
.expression
|
|
(
|
|
"1 + koo0(x * y, 3) / z;"
|
|
));
|
|
|
|
|
|
A function compositor can also be instantiated without a symbol_table.
|
|
When this is the case an internal symbol_table is used for holding the
|
|
references to the composited functions.
|
|
|
|
compositor_t compositor;
|
|
|
|
// Define function koo2(v1, v2) { ... }
|
|
compositor.add(
|
|
function_t("koo2"),
|
|
.vars("v1", "v2", "v3")
|
|
.expression
|
|
( " abs(v1 * v2) / v3; " ));
|
|
|
|
|
|
When wanting to reference functions from the compositor above in an
|
|
expression, the compositor's symbol_table will need to be registered
|
|
with the expression prior to compilation, as is demonstrated in the
|
|
following code:
|
|
|
|
expression_t expression;
|
|
.
|
|
.
|
|
expression.register_symbol_table(compositor.symbol_table());
|
|
|
|
|
|
In the situation where more than one symbol table's contents will be
|
|
required by the functions being composited, then those symbol tables
|
|
can be registered as auxiliary symbol tables with the compositor:
|
|
|
|
symbol_table_t global_symbol_table;
|
|
symbol_table_t local_symbol_table;
|
|
.
|
|
.
|
|
.
|
|
compositor_t compositor;
|
|
|
|
compositor.add_auxiliary_symtab(global_symbol_table);
|
|
compositor.add_auxiliary_symtab(local_symbol_table );
|
|
|
|
Note19: In the event, that two or more symbol tables contain similarly
|
|
named variables, vectors, strings or functions, the order of
|
|
registration with the compositor shall determine the symbol table from
|
|
which the target symbol will be referenced.
|
|
|
|
|
|
(7) Using Functions In Expressions
|
|
For the above denoted custom and composited functions to be used in an
|
|
expression, an instance of each function needs to be registered with a
|
|
symbol_table that has been associated with the expression instance.
|
|
The following demonstrates how all the pieces are put together:
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
typedef exprtk::function_compositor<double> compositor_t;
|
|
typedef typename compositor_t::function function_t;
|
|
|
|
foo<double> f;
|
|
boo<double> b;
|
|
too<double> t;
|
|
toupper<double> tu;
|
|
|
|
symbol_table_t symbol_table;
|
|
compositor_t compositor(symbol_table);
|
|
|
|
symbol_table.add_function("foo",f);
|
|
symbol_table.add_function("boo",b);
|
|
symbol_table.add_function("too",t);
|
|
|
|
symbol_table
|
|
.add_function("toupper", tu, symbol_table_t::e_ft_strfunc);
|
|
|
|
compositor.add(
|
|
function_t("koo")
|
|
.var("v1")
|
|
.var("v2")
|
|
.expression
|
|
(
|
|
"1 + cos(v1 * v2) / 3;"
|
|
));
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
const std::string expression_str =
|
|
" if (foo(1,2,3) + boo(1) > boo(1/2, 2/3, 3/4, 4/5)) "
|
|
" koo(3,4); "
|
|
" else "
|
|
" too(2 * v1 + v2 / 3, 'abcdef'[2:4], 3.3); "
|
|
" ";
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_str,expression);
|
|
|
|
expression.value();
|
|
|
|
|
|
(8) Function Side-Effects
|
|
All function calls are assumed to have side-effects by default. This
|
|
assumption implicitly disables constant folding optimisations when all
|
|
parameters being passed to the function are deduced as being constants
|
|
at compile time.
|
|
|
|
If it is certain that the function being registered does not have any
|
|
side-effects and can be correctly constant folded where appropriate,
|
|
then during the construction of the function the side-effect trait of
|
|
the function can be disabled.
|
|
|
|
template <typename T>
|
|
struct foo final : public exprtk::ifunction<T>
|
|
{
|
|
foo() : exprtk::ifunction<T>(3)
|
|
{
|
|
exprtk::disable_has_side_effects(*this);
|
|
}
|
|
|
|
T operator()(const T& v1, const T& v2, const T& v3) override
|
|
{ ... }
|
|
};
|
|
|
|
|
|
(9) Zero Parameter Functions
|
|
When either an ifunction, ivararg_function or igeneric_function
|
|
derived type is defined with zero number of parameters, there are two
|
|
calling conventions within expressions that are allowed. For a
|
|
function named 'foo' with zero input parameters the calling styles are
|
|
as follows:
|
|
|
|
(1) x + sin(foo()- 2) / y
|
|
(2) x + sin(foo - 2) / y
|
|
|
|
|
|
By default the zero parameter trait is disabled. In order to enable
|
|
it, a process similar to that of enabling of the side-effect trait is
|
|
carried out:
|
|
|
|
template <typename T>
|
|
struct foo final : public exprtk::ivararg_function<T>
|
|
{
|
|
foo()
|
|
{
|
|
exprtk::enable_zero_parameters(*this);
|
|
}
|
|
|
|
inline T operator()(const std::vector<T>& arglist) override
|
|
{ ... }
|
|
};
|
|
|
|
|
|
Note20: For the igeneric_function type, there also needs to be a 'Z'
|
|
parameter sequence defined in order for the zero parameter trait to
|
|
properly take effect otherwise a compilation error will occur.
|
|
|
|
|
|
(10) Free Functions
|
|
The ExprTk symbol table supports the registration of free functions
|
|
and lambdas (anonymous functors) for use in expressions. The basic
|
|
requirements are similar to those found in ifunction derived user
|
|
defined functions. This includes support for free functions using
|
|
anywhere from zero up to fifteen input parameters of scalar type, with
|
|
a return type that is also scalar. Furthermore such functions will by
|
|
default be assumed to have side-effects and hence will not participate
|
|
in constant folding optimisations.
|
|
|
|
In the following example, a one input parameter free function named
|
|
'compute1', a two input parameter template free function named
|
|
'compute2' and a three input parameter lambda named 'compute3' will be
|
|
registered with the given symbol_table instance:
|
|
|
|
double compute1(double v0)
|
|
{
|
|
return 2.0 * std::abs(v0);
|
|
}
|
|
|
|
template <typename T>
|
|
T compute2(T v0, T v1)
|
|
{
|
|
return 2.0 * v0 + v1 / 3.0;
|
|
}
|
|
.
|
|
.
|
|
.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
|
|
symbol_table_t symbol_table;
|
|
|
|
symbol_table.add_function("compute1", compute1);
|
|
symbol_table.add_function("compute2", compute2<double>);
|
|
|
|
symbol_table.add_function(
|
|
"compute3",
|
|
[](double v0, double v1, double v2) -> double
|
|
{ return v0 / v1 + v2; });
|
|
|
|
|
|
Note21: Similar to variables registered with symbol_table instances,
|
|
for any of the following function providers:
|
|
|
|
1. ifunction
|
|
2. ivararg_function
|
|
3. igeneric_function
|
|
4. function_compositor
|
|
5. Free function
|
|
7. Lambda
|
|
|
|
|
|
Their instance lifetimes must exceed the symbol_tables and expressions
|
|
they are registered with. In the event that is not the case, the
|
|
expected result shall be undefined behaviour.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 16 - EXPRESSION DEPENDENTS]
|
|
Any expression that is not a literal (aka constant) will have
|
|
dependencies. The types of 'dependencies' an expression can have are
|
|
as follows:
|
|
|
|
(a) Variables
|
|
(b) Vectors
|
|
(c) Strings
|
|
(d) Functions
|
|
(e) Assignments
|
|
|
|
|
|
In the following example the denoted expression has its various
|
|
dependencies listed:
|
|
|
|
z := abs(x + sin(2 * pi / y))
|
|
|
|
(a) Variables: x, y, z and pi
|
|
(b) Functions: abs, sin
|
|
(c) Assignments: z
|
|
|
|
|
|
ExprTk allows for the derivation of expression dependencies via the
|
|
'dependent_entity_collector' (DEC). When activated either through
|
|
'compile_options' at the construction of the parser or through calls
|
|
to enabler methods just prior to compilation, the DEC will proceed to
|
|
collect any of the relevant types that are encountered during the
|
|
parsing phase. Once the compilation process has successfully
|
|
completed, the caller can then obtain a list of symbols and their
|
|
associated types from the DEC.
|
|
|
|
The kinds of questions one can ask regarding the dependent entities
|
|
within an expression are as follows:
|
|
|
|
* What user defined variables, vectors or strings are used?
|
|
* What functions or custom user functions are used?
|
|
* Which variables, vectors or strings have values assigned to them?
|
|
|
|
|
|
The following example demonstrates usage of the DEC in determining the
|
|
dependents of the given expression:
|
|
|
|
typedef typename parser_t::
|
|
dependent_entity_collector::symbol_t symbol_t;
|
|
|
|
const std::string expression_string =
|
|
"z := abs(x + sin(2 * pi / y))";
|
|
|
|
T x,y,z;
|
|
|
|
parser_t parser;
|
|
symbol_table_t symbol_table;
|
|
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
symbol_table.add_variable("z",z);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
// Collect only variable and function symbols
|
|
parser.dec().collect_variables() = true;
|
|
parser.dec().collect_functions() = true;
|
|
|
|
if (!parser.compile(expression_string,expression))
|
|
{
|
|
// error....
|
|
}
|
|
|
|
std::deque<symbol_t> symbol_list;
|
|
|
|
parser.dec().symbols(symbol_list);
|
|
|
|
for (std::size_t i = 0; i < symbol_list.size(); ++i)
|
|
{
|
|
const symbol_t& symbol = symbol_list[i];
|
|
|
|
switch (symbol.second)
|
|
{
|
|
case parser_t::e_st_variable : ... break;
|
|
case parser_t::e_st_vector : ... break;
|
|
case parser_t::e_st_string : ... break;
|
|
case parser_t::e_st_function : ... break;
|
|
}
|
|
}
|
|
|
|
|
|
Note22: The 'symbol_t' type is a std::pair comprising of the symbol
|
|
name (std::string) and the associated type of the symbol as denoted by
|
|
the cases in the switch statement.
|
|
|
|
Having particular symbols (variable or function) present in an
|
|
expression is one form of dependency. Another and just as interesting
|
|
and important type of dependency is that of assignments. Assignments
|
|
are the set of dependent symbols that 'may' have their values modified
|
|
within an expression. The following are example expressions and their
|
|
associated assignments:
|
|
|
|
Assignments Expression
|
|
(1) x x := y + z
|
|
(2) x, y x += y += z
|
|
(3) x, y, z x := y += sin(z := w + 2)
|
|
(4) w, z if (x > y, z := x + 2, w := 'A String')
|
|
(5) None x + y + z
|
|
|
|
|
|
Note23: In expression 4, both variables 'w' and 'z' are denoted as
|
|
being assignments even though only one of them can ever be modified at
|
|
the time of evaluation. Furthermore the determination of which of the
|
|
two variables the modification will occur upon can only be known with
|
|
certainty at evaluation time and not beforehand, hence both are listed
|
|
as being candidates for assignment.
|
|
|
|
The following builds upon the previous example demonstrating the usage
|
|
of the DEC in determining the 'assignments' of the given expression:
|
|
|
|
// Collect assignments
|
|
parser.dec().collect_assignments() = true;
|
|
|
|
if (!parser.compile(expression_string,expression))
|
|
{
|
|
// error....
|
|
}
|
|
|
|
std::deque<symbol_t> symbol_list;
|
|
|
|
parser.dec().assignment_symbols(symbol_list);
|
|
|
|
for (std::size_t i = 0; i < symbol_list.size(); ++i)
|
|
{
|
|
symbol_t& symbol = symbol_list[i];
|
|
|
|
switch (symbol.second)
|
|
{
|
|
case parser_t::e_st_variable : ... break;
|
|
case parser_t::e_st_vector : ... break;
|
|
case parser_t::e_st_string : ... break;
|
|
}
|
|
}
|
|
|
|
|
|
Note24: The assignments will only consist of variable types and as
|
|
such will not contain symbols denoting functions.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 17 - HIERARCHIES OF SYMBOL TABLES]
|
|
Most situations will only require a single symbol_table instance to be
|
|
associated with a given expression instance.
|
|
|
|
However as an expression can have more than one symbol table instance
|
|
associated with itself, when building more complex systems that
|
|
utilise many expressions where each can in turn utilise one or more
|
|
variables from a large set of potential variables, functions or
|
|
constants, it becomes evident that grouping variables into layers of
|
|
symbol_tables will simplify and streamline the overall process.
|
|
|
|
A recommended hierarchy of symbol tables is the following:
|
|
|
|
(a) Global constant value symbol table
|
|
(b) Global non side-effect functions symbol table
|
|
(c) Global variable symbol table
|
|
(d) Expression specific variable symbol table
|
|
|
|
|
|
(a) Global constant value symbol table
|
|
This symbol table will contain constant variables denoting immutable
|
|
values. These variables can be made available to all expressions, and
|
|
in turn expressions will assume the values themselves will never be
|
|
modified for the duration of the process run-time. Examples of such
|
|
variables are:
|
|
|
|
(1) pi or e
|
|
(2) speed_of_light
|
|
(3) avogadro_number
|
|
(4) num_cpus
|
|
|
|
|
|
(b) Global non side-effect functions symbol table
|
|
This symbol table will contain only user defined functions that will
|
|
not incur any side-effects that are observable to any of the
|
|
expressions that invoke them. These functions shall be thread-safe or
|
|
threading invariant and will not maintain any form of state between
|
|
invocations. Examples of such functions are:
|
|
|
|
(1) calc_volume_of_sphere(r)
|
|
(2) distance(x0,y0,x1,y1)
|
|
|
|
|
|
(c) Global variable symbol table
|
|
This symbol table will contain variables that will be accessible to
|
|
all associated expressions and will not be specific or exclusive to
|
|
any one expression. This variant differs from (a) in that the values
|
|
of the variables can change (or be updated) between evaluations of
|
|
expressions - but through properly scheduled evaluations are
|
|
guaranteed to never change during the evaluation of any dependent
|
|
expressions. Furthermore it is assumed that these variables will be
|
|
used in a read-only context and that no expressions will attempt to
|
|
modify these variables via assignments or other means.
|
|
|
|
(1) price_of_stock_xyz
|
|
(2) outside_temperature or inside_temperature
|
|
(3) fuel_in_tank
|
|
(4) num_customers_in_store
|
|
(5) num_items_on_shelf
|
|
|
|
|
|
(d) Expression specific variable symbol table
|
|
This symbol_table is the most common form, and is used to store
|
|
variables that are specific and exclusive to a particular expression.
|
|
That is to say references to variables in this symbol_table will not
|
|
be part of another expression. Though it may be possible to have
|
|
expressions that contain the variables with the same name, in that
|
|
case those variables will be distinctly different. Which would mean if
|
|
a particular expression were to be compiled twice, each expression
|
|
would have its own unique symbol_table which in turn would have its
|
|
own instances of those variables. Examples of such variables could be:
|
|
|
|
(1) x or y
|
|
(2) customer_name
|
|
|
|
|
|
The following is a diagram depicting a possible variant of the denoted
|
|
symbol table hierarchies. In the diagram there are two unique
|
|
expressions, each of which have a reference to the Global constant,
|
|
functions and variables symbol tables and an exclusive reference to a
|
|
local symbol table.
|
|
|
|
+-------------------------+ +-------------------------+
|
|
| Global Constants | | Global Functions |
|
|
| Symbol Table | | Symbol Table |
|
|
+----o--o-----------------+ +--------------------o----+
|
|
| | |
|
|
| | +-------+
|
|
| +------------------->----------------------------+ |
|
|
| +----------------------------+ | |
|
|
| | Global Variables | | |
|
|
| +------o Symbol Table o-----+ | V
|
|
| | +----------------------------+ | | |
|
|
| | | | |
|
|
| | +----------------+ +----------------+ | | |
|
|
| | | Symbol Table 0 | | Symbol Table 1 | | V |
|
|
| | +--o-------------+ +--o-------------+ | | |
|
|
| | | | | | |
|
|
| | | | | | |
|
|
+--V--V----V---------+ +-V---------------V--+ | |
|
|
| Expression 0 | | Expression 1 |<--+--+
|
|
| '2 * sin(x) - y' | | 'k + abs(x - y)' |
|
|
+--------------------+ +--------------------+
|
|
|
|
|
|
Bringing all of the above together, in the following example the
|
|
hierarchy of symbol tables are instantiated and initialised. An
|
|
expression that makes use of various elements of each symbol table is
|
|
then compiled and later on evaluated:
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
|
|
// Setup global constants symbol table
|
|
symbol_table_t glbl_const_symbol_table;
|
|
glbl_const_symbtab.add_constants(); // pi, epsilon and inf
|
|
glbl_const_symbtab.add_constant("speed_of_light",299e6);
|
|
glbl_const_symbtab.add_constant("avogadro_number",6e23);
|
|
|
|
// Setup global function symbol table
|
|
symbol_table_t glbl_funcs_symbol_table;
|
|
glbl_func_symbtab.add_function('distance',distance);
|
|
glbl_func_symbtab.add_function('calc_spherevol',calc_sphrvol);
|
|
|
|
......
|
|
|
|
// Setup global variable symbol table
|
|
symbol_table_t glbl_variable_symbol_table;
|
|
glbl_variable_symbtab.add_variable('temp_outside',thermo.outside);
|
|
glbl_variable_symbtab.add_variable('temp_inside' ,thermo.inside );
|
|
glbl_variable_symbtab.add_variable('num_cstmrs',store.num_cstmrs);
|
|
|
|
......
|
|
|
|
double x,y,z;
|
|
|
|
// Setup expression specific symbol table
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable('x',x);
|
|
symbol_table.add_variable('y',y);
|
|
symbol_table.add_variable('z',z);
|
|
|
|
expression_t expression;
|
|
|
|
// Register the various symbol tables
|
|
expression
|
|
.register_symbol_table(symbol_table);
|
|
|
|
expression
|
|
.register_symbol_table(glbl_funcs_symbol_table);
|
|
|
|
expression
|
|
.register_symbol_table(glbl_const_symbol_table);
|
|
|
|
expression
|
|
.register_symbol_table(glbl_variable_symbol_table);
|
|
|
|
const std::string expression_str =
|
|
"abs(temp_inside - temp_outside) + 2 * speed_of_light / x";
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_str,expression);
|
|
|
|
......
|
|
|
|
while (keep_evaluating)
|
|
{
|
|
....
|
|
|
|
T result = expression.value();
|
|
|
|
....
|
|
}
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 18 - UNKNOWN UNKNOWNS]
|
|
In this section we will discuss the process of handling expressions
|
|
with a mix of known and unknown variables. Initially a discussion into
|
|
the types of expressions that exist will be provided, then a series of
|
|
possible solutions will be presented for each scenario.
|
|
|
|
When parsing an expression, there may be situations where one is not
|
|
fully aware of what if any variables will be used prior to the
|
|
expression being compiled.
|
|
|
|
This can become problematic, as in the default scenario it is assumed
|
|
the symbol_table that is registered with the expression instance will
|
|
already possess the externally available variables, functions and
|
|
constants needed during the compilation of the expression.
|
|
|
|
In the event there are symbols in the expression that can't be mapped
|
|
to either a reserved word, or located in the associated
|
|
symbol_table(s), an "Undefined symbol" error will be raised and the
|
|
compilation process will fail.
|
|
|
|
The numerous scenarios that can occur when compiling an expression
|
|
with ExprTk generally fall into one of the following three categories:
|
|
|
|
(a) No external variables
|
|
(b) Predetermined set of external variables
|
|
(c) Unknown set of variables
|
|
|
|
|
|
(a) No external variables
|
|
These are expressions that contain no external variables but may
|
|
contain local variables. As local variables cannot be accessed
|
|
externally from the expression, it is assumed that such expressions
|
|
will not have a need for a symbol_table and furthermore expressions
|
|
which don't make use of functions that have side-effects will be
|
|
evaluated completely at compile time resulting in a constant return
|
|
value. The following are examples of such expressions:
|
|
|
|
(1) 1 + 2
|
|
(2) var x := 3; 2 * x - 3
|
|
(3) var x := 3; var y := abs(x - 8); x - y / 7
|
|
|
|
|
|
(b) Predetermined set of external variables
|
|
These are expressions that are comprised of externally available
|
|
variables and functions and will only compile successfully if the
|
|
symbols that correspond to the variables and functions are already
|
|
defined in their associated symbol_table(s). This is by far the most
|
|
common scenario when using ExprTk.
|
|
|
|
As an example, one may have three external variables: x, y and z which
|
|
have been registered with the associated symbol_table, and will then
|
|
need to compile and evaluate expressions comprised of any subset of
|
|
these three variables. The following are a few examples of such
|
|
expressions:
|
|
|
|
(1) 1 + x
|
|
(2) x / y
|
|
(3) 2 * x * y / z
|
|
|
|
|
|
In this scenario one can use the 'dependent_entity_collector'
|
|
component as described in [Section 16] to further determine which of
|
|
the registered variables were actually used in the given expression.
|
|
As an example once the set of utilised variables are known, any
|
|
further 'attention' can be restricted to only those variables when
|
|
evaluating the expression. This can be quite useful when dealing with
|
|
expressions that can draw from a set of hundreds or even thousands of
|
|
variables.
|
|
|
|
|
|
(c) Unknown set of variables
|
|
These are expressions that are comprised of symbols other than the
|
|
standard ExprTk reserved words or what has been registered with their
|
|
associated symbol_table, and will normally fail compilation due to the
|
|
associated symbol_table not having a reference to them. As such this
|
|
scenario can be seen as a combination of scenario B, where one may
|
|
have a symbol_table with registered variables, but would also like to
|
|
handle the situation of variables that aren't present in said
|
|
symbol_table.
|
|
|
|
When dealing with expressions of category (c), one must perform all of
|
|
the following:
|
|
|
|
(1) Determine the variables used in the expression
|
|
(2) Populate a symbol_table(s) with the entities from (1)
|
|
(3) Compile the expression
|
|
(4) Provide a means by which the entities from (1) can be modified
|
|
|
|
|
|
Depending on the nature of processing, steps (1) and (2) can be done
|
|
either independently of each other or combined into one. The following
|
|
example will initially look at solving the problem of unknown
|
|
variables with the latter method using the 'unknown_symbol_resolver'
|
|
component.
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
T x = T(123.456);
|
|
T y = T(789.123);
|
|
|
|
symbol_table_t unknown_var_symbol_table;
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(unknown_var_symbol_table);
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.enable_unknown_symbol_resolver();
|
|
|
|
const std::string expression_str = "x + abs(y / 3k) * z + 2";
|
|
|
|
parser.compile(expression_str,expression);
|
|
|
|
|
|
In the example above, the symbols 'k' and 'z' will be treated as
|
|
unknown symbols. The parser in the example is set to handle unknown
|
|
symbols using the built-in default unknown_symbol_resolver (USR). The
|
|
default USR will automatically resolve any unknown symbols as a
|
|
variable (scalar type). The new variables will be added to the primary
|
|
symbol_table, which in this case is the 'unknown_var_symbol_table'
|
|
instance. Once the compilation has completed successfully, the
|
|
variables that were resolved during compilation can be accessed from
|
|
the primary symbol_table using the 'get_variable_list' and
|
|
'variable_ref' methods and then if needed can be modified accordingly
|
|
after which the expression itself can be evaluated.
|
|
|
|
std::vector<std::string> variable_list;
|
|
|
|
unknown_var_symbol_table.get_variable_list(variable_list);
|
|
|
|
for (const auto& var_name : variable_list)
|
|
{
|
|
T& v = unknown_var_symbol_table.variable_ref(var_name);
|
|
|
|
v = ...;
|
|
}
|
|
|
|
...
|
|
|
|
expression.value();
|
|
|
|
|
|
Note25: As previously mentioned the default USR will automatically
|
|
assume any unknown symbol to be a valid scalar variable, and will then
|
|
proceed to add said symbol as a variable to the primary symbol_table
|
|
of the associated expression during the compilation process. However a
|
|
problem that may arise, is that expressions that are parsed with the
|
|
USR enabled, but contain 'typos' or otherwise syntactic errors may
|
|
inadvertently compile successfully due to the simplistic nature of the
|
|
default USR. The following are some example expressions:
|
|
|
|
(1) 1 + abz(x + 1)
|
|
(2) sine(y / 2) - coz(3x)
|
|
|
|
|
|
The two expressions above contain misspelt symbols (abz, sine, coz)
|
|
which if implied multiplications and default USR are enabled during
|
|
compilation will result in them being assumed to be valid 'variables',
|
|
which obviously is not the intended outcome by the user. A possible
|
|
solution to this problem is for one to implement their own specific
|
|
USR that will perform a user defined business logic in determining if
|
|
an encountered unknown symbol should be treated as a variable or if it
|
|
should raise a compilation error. The following example demonstrates a
|
|
simple user defined USR:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
template <typename T>
|
|
struct my_usr final : public parser_t::unknown_symbol_resolver
|
|
{
|
|
typedef typename parser_t::unknown_symbol_resolver usr_t;
|
|
|
|
bool process(const std::string& unknown_symbol,
|
|
typename usr_t::usr_symbol_type& st,
|
|
T& default_value,
|
|
std::string& error_message) override
|
|
{
|
|
if (0 != unknown_symbol.find("var_"))
|
|
{
|
|
error_message = "Invalid symbol: " + unknown_symbol;
|
|
return false;
|
|
}
|
|
|
|
st = usr_t::e_usr_variable_type;
|
|
default_value = T(123.123);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
...
|
|
|
|
T x = T(123.456);
|
|
T y = T(789.123);
|
|
|
|
symbol_table_t unknown_var_symbol_table;
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(unknown_var_symbol_table);
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
my_usr<T> musr;
|
|
|
|
parser_t parser;
|
|
parser.enable_unknown_symbol_resolver(&musr);
|
|
|
|
std::string expression_str = "var_x + abs(var_y - 3) * var_z";
|
|
|
|
parser.compile(expression_str,expression);
|
|
|
|
|
|
In the example above, a user specified USR is defined, and is
|
|
registered with the parser enabling the USR functionality.
|
|
Subsequently during the compilation process when an unknown symbol is
|
|
encountered, the USR's process method will be invoked. The USR in the
|
|
example will only 'accept' unknown symbols that have a prefix of
|
|
'var_' as being valid variables, all other unknown symbols will result
|
|
in a compilation error being raised.
|
|
|
|
In the example above the callback of the USR that is invoked during
|
|
the unknown symbol resolution process only allows for scalar variables
|
|
to be defined and resolved - as that is the simplest and most common
|
|
form.
|
|
|
|
There is a further extended version of the callback that can be
|
|
overridden that will allow for more control and choice over the type
|
|
of symbol being resolved. The following is an example definition of
|
|
said extended callback:
|
|
|
|
template <typename T>
|
|
struct my_usr final : public parser_t::unknown_symbol_resolver
|
|
{
|
|
typedef typename parser_t::unknown_symbol_resolver usr_t;
|
|
|
|
my_usr()
|
|
: usr_t(usr_t::e_usrmode_extended)
|
|
{}
|
|
|
|
bool process(const std::string& unknown_symbol,
|
|
symbol_table_t& symbol_table,
|
|
std::string& error_message) override
|
|
{
|
|
bool result = false;
|
|
|
|
if (0 == unknown_symbol.find("var_"))
|
|
{
|
|
// Default value of zero
|
|
result = symbol_table.create_variable(unknown_symbol,0);
|
|
|
|
if (!result)
|
|
{
|
|
error_message = "Failed to create variable...";
|
|
}
|
|
}
|
|
else if (0 == unknown_symbol.find("str_"))
|
|
{
|
|
// Default value of empty string
|
|
result = symbol_table.create_stringvar(unknown_symbol,"");
|
|
|
|
if (!result)
|
|
{
|
|
error_message = "Failed to create string variable...";
|
|
}
|
|
}
|
|
else
|
|
error_message = "Indeterminable symbol type.";
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
|
|
In the example above, the USR callback when invoked will pass the
|
|
primary symbol table associated with the expression being parsed. The
|
|
symbol resolution business logic can then determine under what
|
|
conditions a symbol will be resolved including its type (scalar,
|
|
string, vector etc) and default value. When the callback successfully
|
|
returns the symbol parsing and resolution process will again be
|
|
executed by the parser. The idea here is that given the primary symbol
|
|
table will now have the previously detected unknown symbol registered,
|
|
it will be correctly resolved and the general parsing processing can
|
|
then resume as per normal.
|
|
|
|
Note26: In order to have the USR's extended mode callback be invoked
|
|
it is necessary to pass the e_usrmode_extended enum value during the
|
|
constructor of the user defined USR.
|
|
|
|
Note27: The primary symbol table for an expression is the first symbol
|
|
table to be registered with that instance of the expression.
|
|
|
|
Note28: For a successful symbol resolution using the normal USR all of
|
|
the following are required:
|
|
|
|
(1) Only if successful shall the process method return TRUE
|
|
(2) The default_value parameter will have been set
|
|
(3) The error_message parameter will be empty
|
|
(4) usr_symbol_type input parameter field will be set to either:
|
|
(*) e_usr_variable_type
|
|
(*) e_usr_constant_type
|
|
|
|
Note29: For a successful symbol resolution using the extended USR all
|
|
of the following are required:
|
|
|
|
(1) Only if successful shall the process method return TRUE
|
|
(2) symbol_table parameter will have had the newly resolved
|
|
variable or string added to it
|
|
(3) error_message parameter will be empty
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 19 - ENABLING & DISABLING FEATURES]
|
|
The parser can be configured via its settings instance to either allow
|
|
or disallow certain features that are available within the ExprTk
|
|
grammar. The features fall into one of the following six categories:
|
|
|
|
(1) Base Functions
|
|
(2) Control Flow Structures
|
|
(3) Logical Operators
|
|
(4) Arithmetic Operators
|
|
(5) Inequality Operators
|
|
(6) Assignment Operators
|
|
|
|
|
|
(1) Base Functions
|
|
The list of available base functions is as follows:
|
|
|
|
abs, acos, acosh, asin, asinh, atan, atanh, atan2, avg, ceil,
|
|
clamp, cos, cosh, cot, csc, equal, erf, erfc, exp, expm1,
|
|
floor, frac, hypot, iclamp, like, log, log10, log2, logn,
|
|
log1p, mand, max, min, mod, mor, mul, ncdf, pow, root, round,
|
|
roundn, sec, sgn, sin, sinc, sinh, sqrt, sum, swap, tan, tanh,
|
|
trunc, not_equal, inrange, deg2grad, deg2rad, rad2deg, grad2deg
|
|
|
|
|
|
The above mentioned base functions can be either enabled or disabled
|
|
'all' at once, as is demonstrated below:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings().disable_all_base_functions();
|
|
|
|
parser
|
|
.compile("2 * abs(2 - 3)",expression); // compilation failure
|
|
|
|
parser.settings().enable_all_base_functions();
|
|
|
|
parser
|
|
.compile("2 * abs(2 - 3)",expression); // compilation success
|
|
|
|
|
|
One can also enable or disable specific base functions. The following
|
|
example demonstrates the disabling of the trigonometric functions
|
|
'sin' and 'cos':
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings()
|
|
.disable_base_function(settings_t::e_bf_sin)
|
|
.disable_base_function(settings_t::e_bf_cos);
|
|
|
|
parser
|
|
.compile("(sin(x) / cos(x)) == tan(x)",expression); // failure
|
|
|
|
parser.settings()
|
|
.enable_base_function(settings_t::e_bf_sin)
|
|
.enable_base_function(settings_t::e_bf_cos);
|
|
|
|
parser
|
|
.compile("(sin(x) / cos(x)) == tan(x)",expression); // success
|
|
|
|
|
|
(2) Control Flow Structures
|
|
The list of available control flow structures is as follows:
|
|
|
|
(a) If or If-Else
|
|
(b) Switch statement
|
|
(c) For Loop
|
|
(d) While Loop
|
|
(e) Repeat Loop
|
|
|
|
|
|
The above mentioned control flow structures can be either enabled
|
|
or disabled 'all' at once, as is demonstrated below:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
const std::string program =
|
|
" var x := 0; "
|
|
" for (var i := 0; i < 10; i += 1) "
|
|
" { "
|
|
" x += i; "
|
|
" } ";
|
|
|
|
parser.settings().disable_all_control_structures();
|
|
|
|
parser
|
|
.compile(program,expression); // compilation failure
|
|
|
|
parser.settings().enable_all_control_structures();
|
|
|
|
parser
|
|
.compile(program,expression); // compilation success
|
|
|
|
|
|
One can also enable or disable specific control flow structures. The
|
|
following example demonstrates the disabling of the for-loop control
|
|
flow structure:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
const std::string program =
|
|
" var x := 0; "
|
|
" for (var i := 0; i < 10; i += 1) "
|
|
" { "
|
|
" x += i; "
|
|
" } ";
|
|
|
|
parser.settings()
|
|
.disable_control_structure(settings_t::e_ctrl_for_loop);
|
|
|
|
parser
|
|
.compile(program,expression); // failure
|
|
|
|
parser.settings()
|
|
.enable_control_structure(settings_t::e_ctrl_for_loop);
|
|
|
|
parser
|
|
.compile(program,expression); // success
|
|
|
|
|
|
(3) Logical Operators
|
|
The list of available logical operators is as follows:
|
|
|
|
and, nand, nor, not, or, xnor, xor, &, |
|
|
|
|
|
|
The above mentioned logical operators can be either enabled or
|
|
disabled 'all' at once, as is demonstrated below:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings().disable_all_logic_ops();
|
|
|
|
parser
|
|
.compile("1 or not(0 and 1)",expression); // compilation failure
|
|
|
|
parser.settings().enable_all_logic_ops();
|
|
|
|
parser
|
|
.compile("1 or not(0 and 1)",expression); // compilation success
|
|
|
|
|
|
One can also enable or disable specific logical operators. The
|
|
following example demonstrates the disabling of the 'and' logical
|
|
operator:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings()
|
|
.disable_logic_operation(settings_t::e_logic_and);
|
|
|
|
parser
|
|
.compile("1 or not(0 and 1)",expression); // failure
|
|
|
|
parser.settings()
|
|
.enable_logic_operation(settings_t::e_logic_and);
|
|
|
|
parser
|
|
.compile("1 or not(0 and 1)",expression); // success
|
|
|
|
|
|
(4) Arithmetic Operators
|
|
The list of available arithmetic operators is as follows:
|
|
|
|
+, -, *, /, %, ^
|
|
|
|
|
|
The above mentioned arithmetic operators can be either enabled or
|
|
disabled 'all' at once, as is demonstrated below:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings().disable_all_arithmetic_ops();
|
|
|
|
parser
|
|
.compile("1 + 2 / 3",expression); // compilation failure
|
|
|
|
parser.settings().enable_all_arithmetic_ops();
|
|
|
|
parser
|
|
.compile("1 + 2 / 3",expression); // compilation success
|
|
|
|
|
|
One can also enable or disable specific arithmetic operators. The
|
|
following example demonstrates the disabling of the addition '+'
|
|
arithmetic operator:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings()
|
|
.disable_arithmetic_operation(settings_t::e_arith_add);
|
|
|
|
parser
|
|
.compile("1 + 2 / 3",expression); // failure
|
|
|
|
parser.settings()
|
|
.enable_arithmetic_operation(settings_t::e_arith_add);
|
|
|
|
parser
|
|
.compile("1 + 2 / 3",expression); // success
|
|
|
|
|
|
(5) Inequality Operators
|
|
The list of available inequality operators is as follows:
|
|
|
|
<, <=, >, >=, ==, =, != <>
|
|
|
|
|
|
The above mentioned inequality operators can be either enabled or
|
|
disabled 'all' at once, as is demonstrated below:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings().disable_all_inequality_ops();
|
|
|
|
parser
|
|
.compile("1 < 3",expression); // compilation failure
|
|
|
|
parser.settings().enable_all_inequality_ops();
|
|
|
|
parser
|
|
.compile("1 < 3",expression); // compilation success
|
|
|
|
|
|
One can also enable or disable specific inequality operators. The
|
|
following example demonstrates the disabling of the less-than '<'
|
|
inequality operator:
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
|
|
parser.settings()
|
|
.disable_inequality_operation(settings_t::e_ineq_lt);
|
|
|
|
parser
|
|
.compile("1 < 3",expression); // failure
|
|
|
|
parser.settings()
|
|
.enable_inequality_operation(settings_t::e_ineq_lt);
|
|
|
|
parser
|
|
.compile("1 < 3",expression); // success
|
|
|
|
|
|
(6) Assignment Operators
|
|
The list of available assignment operators is as follows:
|
|
|
|
:=, +=, -=, *=, /=, %=
|
|
|
|
|
|
The above mentioned assignment operators can be either enabled or
|
|
disabled 'all' at once, as is demonstrated below:
|
|
|
|
T x = T(0);
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
symbol_table_t symbol_table;
|
|
|
|
symbol_table.add_variable("x",x);
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser.settings().disable_all_assignment_ops();
|
|
|
|
parser
|
|
.compile("x := 3",expression); // compilation failure
|
|
|
|
parser.settings().enable_all_assignment_ops();
|
|
|
|
parser
|
|
.compile("x := 3",expression); // compilation success
|
|
|
|
|
|
One can also enable or disable specific assignment operators. The
|
|
following example demonstrates the disabling of the '+=' addition
|
|
assignment operator:
|
|
|
|
T x = T(0);
|
|
|
|
parser_t parser;
|
|
expression_t expression;
|
|
symbol_table_t symbol_table;
|
|
|
|
symbol_table.add_variable("x",x);
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser.settings()
|
|
.disable_assignment_operation(settings_t::e_assign_addass);
|
|
|
|
parser
|
|
.compile("x += 3",expression); // failure
|
|
|
|
parser.settings()
|
|
.enable_assignment_operation(settings_t::e_assign_addass);
|
|
|
|
parser
|
|
.compile("x += 3",expression); // success
|
|
|
|
|
|
Note30: In the event of a base function being disabled, one can
|
|
redefine the base function using the standard custom function
|
|
definition process. In the following example the 'sin' function is
|
|
disabled then redefined as a function taking degree input.
|
|
|
|
template <typename T>
|
|
struct sine_deg final : public exprtk::ifunction<T>
|
|
{
|
|
sine_deg() : exprtk::ifunction<T>(1) {}
|
|
|
|
inline T operator()(const T& v) override
|
|
{
|
|
const T pi = exprtk::details::numeric::constant::pi;
|
|
return std::sin((v * T(pi)) / T(180));
|
|
}
|
|
};
|
|
|
|
...
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
typedef typename parser_t::settings_store settings_t;
|
|
|
|
sine_deg<T> sine;
|
|
|
|
symbol_table.add_reserved_function("sin",sine);
|
|
|
|
expression_t expression;
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
|
|
parser.settings()
|
|
.disable_base_function(settings_t::e_bf_sin);
|
|
|
|
parser.compile("1 + sin(30)",expression);
|
|
|
|
|
|
In the example above, the custom 'sin' function is registered with the
|
|
symbol_table using the method 'add_reserved_function'. This is done so
|
|
as to bypass the checks for reserved words that are carried out on the
|
|
provided symbol names when calling the standard 'add_function' method.
|
|
Normally if a user specified symbol name conflicts with any of the
|
|
ExprTk reserved words, the add_function call will fail.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 20 - EXPRESSION RETURN VALUES]
|
|
ExprTk expressions can return immediately from any point by utilising
|
|
the return call. Furthermore the return call can be used to transfer
|
|
out multiple return values from within the expression.
|
|
|
|
If an expression evaluation exits using a return point, the result of
|
|
the call to the 'value' method will be NaN, and it is expected that
|
|
the return values will be available from the results_context.
|
|
|
|
In the following example there are three return points in the
|
|
expression. If neither of the return points are hit, then the
|
|
expression will return normally.
|
|
|
|
const std::string expression_string =
|
|
" if (x < y) "
|
|
" return [x + 1,'return-call 1']; "
|
|
" else if (x > y) "
|
|
" return [y / 2, y + 1, 'return-call 2']; "
|
|
" else if (equal(x,y)) "
|
|
" x + y; "
|
|
" return [x, y, x + y, x - y, 'return-call 3'] ";
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
double x = 0;
|
|
double y = 0;
|
|
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser.compile(expression_string,expression);
|
|
|
|
T result = expression.value();
|
|
|
|
if (expression.return_invoked())
|
|
{
|
|
typedef exprtk::results_context<T> results_context_t;
|
|
typedef typename results_context_t::type_store_t type_t;
|
|
typedef typename type_t::scalar_view scalar_t;
|
|
typedef typename type_t::vector_view vector_t;
|
|
typedef typename type_t::string_view string_t;
|
|
|
|
const results_context_t& results = expression.results();
|
|
|
|
for (std::size_t i = 0; i < results.count(); ++i)
|
|
{
|
|
type_t t = results[i];
|
|
|
|
switch (t.type)
|
|
{
|
|
case type_t::e_scalar : ...
|
|
break;
|
|
|
|
case type_t::e_vector : ...
|
|
break;
|
|
|
|
case type_t::e_string : ...
|
|
break;
|
|
|
|
default : continue;
|
|
}
|
|
}
|
|
|
|
|
|
In the above example, there are three possible "return" points and one
|
|
regular result. Only one of the four paths can ever be realised. Hence
|
|
it is necessary to capture the result of the expression value method
|
|
call. In the event, the call to return_invoked is not true then the
|
|
non-return code path was executed and the result of the evaluation
|
|
will be the result of the expression's value method.
|
|
|
|
Note31: Processing of the return results is similar to that of the
|
|
generic function call parameters.
|
|
|
|
The results_context provides getter methods for each of the possible
|
|
return types (scalar, vector and string) and can be used as follows:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
const std::string expression_str =
|
|
" if (x > y) "
|
|
" return [1]; "
|
|
" else "
|
|
" return [ x, x + y, 2 * v, s + 'world' ]; ";
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
symbol_table.add_variable ("x", x);
|
|
symbol_table.add_variable ("y", y);
|
|
symbol_table.add_variable ("z", z);
|
|
symbol_table.add_vector ("v", v);
|
|
symbol_table.add_stringvar("s", s);
|
|
|
|
parser.compile(expression_str, expression);
|
|
|
|
expression.value();
|
|
|
|
typedef exprtk::results_context<T> results_context_t;
|
|
const results_context_t& results = expression.results();
|
|
|
|
if (results.count() == 4)
|
|
{
|
|
T result_x0;
|
|
T result_x1;
|
|
std::string result_s;
|
|
std::vector<T> result_v;
|
|
|
|
results.get_scalar(0, result_x0);
|
|
results.get_scalar(1, result_x1);
|
|
results.get_string(3, result_s );
|
|
results.get_vector(2, result_v );
|
|
}
|
|
|
|
|
|
It is however recommended that if there is to be only a single flow of
|
|
execution through the expression, that the simpler approach of
|
|
registering external variables of appropriate type be used.
|
|
|
|
This method simply requires the variables that are to hold the various
|
|
results that are to be computed within the expression to be registered
|
|
with an associated symbol_table instance. Then within the expression
|
|
itself to have the result variables be assigned the appropriate
|
|
values.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
|
|
const std::string expression_string =
|
|
" var x := 123.456; "
|
|
" var s := 'ijk'; "
|
|
" result0 := x + 78.90; "
|
|
" result1 := s + '123' ";
|
|
|
|
double result0;
|
|
std::string result1;
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable ("result0",result0);
|
|
symbol_table.add_stringvar("result1",result1);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string,expression);
|
|
|
|
expression.value();
|
|
|
|
printf("Result0: %15.5f\n", result0 );
|
|
printf("Result1: %s\n" , result1.c_str());
|
|
|
|
|
|
In the example above, the expression will compute two results. As such
|
|
two result variables are defined to hold the values named result0 and
|
|
result1 respectively. The first is of scalar type (double), the second
|
|
is of string type. Once the expression has been evaluated, the two
|
|
variables will have been updated with the new result values, and can
|
|
then be further utilised from within the calling host program.
|
|
|
|
There will be times when an expression may have multiple exit paths,
|
|
where not all the paths will be return-statement based. The following
|
|
example builds upon the previous examples, but this time at least one
|
|
path is not return based.
|
|
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
|
|
double x = 100.0;
|
|
double y = 200.0;
|
|
|
|
symbol_table_t symbol_table;
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
symbol_table.add_variable ("x", x);
|
|
symbol_table.add_variable ("y", y);
|
|
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
const std::string expression_string =
|
|
" for (var i := 0; i < 10; i += 1) "
|
|
" { "
|
|
" if (i > x) "
|
|
" { "
|
|
" return [x + y, 'return-call 1']; "
|
|
" } "
|
|
" else if (i > y) "
|
|
" { "
|
|
" return [x - y, 'return-call 2']; "
|
|
" } "
|
|
" }; "
|
|
" "
|
|
" x / y ";
|
|
|
|
parser.compile(expression_str, expression);
|
|
|
|
const auto result = expression.value();
|
|
|
|
if (expression.return_invoked())
|
|
{
|
|
const auto results = expression.results();
|
|
|
|
for (std::size_t i = 0; i < results.count(); ++i)
|
|
{
|
|
const auto& rtrn_result = results[i];
|
|
.
|
|
.
|
|
.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("result: %f\n",result);
|
|
}
|
|
|
|
|
|
After having called the value method on the expression, calling the
|
|
return_invoked method will determine if the expression completed due
|
|
to a return statement being invoked or if it finished normally.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 21 - COMPILATION ERRORS]
|
|
When attempting to compile a malformed or otherwise erroneous ExprTk
|
|
expression, the compilation process will result in an error, as is
|
|
indicated by the 'compile' method returning a false value. A
|
|
diagnostic indicating the first error encountered and its cause can be
|
|
obtained by invoking the 'error' method, as is demonstrated in the
|
|
following example:
|
|
|
|
if (!parser.compile(expression_string,expression))
|
|
{
|
|
printf("Error: %s\n", parser.error().c_str());
|
|
return false;
|
|
}
|
|
|
|
|
|
Any error(s) resulting from a failed compilation will be stored in the
|
|
parser instance until the next time a compilation is performed. Before
|
|
then errors can be enumerated in the order they occurred by invoking
|
|
the 'get_error' method which itself will return a 'parser_error' type.
|
|
A parser_error object will contain an error diagnostic, an error mode
|
|
(or class), and the character position of the error in the expression
|
|
string. The following example demonstrates the enumeration of error(s)
|
|
in the event of a failed compilation.
|
|
|
|
typedef exprtk::parser<T> parser_t;
|
|
typedef exprtk::parser_error::type error_t;
|
|
|
|
if (!parser.compile(expression_string,expression))
|
|
{
|
|
for (std::size_t i = 0; i < parser.error_count(); ++i)
|
|
{
|
|
typedef exprtk::parser_error::type error_t;
|
|
|
|
error_t error = parser.get_error(i);
|
|
|
|
printf("Error[%02d] Position: %02d Type: [%14s] Msg: %s\n",
|
|
i,
|
|
error.token.position,
|
|
exprtk::parser_error::to_str(error.mode).c_str(),
|
|
error.diagnostic.c_str());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
Assuming the following expression '2 + (3 / log(1 + x))' which uses a
|
|
variable named 'x' that has not been registered with the appropriate
|
|
symbol_table instance and is not a locally defined variable, once
|
|
compiled the above denoted post compilation error handling code shall
|
|
produce the following output:
|
|
|
|
Error[00] Pos:17 Type:[Syntax] Msg: ERR184 - Undefined symbol: 'x'
|
|
|
|
|
|
For expressions comprised of multiple lines, the error position
|
|
provided in the parser_error object can be converted into a pair of
|
|
line and column numbers by invoking the 'update_error' function as is
|
|
demonstrated by the following example:
|
|
|
|
if (!parser.compile(program_str,expression))
|
|
{
|
|
for (std::size_t i = 0; i < parser.error_count(); ++i)
|
|
{
|
|
typedef exprtk::parser_error::type error_t;
|
|
|
|
error_t error = parser.get_error(i);
|
|
|
|
exprtk::parser_error::update_error(error,program_str);
|
|
|
|
printf("Error[%0lu] at line: %lu column: %lu\n",
|
|
i,
|
|
error.line_no,
|
|
error.column_no);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
Note32: There are five distinct error modes in ExprTk which denote the
|
|
class of an error. These classes are as follows:
|
|
|
|
(a) Syntax
|
|
(b) Token
|
|
(c) Numeric
|
|
(d) Symbol Table
|
|
(e) Lexer
|
|
|
|
|
|
(a) Syntax Errors
|
|
These are errors related to invalid syntax found within the denoted
|
|
expression. Examples are invalid sequences of operators and variables,
|
|
incorrect number of parameters to functions, invalid conditional or
|
|
loop structures and invalid use of keywords.
|
|
|
|
eg: 'for := sin(x,y,z) + 2 * equal > until[2 - x,3]'
|
|
|
|
|
|
(b) Token Errors
|
|
Errors in this class relate to token level errors detected by one or
|
|
more of the following checkers:
|
|
|
|
(1) Bracket Checker
|
|
(2) Numeric Checker
|
|
(3) Sequence Checker
|
|
|
|
|
|
(c) Numeric Errors
|
|
This class of error is related to conversion of numeric values from
|
|
their string form to the underlying numerical type (float, double
|
|
etc).
|
|
|
|
(d) Symbol Table Errors
|
|
This is the class of errors related to failures when interacting with
|
|
the registered symbol_table instance. Errors such as not being able to
|
|
find, within the symbol_table, symbols representing variables or
|
|
functions, to being unable to create new variables in the symbol_table
|
|
via the 'unknown symbol resolver' mechanism.
|
|
|
|
Note33: The function compositor also supports error message handling
|
|
similar to how it is done via the parser. The following demonstrates
|
|
how after a failed function composition the associated errors can be
|
|
enumerated.
|
|
|
|
typedef exprtk::function_compositor<T> compositor_t;
|
|
typedef typename compositor_t::function function_t;
|
|
|
|
compositor_t compositor;
|
|
|
|
const bool compositor_result =
|
|
compositor.add(
|
|
function_t("foobar")
|
|
.vars("x","y")
|
|
.expression
|
|
( " x + y / z " ));
|
|
|
|
if (!compositor_result)
|
|
{
|
|
printf("Error: %s\n", compositor.error().c_str());
|
|
|
|
for (std::size_t i = 1; i < compositor.error_count(); ++i)
|
|
{
|
|
typedef exprtk::parser_error::type error_t;
|
|
|
|
error_t error = compositor.get_error(i);
|
|
|
|
printf("Err No.: %02d Pos: %02d Type: [%14s] Msg: %s\n",
|
|
static_cast<unsigned int>(i),
|
|
static_cast<unsigned int>(error.token.position),
|
|
exprtk::parser_error::to_str(error.mode).c_str(),
|
|
error.diagnostic.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 22 - RUNTIME LIBRARY PACKAGES]
|
|
ExprTk includes a range of extensions, that provide functionalities
|
|
beyond simple numerical calculations. Currently the available packages
|
|
are:
|
|
|
|
+---+--------------------+-----------------------------------+
|
|
| # | Package Name | Namespace/Type |
|
|
+---+--------------------+-----------------------------------+
|
|
| 1 | Basic I/O | exprtk::rtl::io::package<T> |
|
|
| 2 | File I/O | exprtk::rtl::io::file::package<T> |
|
|
| 3 | Vector Operations | exprtk::rtl::vecops::package<T> |
|
|
+---+--------------------+-----------------------------------+
|
|
|
|
|
|
In order to make the features of a specific package available within
|
|
an expression, an instance of the package must be added to the
|
|
expression's associated symbol table. In the following example, the
|
|
file I/O package is made available for the given expression:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
exprtk::rtl::io::file::package<T> fileio_package;
|
|
|
|
const std::string expression_string =
|
|
" var file_name := 'file.txt'; "
|
|
" var stream := null; "
|
|
" "
|
|
" stream := open(file_name,'w'); "
|
|
" "
|
|
" write(stream,'Hello world....\n'); "
|
|
" "
|
|
" close(stream); "
|
|
" ";
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_package(fileio_package);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string,expression);
|
|
|
|
expression.value();
|
|
|
|
|
|
(1) Basic I/O functions:
|
|
|
|
(a) print
|
|
(b) println
|
|
|
|
(2) File I/O functions:
|
|
|
|
(a) open (b) close
|
|
(c) write (d) read
|
|
(e) getline (f) eof
|
|
|
|
(3) Vector Operations functions:
|
|
|
|
(a) all_true (b) all_false
|
|
(c) any_true (d) any_false
|
|
(e) assign (f) count
|
|
(g) copy (h) reverse
|
|
(i) rotate-left (j) rotate-right
|
|
(k) shift-left (l) shift-right
|
|
(m) sort (n) nth_element
|
|
(o) iota (p) sumk
|
|
(q) axpy (r) axpby
|
|
(s) axpyz (t) axpbyz
|
|
(u) axpbz (v) dot
|
|
(w) dotk (x) diff
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 23 - HELPERS & UTILS]
|
|
The ExprTk library provides a series of usage simplifications via
|
|
helper routines that combine various processes into a single 'function
|
|
call' making certain actions easier to carry out though not
|
|
necessarily in the most efficient way possible. A list of the routines
|
|
are as follows:
|
|
|
|
(a) collect_variables
|
|
(b) collect_functions
|
|
(c) compute
|
|
(d) integrate
|
|
(e) derivative
|
|
(f) second_derivative
|
|
(g) third_derivative
|
|
|
|
|
|
(a) collect_variables
|
|
This function will collect all the variable symbols in a given string
|
|
representation of an expression and return them in an STL compatible
|
|
sequence data structure (eg: std::vector, dequeue etc) specialised
|
|
upon a std::string type. If an error occurs during the parsing of the
|
|
expression then the return value of the function will be false,
|
|
otherwise it will be true. An example use of the given routine is as
|
|
follows:
|
|
|
|
const std::string expression = "x + abs(y / z)";
|
|
|
|
std::vector<std::string> variable_list;
|
|
|
|
if (exprtk::collect_variables(expression, variable_list))
|
|
{
|
|
for (const auto& var : variable_list)
|
|
{
|
|
...
|
|
}
|
|
}
|
|
else
|
|
printf("An error occurred.");
|
|
|
|
|
|
(b) collect_functions
|
|
This function will collect all the function symbols in a given string
|
|
representation of an expression and return them in an STL compatible
|
|
sequence data structure (eg: std::vector, dequeue etc) specialised
|
|
upon a std::string type. If an error occurs during the parsing of the
|
|
expression then the return value of the function will be false,
|
|
otherwise it will be true. An example use of the given routine is as
|
|
follows:
|
|
|
|
const std::string expression = "x + abs(y / cos(1 + z))";
|
|
|
|
std::deque<std::string> function_list;
|
|
|
|
if (exprtk::collect_functions(expression, function_list))
|
|
{
|
|
for (const auto& func : function_list)
|
|
{
|
|
...
|
|
}
|
|
}
|
|
else
|
|
printf("An error occurred.");
|
|
|
|
|
|
Note34: When either the 'collect_variables' or 'collect_functions'
|
|
free functions return true - that does not necessarily indicate the
|
|
expression itself is valid. It is still possible that when compiled
|
|
the expression may have certain 'type' related errors - though it is
|
|
highly likely that no semantic errors will occur if either return
|
|
true.
|
|
|
|
Note35: The default interface provided for both the collect_variables
|
|
and collect_functions free_functions, assumes that expressions will
|
|
only be utilising the ExprTk reserved functions (eg: abs, cos, min
|
|
etc). When user defined functions are to be used in an expression, a
|
|
symbol_table instance containing said functions can be passed to
|
|
either routine, and will be incorporated during the compilation and
|
|
Dependent Entity Collection processes. In the following example, a
|
|
user defined free function named 'foo' is registered with a
|
|
symbol_table. Finally the symbol_table instance and associated
|
|
expression string are passed to the exprtk::collect_functions routine.
|
|
|
|
template <typename T>
|
|
T foo(T v)
|
|
{
|
|
return std::abs(v + T(2)) / T(3);
|
|
}
|
|
|
|
......
|
|
|
|
exprtk::symbol_table<T> sym_tab;
|
|
|
|
symbol_table.add_function("foo",foo);
|
|
|
|
const std::string expression = "x + foo(y / cos(1 + z))";
|
|
|
|
std::deque<std::string> function_list;
|
|
|
|
if (exprtk::collect_functions(expression, sym_tab, function_list))
|
|
{
|
|
for (const auto& func : function_list)
|
|
{
|
|
...
|
|
}
|
|
}
|
|
else
|
|
printf("An error occurred.");
|
|
|
|
|
|
(c) compute
|
|
This free function will compute the value of an expression from its
|
|
string form. If an invalid expression is passed, the result of the
|
|
function will be false indicating an error, otherwise the return value
|
|
will be true indicating success. The compute function has three
|
|
overloads, the definitions of which are:
|
|
|
|
(1) No variables
|
|
(2) One variable called x
|
|
(3) Two variables called x and y
|
|
(3) Three variables called x, y and z
|
|
|
|
|
|
Example uses of each of the three overloads for the compute routine
|
|
are as follows:
|
|
|
|
T result = T(0);
|
|
|
|
// No variables overload
|
|
const std::string no_vars = "abs(1 - (3 / pi)) * 5";
|
|
|
|
if (!exprtk::compute(no_vars,result))
|
|
printf("Failed to compute: %s",no_vars.c_str());
|
|
else
|
|
printf("Result: %15.5f\n",result);
|
|
|
|
// One variable 'x' overload
|
|
T x = T(123.456);
|
|
|
|
const std::string one_var = "abs(x - (3 / pi)) * 5";
|
|
|
|
if (!exprtk::compute(one_var, x, result))
|
|
printf("Failed to compute: %s",one_var.c_str());
|
|
else
|
|
printf("Result: %15.5f\n",result);
|
|
|
|
// Two variables 'x' and 'y' overload
|
|
T y = T(789.012);
|
|
|
|
const std::string two_var = "abs(x - (y / pi)) * 5";
|
|
|
|
if (!exprtk::compute(two_var, x, y, result))
|
|
printf("Failed to compute: %s",two_var.c_str());
|
|
else
|
|
printf("Result: %15.5f\n",result);
|
|
|
|
// Three variables 'x', 'y' and 'z' overload
|
|
T z = T(345.678);
|
|
|
|
const std::string three_var = "abs(x - (y / pi)) * z";
|
|
|
|
if (!exprtk::compute(three_var, x, y, z, result))
|
|
printf("Failed to compute: %s",three_var.c_str());
|
|
else
|
|
printf("Result: %15.5f\n",result);
|
|
|
|
|
|
(d) integrate
|
|
This free function will attempt to perform a numerical integration of
|
|
a single variable compiled expression over a specified range and step
|
|
size. The numerical integration is based on the three point form of
|
|
Simpson's rule. The integrate function has two overloads, where the
|
|
variable of integration can either be passed as a reference or as a
|
|
name in string form. Example usage of the function is as follows:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
const std::string expression_string = "sqrt(1 - (x^2))";
|
|
|
|
T x = T(0);
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string,expression);
|
|
|
|
....
|
|
|
|
// Integrate in domain [-1,1] using a reference to x variable
|
|
T area1 = exprtk::integrate(expression, x, T(-1), T(1));
|
|
|
|
// Integrate in domain [-1,1] using name of x variable
|
|
T area2 = exprtk::integrate(expression, "x", T(-1), T(1));
|
|
|
|
|
|
(e) derivative
|
|
This free function will attempt to perform a numerical differentiation
|
|
of a single variable compiled expression at a given point for a given
|
|
epsilon, using a variant of Newton's difference quotient called the
|
|
five-point stencil method. The derivative function has two overloads,
|
|
where the variable of differentiation can either be passed as a
|
|
reference or as a name in string form. Example usage of the derivative
|
|
function is as follows:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
const std::string expression_string = "sqrt(1 - (x^2))";
|
|
|
|
T x = T(0);
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string,expression);
|
|
|
|
....
|
|
|
|
// Differentiate expression at value of x = 12.3 using a reference
|
|
// to the x variable
|
|
x = T(12.3);
|
|
T derivative1 = exprtk::derivative(expression, x);
|
|
|
|
// Differentiate expression where value x = 45.6 using name
|
|
// of the x variable
|
|
x = T(45.6);
|
|
T derivative2 = exprtk::derivative(expression, "x");
|
|
|
|
|
|
(f) second_derivative
|
|
This free function will attempt to perform a numerical second
|
|
derivative of a single variable compiled expression at a given point
|
|
for a given epsilon, using a variant of Newton's difference quotient
|
|
method. The second_derivative function has two overloads, where the
|
|
variable of differentiation can either be passed as a reference or as
|
|
a name in string form. Example usage of the second_derivative function
|
|
is as follows:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
const std::string expression_string = "sqrt(1 - (x^2))";
|
|
|
|
T x = T(0);
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string,expression);
|
|
|
|
....
|
|
|
|
// Second derivative of expression where value of x = 12.3 using a
|
|
// reference to x variable
|
|
x = T(12.3);
|
|
T derivative1 = exprtk::second_derivative(expression,x);
|
|
|
|
// Second derivative of expression where value of x = 45.6 using
|
|
// name of x variable
|
|
x = T(45.6);
|
|
T derivative2 = exprtk::second_derivative(expression, "x");
|
|
|
|
|
|
(g) third_derivative
|
|
This free function will attempt to perform a numerical third
|
|
derivative of a single variable compiled expression at a given point
|
|
for a given epsilon, using a variant of Newton's difference quotient
|
|
method. The third_derivative function has two overloads, where the
|
|
variable of differentiation can either be passed as a reference or as
|
|
a name in string form. Example usage of the third_derivative function
|
|
is as follows:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
const std::string expression_string = "sqrt(1 - (x^2))";
|
|
|
|
T x = T(0);
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_variable("x",x);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
parser.compile(expression_string,expression);
|
|
|
|
....
|
|
|
|
// Third derivative of expression where value of x = 12.3 using a
|
|
// reference to the x variable
|
|
x = T(12.3);
|
|
T derivative1 = exprtk::third_derivative(expression, x);
|
|
|
|
// Third derivative of expression where value of x = 45.6 using
|
|
// name of the x variable
|
|
x = T(45.6);
|
|
T derivative2 = exprtk::third_derivative(expression, "x");
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 24 - RUNTIME CHECKS]
|
|
The ExprTk library provides the ability to perform runtime checks
|
|
during expression evaluation so as to ensure memory access violations
|
|
errors are caught and handled without causing further issues. The
|
|
checks typically cover:
|
|
|
|
1. Vector access and handling
|
|
2. String access and handling
|
|
3. Loop iteration checks
|
|
4. Compilation checkpointing
|
|
5. Assert statements
|
|
|
|
|
|
(1) Vector Access Runtime Checks
|
|
Expressions that contain vectors where elements of the vectors may be
|
|
accessed using indexes that can only be determined at runtime may
|
|
result in memory access violations when the index is out of the
|
|
vector's bound. Some examples of problematic expressions are as
|
|
follows:
|
|
|
|
1. vec[i]
|
|
2. vec[i + j]
|
|
3. vec[i + 10]
|
|
4. vec[i + vec[]] := x + y
|
|
5. vec[i + j] <=> vec[i]
|
|
6. vec[i + j] := (vec1 + vec2)[i + j]
|
|
|
|
|
|
In the above expressions, it is assumed that the values used in the
|
|
index operator may either exceed the vector bounds or precede the
|
|
vector's start, In short, the indexes may not necessarily be within
|
|
the range [0,vec[]).
|
|
|
|
ExprTk provides the ability to inject a runtime check at the point of
|
|
index evaluation and handle situations where the index violates the
|
|
vector's bounds. This capability is done by registering a user-
|
|
implemented Vector Access Runtime Check (VARTC) to the parser before
|
|
expression compilation. Initially a VARTC can be defined as follows:
|
|
|
|
struct my_vector_access_rtc final :
|
|
public exprtk::vector_access_runtime_check
|
|
{
|
|
bool handle_runtime_violation(violation_context& context)
|
|
override
|
|
{
|
|
// Handling of the violation
|
|
return ...;
|
|
}
|
|
};
|
|
|
|
|
|
Then an instance of the VARTC can be registered with a parser instance
|
|
as follows:
|
|
|
|
my_vector_access_rtc vartc;
|
|
|
|
exprtk::symbol_table<T> symbol_table;
|
|
|
|
T i;
|
|
T x;
|
|
T y;
|
|
std::vector<T> vec = { 0, 1, 2, 3, 4 };
|
|
|
|
symbol_table.add_variable("i" , i );
|
|
symbol_table.add_variable("x" , x );
|
|
symbol_table.add_variable("y" , y );
|
|
symbol_table.add_vector ("vec", vec);
|
|
|
|
exprtk::expression<T> expression;
|
|
exprtk::parser<T> parser;
|
|
|
|
parser.register_vector_access_runtime_check(vartc);
|
|
|
|
std::string expression = "vec[i + vec[]] := x + y";
|
|
|
|
parser.compile(expression_str, expression);
|
|
|
|
try
|
|
{
|
|
expression.value();
|
|
}
|
|
catch (std::runtime_error& rte)
|
|
{
|
|
printf("Exception: %s\n", rte.what());
|
|
}
|
|
|
|
|
|
Note36: The lifetime of any parser or expression instance must not
|
|
exceed that of any VARTC instance that has been registered with it.
|
|
|
|
When a vector access violation occurs, the registered VARTC instance's
|
|
handle_runtime_violation method will be invoked, coupled with it a
|
|
violation_context shall be provided that will contain the following
|
|
members:
|
|
|
|
1. base_ptr: Of type void*, which points to the first element
|
|
of the vector. The base_ptr can also be used as a key to
|
|
determine the vector upon which the access violation has
|
|
occurred.
|
|
|
|
2. end_ptr : Of type void*, which points to one position after
|
|
the last element of the vector
|
|
|
|
3. access_ptr: Of type void*, points to the memory location
|
|
which is the base_ptr offset by the derived index value.
|
|
|
|
4. type_size: Size of the vector's element type in bytes. This
|
|
value can be used to determine the number of elements in
|
|
the vector based on the base_ptr and end_ptr.
|
|
|
|
|
|
The implementation of the handle_runtime_violation method can at this
|
|
point perform various actions such as:
|
|
|
|
1. Log the violation
|
|
2. Throw an exception (eg: std::runtime_error)
|
|
3. Remedy the access_ptr to allow for the evaluation to continue
|
|
|
|
|
|
Note37: When employing option [3], handle_runtime_violation needs to
|
|
return true, otherwise the caller will assume an unhandled access
|
|
violation and default to using the base_ptr.
|
|
|
|
It is recommended, at the very least, to throw an exception when
|
|
handling vector access violations and to only consider option [3] when
|
|
the the ramifications of changing the access_ptr are well understood.
|
|
|
|
The following are simple examples of how the handle_runtime_violation
|
|
can be implemented.
|
|
|
|
Example 1: Log the access violation to stdout and then throw a runtime
|
|
error exception:
|
|
|
|
bool handle_runtime_violation(violation_context& context) override
|
|
{
|
|
printf("ERROR - Runtime vector access violation. "
|
|
"base: %p end: %p access: %p typesize: %lu\n",
|
|
context.base_ptr ,
|
|
context.end_ptr ,
|
|
context.access_ptr,
|
|
context.type_size);
|
|
|
|
throw std::runtime_error("Runtime vector access violation.");
|
|
return false;
|
|
}
|
|
|
|
|
|
Example 2: Handle the access violation by resetting the access pointer
|
|
to the last value in the vector.
|
|
|
|
bool handle_runtime_violation(violation_context& context) override
|
|
{
|
|
context.access_ptr =
|
|
static_cast<char*>(context.end_ptr) - context.type_size;
|
|
return true;
|
|
}
|
|
|
|
|
|
Note38: The return value of true in the above handler method signals
|
|
the caller to continue the vector access using the updated access_ptr.
|
|
|
|
|
|
(2) String Access Runtime Checks
|
|
Expressions that contain strings where elements or substrings of the
|
|
strings may be accessed using indexes that can only be determined at
|
|
runtime may result in memory access violations when the index or range
|
|
is out of the string's bound. Examples of problematic expressions are
|
|
as follows:
|
|
|
|
1. s[i : j + k]
|
|
2. s[i : j + k][-x : y]
|
|
3. (s1 + s2)[i : j + k]
|
|
4. '01234'[5 + i]
|
|
5. s += s[i : j + k]
|
|
6. s[i : j + k] := 'chappy days'[1 : ]
|
|
|
|
|
|
To enable string access runtime checks all one needs to do is simply
|
|
use the following define before the ExprTk header is included or as
|
|
part of the compilation define parameters:
|
|
|
|
exprtk_enable_range_runtime_checks
|
|
|
|
|
|
When the above define is used, and a string related runtime access
|
|
violation occurs a std::runtime_error exception will be thrown. The
|
|
following demonstrates the general flow of handling the access
|
|
violation:
|
|
|
|
parser.compile(expression_string, expression)
|
|
.
|
|
.
|
|
try
|
|
{
|
|
expression.value();
|
|
}
|
|
catch (std::runtime_error& rte)
|
|
{
|
|
printf("Exception: %s\n", rte.what());
|
|
}
|
|
|
|
|
|
(3) Loop Iteration Checks
|
|
Expressions that contain loop structures (eg: for/while/repeat et al)
|
|
can be problematic from a usage point of view due to the difficulty in
|
|
determining the following:
|
|
|
|
1. Will the loop ever complete (aka is this an infinite loop?)
|
|
2. Maximum loop execution time
|
|
|
|
|
|
ExprTk provides the ability to inject a runtime check within loop
|
|
conditionals, and to have the result of the check either signal the
|
|
loop to continue or for the check to raise a loop violation error.
|
|
|
|
The process involves instantiating a user defined loop_runtime_check
|
|
(LRTC), registering the instance with a exprtk::parser instance and
|
|
specifying which loop types the check is to performed upon. The
|
|
following code demonstrates a how custom LRTC can be instantiated and
|
|
registered with the associated parser:
|
|
|
|
typedef exprtk::parser<T> parser_t;
|
|
typedef exprtk::loop_runtime_check loop_runtime_check_t;
|
|
|
|
my_loop_rtc loop_rtc;
|
|
loop_runtime_check.loop_set = loop_runtime_check_t::e_all_loops;
|
|
loop_runtime_check.max_loop_iterations = 100000;
|
|
|
|
parser_t parser;
|
|
|
|
parser.register_loop_runtime_check(loop_rtc);
|
|
|
|
|
|
The following is an example of how one could derive from and implement
|
|
a custom loop_runtime_check:
|
|
|
|
struct my_loop_rtc final : exprtk::loop_runtime_check
|
|
{
|
|
|
|
bool check() override
|
|
{
|
|
//
|
|
return ...
|
|
}
|
|
|
|
void handle_runtime_violation
|
|
(const exprtk::violation_context&) override
|
|
{
|
|
throw std::runtime_error("Loop runtime violation.");
|
|
}
|
|
};
|
|
|
|
|
|
In the above code, if either the check method returns false or the
|
|
loop iteration count exceeds the max_loop_iterations value, the
|
|
handle_runtime_violation method will be invoked, coupled with it a
|
|
violation_context shall be provided that will contain the following
|
|
members:
|
|
|
|
1. loop: Of type loop_types. This value denotes the type of
|
|
loop that triggered the violation (e_for_loop, e_while_loop,
|
|
e_repeat_until_loop).
|
|
|
|
2. violation: Of type type. This value denotes the type of
|
|
violation (e_iteration_count, e_timeout)
|
|
|
|
3. iteration_count: Of type uint64_t. The number of iterations
|
|
that the triggering loop has executed since the start of the
|
|
expression.
|
|
|
|
|
|
Note39: The lifetime of any parser or expression instance must not
|
|
exceed that of any LRTC instance that has been registered with it.
|
|
|
|
The following is an example implementation of an LRTC that
|
|
supports loop timeout violations:
|
|
|
|
struct timeout_loop_rtc final : exprtk::loop_runtime_check
|
|
{
|
|
using time_point_t =
|
|
std::chrono::time_point<std::chrono::steady_clock>;
|
|
|
|
std::size_t iterations_ = 0;
|
|
time_point_t timeout_tp_;
|
|
|
|
bool check() override
|
|
{
|
|
if (std::chrono::steady_clock::now() >= timeout_tp_)
|
|
{
|
|
// handle_runtime_violation shall be invoked
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void handle_runtime_violation
|
|
(const exprtk::violation_context&) override
|
|
{
|
|
throw std::runtime_error("Loop timed out");
|
|
}
|
|
|
|
void set_timeout_time(const time_point_t& timeout_tp)
|
|
{
|
|
timeout_tp_ = timeout_tp;
|
|
}
|
|
};
|
|
|
|
|
|
In the above code, the check method shall be invoked on each iteration
|
|
of the associated loop. Within the method the current time is compared
|
|
to the setup timeout time-point, in the event the current time exceeds
|
|
the timeout, the method returns false, triggering the violation, which
|
|
in turn will result in the handle_runtime_violation being invoked.
|
|
|
|
The following code demonstrates how the above defined LRTC can be used
|
|
to ensure that at the very least the loop portion(s) of an expression
|
|
will never exceed a given amount of execution time.
|
|
|
|
typedef exprtk::parser<T> parser_t;
|
|
typedef exprtk::loop_runtime_check loop_runtime_check_t;
|
|
|
|
my_loop_rtc loop_rtc;
|
|
loop_rtc.loop_set = loop_runtime_check_t::e_all_loops;
|
|
loop_rtc.max_loop_iterations = 100000;
|
|
|
|
parser_t parser;
|
|
|
|
parser.register_loop_runtime_check(loop_rtc);
|
|
.
|
|
.
|
|
.
|
|
.
|
|
using std::chrono;
|
|
const auto max_duration = seconds(25);
|
|
|
|
try
|
|
{
|
|
loop_rtc.set_timeout_time(steady_clock::now() + max_duration);
|
|
expression.value();
|
|
|
|
loop_rtc.set_timeout_time(steady_clock::now() + max_duration);
|
|
expression.value();
|
|
|
|
loop_rtc.set_timeout_time(steady_clock::now() + max_duration);
|
|
expression.value();
|
|
|
|
}
|
|
catch(std::runtime_error& exception)
|
|
{
|
|
printf("Exception: %s\n",exception.what());
|
|
}
|
|
|
|
|
|
(4) Compilation Process Checkpointing
|
|
When compiling an expression, one may require the compilation process
|
|
to periodically checkpoint its internal state, subsequently at the
|
|
checkpoint one can then make the decision to continue the compilation
|
|
process or to immediately terminate and return.
|
|
|
|
The following are reasons one may want to checkpoint the compilation
|
|
process:
|
|
|
|
1. Determine if the compilation process has run for far too long
|
|
2. Determine if the current stack frame size exceeds a limit
|
|
3. Enforce an external termination request
|
|
|
|
|
|
ExprTk provides the ability to inject a checkpoint into the
|
|
compilation process that will be evaluated periodically. This
|
|
capability is achieved by registering a user-implemented compilation
|
|
check (CCK) to the parser before expression compilation. Initially a
|
|
CCK can be defined as follows:
|
|
|
|
struct compilation_timeout_check final :
|
|
public exprtk::compilation_check
|
|
{
|
|
bool continue_compilation(compilation_context& context)
|
|
override
|
|
{
|
|
// Determine if compilation should continue
|
|
return ...;
|
|
}
|
|
};
|
|
|
|
|
|
An example checkpoint use-case could be that we do not want the
|
|
compilation process to take longer than a maximum defined period, eg:
|
|
five seconds. The associated compilation check implementation could be
|
|
as follows:
|
|
|
|
struct my_compilation_timeout_check final :
|
|
public exprtk::compilation_check
|
|
{
|
|
|
|
bool continue_compilation(compilation_context& context)
|
|
override
|
|
{
|
|
static constexpr std::size_t max_iters_per_check = 1000;
|
|
|
|
if (++iterations_ >= max_iters_per_check)
|
|
{
|
|
if (std::chrono::steady_clock::now() >= timeout_tp_)
|
|
{
|
|
context.error_message = "Compilation has timed-out";
|
|
return false;
|
|
}
|
|
|
|
iterations_ = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
using time_point_t = std::chrono::time_point<std::chrono::steady_clock>;
|
|
|
|
void set_timeout_time(const time_point_t& timeout_tp)
|
|
{
|
|
timeout_tp_ = timeout_tp;
|
|
}
|
|
|
|
std::size_t iterations_ = 0;
|
|
time_point_t timeout_tp_;
|
|
};
|
|
|
|
|
|
Usage of the above defined compilation check will require registering
|
|
the check with the parser, setting up the expiry time and then
|
|
proceeding to compile the expression. The following is a general
|
|
outline of what will be needed:
|
|
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
expression_t expression;
|
|
|
|
my_compilation_timeout_check compilation_timeout_check;
|
|
|
|
parser_t parser;
|
|
parser.
|
|
register_compilation_timeout_check(compilation_timeout_check);
|
|
|
|
const auto max_duration = std::chrono::seconds(5);
|
|
const auto timeout_tp =
|
|
std::chrono::steady_clock::now() + max_duration;
|
|
|
|
compilation_timeout_check.set_timeout_time(timeout_tp);
|
|
|
|
if (!parser.compile(large_expression_string, expression))
|
|
{
|
|
printf("Error: %s\t\n", parser.error().c_str());
|
|
return;
|
|
}
|
|
|
|
|
|
(5) Assert statements
|
|
ExprTk supports the use of assert statements to verify pre and post
|
|
conditions during the evaluation of expressions. The assert statements
|
|
are only active when a user defined assert handler is registered with
|
|
the parser before expression compilation, otherwise they are compiled
|
|
out, this is similar to how asserts are included/excluded in C++
|
|
coupled with the definition of NDEBUG. The assert syntax has three
|
|
variations as described below:
|
|
|
|
assert(x + y > i);
|
|
assert(x + y > i, 'assert statement 1');
|
|
assert(x + y > i, 'assert statement 1', 'ASSERT01');
|
|
|
|
|
|
The three assert statement input parameters are as follows:
|
|
|
|
1. assert condition (mandatory)
|
|
2. assert message (optional)
|
|
3. assert id (optional)
|
|
|
|
|
|
The assert condition is essentially a boolean statement that is
|
|
expected to be true during evaluation. The other two parameters of
|
|
assert message and ID are string values that are intended to provide
|
|
feedback to the handler and to ensure the uniqueness of assert
|
|
statement respectively. The three parameters denoted above and the
|
|
offset of the assert statement from the beginning of the expression
|
|
are placed inside assert_context that is provided as part of the
|
|
assert_check handler. A user defined assert_check handler can be
|
|
defined as follows:
|
|
|
|
struct my_assert_handler final : public exprtk::assert_check
|
|
{
|
|
void handle_assert(const assert_context& ctxt) override
|
|
{
|
|
printf("condition: [%s] \n", ctxt.condition.c_str());
|
|
printf("message: [%s] \n", ctxt.message .c_str());
|
|
printf("id: [%s] \n", ctxt.id .c_str());
|
|
printf("offset: [%lu]\n", ctxt.offet );
|
|
// throw std::runtime_error(.....);
|
|
}
|
|
};
|
|
|
|
|
|
Once the assert_check handler has been registered with the parser,
|
|
expressions that contain assert statements will have their asserts
|
|
compiled in as part final evaluable expression instance:
|
|
|
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
const std::string program =
|
|
" var x := 4; "
|
|
" "
|
|
" for (var i := 0; i < 10; i += 1) "
|
|
" { "
|
|
" assert(i < x, 'assert statement 1'); "
|
|
" } ";
|
|
|
|
my_assert_handler handler;
|
|
|
|
expression_t expression;
|
|
parser_t parser;
|
|
|
|
parser.register_assert_check(handler);
|
|
parser.compile(program, expression);
|
|
|
|
|
|
(6) Runtime Check Overheads
|
|
All of the above mentioned runtime checks will incur an execution time
|
|
overhead during the evaluation of expressions. This is an unfortunate
|
|
but necessary side-effect of the process when runtime safety is of
|
|
concern.
|
|
|
|
A recommendation to consider, that is not demonstrated above, is that
|
|
in the check method of the LRTC, one should not evaluate the timeout
|
|
condition on every call to check (aka on every loop iteration).
|
|
Instead a counter should be maintained and incremented on each call
|
|
and when the counter exceeds some predefined amount (eg: 10000
|
|
iterations), then the timeout based check can be preformed. The
|
|
reasoning here is that incrementing an integer should be far less
|
|
expensive than computing the current "now" time-point.
|
|
|
|
|
|
(7) Runtime Check Limitations
|
|
The available RTC mechanisms in ExprTk are limited to implementing
|
|
said checks only within ExprTk based syntax sections of an expression.
|
|
The RTCs will not be active within user defined functions, or
|
|
composited functions that have been compiled with parser instances
|
|
that don't have the same set of RTC configurations enabled.
|
|
|
|
|
|
(8) Runtime Handlers
|
|
When implementing stateful run-time check handlers one must be careful
|
|
to ensure the handler is setup correctly or reset between calls to the
|
|
expression::value or parser::compile methods.
|
|
|
|
The following example code utilises the compilation timeout RTC and
|
|
expression loop duration RTC examples from above to demonstrate the
|
|
need to reset the internal state of the various handlers before
|
|
compilation and valuation processes are invoked, as not doing so will
|
|
affect the ability for the next expression in the list to either be
|
|
correctly compiled or evaluated due to the potential of erroneous
|
|
timeouts occurring.
|
|
|
|
typedef exprtk::expression<T> expression_t;
|
|
typedef exprtk::parser<T> parser_t;
|
|
|
|
my_compilation_timeout_check compilation_timeout_check;
|
|
|
|
my_loop_rtc loop_rtc;
|
|
loop_rtc.loop_set = loop_runtime_check_t::e_all_loops;
|
|
loop_rtc.max_loop_iterations = 100000;
|
|
|
|
parser_t parser;
|
|
parser.register_loop_runtime_check(loop_rtc);
|
|
parser.
|
|
register_compilation_timeout_check(compilation_timeout_check);
|
|
|
|
const auto compile_timeout_tp = []()
|
|
{
|
|
const auto max_duration = std::chrono::seconds(5);
|
|
return std::chrono::steady_clock::now() + max_duration;
|
|
};
|
|
|
|
const auto loop_timeout_tp = []()
|
|
{
|
|
const auto max_duration = std::chrono::seconds(10);
|
|
return std::chrono::steady_clock::now() + max_duration;
|
|
};
|
|
|
|
const std::vector<std::string> expressions =
|
|
{
|
|
"x + y / 2",
|
|
"sin(x) / cos(y) + 1",
|
|
"clamp(-1, sin(2 * pi * x) + cos(y / 2 * pi), +1)"
|
|
};
|
|
|
|
for (const auto& expr_str : expressions)
|
|
{
|
|
// Reset the timeout for the compilation RTC
|
|
compilation_timeout_check
|
|
.set_timeout_time(compile_timeout_tp());
|
|
|
|
expression_t expression;
|
|
|
|
if (!parser.compile(large_expression_string, expression))
|
|
{
|
|
printf("Error: %s\t\n", parser.error().c_str());
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Reset the timeout for the loop duration RTC
|
|
loop_rtc.set_timeout_time(loop_timeout_tp());
|
|
|
|
expression.value();
|
|
}
|
|
catch(std::runtime_error& exception)
|
|
{
|
|
printf("Exception: %s\n Expression: %s\n",
|
|
exception.what(),
|
|
expr_str.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 25 - BENCHMARKING]
|
|
As part of the ExprTk package there is an expression benchmark utility
|
|
named 'exprtk_benchmark'. The utility attempts to determine expression
|
|
evaluation speed (or rate of evaluations - evals per second), by
|
|
evaluating each expression numerous times and mutating the underlying
|
|
variables of the expression between each evaluation. The utility
|
|
assumes any valid ExprTk expression (containing conditionals, loops
|
|
etc), however it will only make use of a predefined set of scalar
|
|
variables, namely: a, b, c, x, y, z and w. That being said expressions
|
|
themselves can contain any number of local variables, vectors or
|
|
strings. There are two modes of operation:
|
|
|
|
(1) Default
|
|
(2) User Specified Expressions
|
|
|
|
|
|
(1) Default
|
|
The default mode is enabled simply by executing the exprtk_benchmark
|
|
binary with no command line parameters. In this mode a predefined set
|
|
of expressions will be evaluated in three phases:
|
|
|
|
(a) ExprTk evaluation
|
|
(b) Native evaluation
|
|
(c) ExprTk parse
|
|
|
|
|
|
In the first two phases (a and b) a list of predefined (hard-coded)
|
|
expressions will be evaluated using both ExprTk and native mode
|
|
implementations. This is done so as to compare evaluation times
|
|
between ExprTk and native implementations. The set of expressions used
|
|
are as follows:
|
|
|
|
(01) (y + x)
|
|
(02) 2 * (y + x)
|
|
(03) (2 * y + 2 * x)
|
|
(04) ((1.23 * x^2) / y) - 123.123
|
|
(05) (y + x / y) * (x - y / x)
|
|
(06) x / ((x + y) + (x - y)) / y
|
|
(07) 1 - ((x * y) + (y / x)) - 3
|
|
(08) (5.5 + x) + (2 * x - 2 / 3 * y) * (x / 3 + y / 4) + (y + 7.7)
|
|
(09) 1.1x^1 + 2.2y^2 - 3.3x^3 + 4.4y^15 - 5.5x^23 + 6.6y^55
|
|
(10) sin(2 * x) + cos(pi / y)
|
|
(11) 1 - sin(2 * x) + cos(pi / y)
|
|
(12) sqrt(111.111 - sin(2 * x) + cos(pi / y) / 333.333)
|
|
(13) (x^2 / sin(2 * pi / y)) - x / 2
|
|
(14) x + (cos(y - sin(2 / x * pi)) - sin(x - cos(2 * y / pi))) - y
|
|
(15) clamp(-1.0, sin(2 * pi * x) + cos(y / 2 * pi), +1.0)
|
|
(16) max(3.33, min(sqrt(1 - sin(2 * x) + cos(pi / y) / 3), 1.11))
|
|
(17) if((y + (x * 2.2)) <= (x + y + 1.1), x - y, x*y) + 2 * pi / x
|
|
|
|
|
|
The third and final phase (c), is used to determine average
|
|
compilation rates (compiles per second) for expressions of varying
|
|
complexity. Each expression is compiled 100K times and the average for
|
|
each expression is output.
|
|
|
|
|
|
(2) User Specified Expressions
|
|
In this mode two parameters are passed to the utility via the command
|
|
line:
|
|
|
|
(a) A name of a text file containing one expression per line
|
|
(b) An integer representing the number of evaluations per expression
|
|
|
|
|
|
An example execution of the benchmark utility in this mode is as
|
|
follows:
|
|
|
|
./exprtk_benchmark my_expressions.txt 1000000
|
|
|
|
|
|
The above invocation will load the expressions from the file
|
|
'my_expressions.txt' and will then proceed to evaluate each expression
|
|
one million times, varying the above mentioned variables (x, y, z
|
|
etc.) between each evaluation, and at the end of each expression round
|
|
a print out of running times, result of a single evaluation and total
|
|
sum of results is provided as demonstrated below:
|
|
|
|
Expression 1 of 7 4.770 ns 47700 ns ( 9370368.0) '((((x+y)+z)))'
|
|
Expression 2 of 7 4.750 ns 47500 ns ( 1123455.9) '((((x+y)-z)))'
|
|
Expression 3 of 7 4.766 ns 47659 ns (21635410.7) '((((x+y)*z)))'
|
|
Expression 4 of 7 5.662 ns 56619 ns ( 1272454.9) '((((x+y)/z)))'
|
|
Expression 5 of 7 4.950 ns 49500 ns ( 4123455.9) '((((x-y)+z)))'
|
|
Expression 6 of 7 7.581 ns 75810 ns (-4123455.9) '((((x-y)-z)))'
|
|
Expression 7 of 7 4.801 ns 48010 ns ( 0.0) '((((x-y)*z)))'
|
|
|
|
|
|
The benchmark utility can be very useful when investigating evaluation
|
|
efficiency issues with ExprTk or simply during the prototyping of
|
|
expressions. As an example, lets take the following expression:
|
|
|
|
1 / sqrt(2x) * e^(3y)
|
|
|
|
|
|
Lets say we would like to determine which sub-part of the expression
|
|
takes the most time to evaluate and perhaps attempt to rework the
|
|
expression based on the results. In order to do this we will create a
|
|
text file called 'test.txt' and then proceed to make some educated
|
|
guesses about how to break the expression up into its more
|
|
'interesting' sub-parts which we will then add as one expression per
|
|
line to the file. An example breakdown may be as follows:
|
|
|
|
1 / sqrt(2x) * e^(3y)
|
|
1 / sqrt(2x)
|
|
e^(3y)
|
|
|
|
|
|
The benchmark with the given file, where each expression will be
|
|
evaluated 100K times can be executed as follows:
|
|
|
|
./exprtk_benchmark test.txt 100000
|
|
Expr 1 of 3 90.340 ns 9034000 ns (296417859.3) '1/sqrt(2x)*e^(3y)'
|
|
Expr 2 of 3 11.100 ns 1109999 ns ( 44267.3) '1/sqrt(2x)'
|
|
Expr 3 of 3 77.830 ns 7783000 ns (615985286.6) 'e^(3y)'
|
|
[*] Number Of Evals: 300000
|
|
[*] Total Time: 0.018sec
|
|
[*] Total Single Eval Time: 0.000ms
|
|
|
|
|
|
From the results above we conclude that the third expression (e^(3y))
|
|
consumes the largest amount of time. The variable 'e', as used in both
|
|
the benchmark and in the expression, is an approximation of the
|
|
transcendental mathematical constant e (2.71828182845904...) hence the
|
|
sub-expression should perhaps be modified to use the generally more
|
|
efficient built-in 'exp' function.
|
|
|
|
./exprtk_benchmark test.txt 1000000
|
|
Expr 1 of 5 86.563 ns 8656300ns (296417859.6) '1/sqrt(2x)*e^(3y)'
|
|
Expr 2 of 5 40.506 ns 4050600ns (296417859.6) '1/sqrt(2x)*exp(3y)'
|
|
Expr 3 of 5 14.248 ns 1424799ns ( 44267.2) '1/sqrt(2x)'
|
|
Expr 4 of 5 88.840 ns 8884000ns (615985286.9) 'e^(3y)'
|
|
Expr 5 of 5 29.267 ns 2926699ns (615985286.9) 'exp(3y)'
|
|
[*] Number Of Evals: 5000000
|
|
[*] Total Time: 0.260sec
|
|
[*] Total Single Eval Time: 0.000ms
|
|
|
|
|
|
The above output demonstrates the results from making the previously
|
|
mentioned modification to the expression. As can be seen the new form
|
|
of the expression using the 'exp' function reduces the evaluation time
|
|
by over 50%, in other words increases the evaluation rate by two fold.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 26 - EXPRTK NOTES]
|
|
The following is a list of facts and suggestions one may want to take
|
|
into account when using ExprTk:
|
|
|
|
(00) Precision and performance of expression evaluations are the
|
|
dominant principles of the ExprTk library.
|
|
|
|
(01) ExprTk uses a rudimentary imperative programming model with
|
|
syntax based on languages such as Pascal and C. Furthermore
|
|
ExprTk is an LL(2) type grammar and is processed using a
|
|
recursive descent parsing algorithm.
|
|
|
|
(02) Supported types are float, double, long double and MPFR/GMP.
|
|
Generally any user defined numerical type that supports all the
|
|
basic floating point arithmetic operations: -,+,*,/,^,% etc;
|
|
unary and binary operations: sin,cos,min,max,equal etc and any
|
|
other ExprTk dependent operations can be used to specialise the
|
|
various components: expression, parser and symbol_table.
|
|
|
|
(03) Standard arithmetic operator precedence is applied (BEDMAS). In
|
|
general C, Pascal or Rust equivalent unary, binary, logical and
|
|
equality/inequality operator precedence rules apply.
|
|
eg: a == b and c > d + 1 ---> (a == b) and (c > (d + 1))
|
|
x - y <= z / 2 ---> (x - y) <= (z / 2)
|
|
a - b / c * d^2^3 ---> a - ((b / c) * d^(2^3))
|
|
|
|
(04) Results of expressions that are deemed as being 'valid' are to
|
|
exist within the set of Real numbers. All other results will be
|
|
of the value: Not-A-Number (NaN). However this may not
|
|
necessarily be a requirement for user defined numerical types,
|
|
eg: complex number type.
|
|
|
|
(05) Supported user defined types are numeric and string
|
|
variables, numeric vectors and functions.
|
|
|
|
(06) All reserved words, keywords, variable, vector, string and
|
|
function names are case-insensitive.
|
|
|
|
(07) Variable, vector, string variable and function names must begin
|
|
with a letter (A-Z or a-z), then can be comprised of any
|
|
combination of letters, digits, underscores and dots, ending in
|
|
either a letter (A-Z or a-z), digit or underscore. (eg: x, y2,
|
|
var1, power_func99, person.age, item.size.0). The associated
|
|
regex pattern is: [a-zA-Z][a-zA-Z0-9_.]*[a-zA-Z0-9_]+
|
|
|
|
(08) Expression lengths and sub-expression lists are limited only by
|
|
storage capacity.
|
|
|
|
(09) The life-time of objects registered with or created from a
|
|
specific symbol-table must span at least the lifetime of the
|
|
symbol table instance and all compiled expressions which
|
|
utilise objects, such as variables, strings, vectors, function
|
|
compositor functions and functions of that symbol-table,
|
|
otherwise the result will be undefined behaviour.
|
|
|
|
(10) Equal and not_equal are normalised-epsilon equality routines,
|
|
which use epsilons of 0.0000000001 and 0.000001 for double and
|
|
float types respectively.
|
|
|
|
(11) All trigonometric functions assume radian input unless stated
|
|
otherwise.
|
|
|
|
(12) Expressions may contain white-space characters such as space,
|
|
tabs, new-lines, control-feed et al.
|
|
('\n', '\r', '\t', '\b', '\v', '\f')
|
|
|
|
(13) Strings may be comprised of any combination of letters, digits
|
|
special characters including (~!@#$%^&*()[]|=+ ,./?<>;:"`~_) or
|
|
hexadecimal escaped sequences (eg: \0x30) and must be enclosed
|
|
with single-quotes.
|
|
eg: 'Frankly my dear, \0x49 do n0t give a damn!'
|
|
|
|
(14) User defined normal functions can have up to 20 parameters,
|
|
where as user defined generic-functions and vararg-functions
|
|
can have an unlimited number of parameters.
|
|
|
|
(15) The inbuilt polynomial functions can be at most of degree 12.
|
|
|
|
(16) Where appropriate constant folding optimisations may be applied.
|
|
(eg: The expression '2 + (3 - (x / y))' becomes '5 - (x / y)')
|
|
|
|
(17) If the strength reduction compilation option has been enabled,
|
|
then where applicable strength reduction optimisations may be
|
|
applied.
|
|
|
|
(18) String processing capabilities are available by default. To
|
|
turn them off, the following needs to be defined at compile
|
|
time: exprtk_disable_string_capabilities
|
|
|
|
(19) Composited functions can call themselves or any other functions
|
|
that have been defined prior to their own definition.
|
|
|
|
(20) Recursive calls made from within composited functions will have
|
|
a stack size bound by the stack of the executing architecture.
|
|
|
|
(21) User defined functions by default are assumed to have side
|
|
effects. As such an "all constant parameter" invocation of such
|
|
functions wont result in constant folding. If the function has
|
|
no side-effects then that can be noted during the constructor
|
|
of the ifunction allowing it to be constant folded where
|
|
appropriate.
|
|
|
|
(22) The entity relationship between symbol_table and an expression
|
|
is many-to-many. However the intended 'typical' use-case where
|
|
possible, is to have a single symbol table manage the variable
|
|
and function requirements of multiple expressions.
|
|
|
|
(23) The common use-case for an expression is to have it compiled
|
|
only ONCE and then subsequently have it evaluated multiple
|
|
times. An extremely inefficient and suboptimal approach would
|
|
be to recompile an expression from its string form every time
|
|
it requires evaluating.
|
|
|
|
(24) It is strongly recommended that the return value of method
|
|
invocations from the parser and symbol_table types be taken
|
|
into account. Specifically the 'compile' method of the parser
|
|
and the 'add_xxx' set of methods of the symbol_table as they
|
|
denote either the success or failure state of the invoked call.
|
|
Continued processing from a failed state without having first
|
|
rectified the underlying issue will in turn result in further
|
|
failures and undefined behaviours.
|
|
|
|
(25) The following are examples of compliant floating point value
|
|
representations:
|
|
|
|
(01) 12345 (06) -123.456
|
|
(02) +123.456e+12 (07) 123.456E-12
|
|
(03) +012.045e+07 (08) .1234
|
|
(04) 1234. (09) -56789.
|
|
(05) 123.456f (10) -321.654E+3L
|
|
|
|
(26) Expressions may contain any of the following comment styles:
|
|
|
|
(1) // .... \n
|
|
(2) # .... \n
|
|
(3) /* .... */
|
|
|
|
(27) The 'null' value type is a special non-zero type that
|
|
incorporates specific semantics when undergoing operations with
|
|
the standard numeric type. The following is a list of type and
|
|
boolean results associated with the use of 'null':
|
|
|
|
(1) null +,-,*,/,% x --> x
|
|
(2) x +,-,*,/,% null --> x
|
|
(3) null +,-,*,/,% null --> null
|
|
(4) null == null --> true
|
|
(5) null == x --> true
|
|
(6) x == null --> true
|
|
(7) x != null --> false
|
|
(8) null != null --> false
|
|
(9) null != x --> false
|
|
|
|
(28) The following is a list of reserved words and symbols used by
|
|
ExprTk. Attempting to add a variable or custom function to a
|
|
symbol table using any of the reserved words will result in a
|
|
failure.
|
|
|
|
abs, acos, acosh, and, asin, asinh, assert, atan, atan2,
|
|
atanh, avg, break, case, ceil, clamp, continue, cosh, cos,
|
|
cot, csc, default, deg2grad, deg2rad, else, equal, erfc,
|
|
erf, exp, expm1, false, floor, for, frac, grad2deg, hypot,
|
|
iclamp, if, ilike, in, inrange, in, like, log, log10, log1p,
|
|
log2, logn, mand, max, min, mod, mor, mul, nand, ncdf, nor,
|
|
not, not_equal, not, null, or, pow, rad2deg, repeat, return,
|
|
root, roundn, round, sec, sgn, shl, shr, sinc, sinh, sin,
|
|
sqrt, sum, swap, switch, tanh, tan, true, trunc, until, var,
|
|
while, xnor, xor
|
|
|
|
(29) Every valid ExprTk statement is a "value returning" expression.
|
|
Unlike some languages that limit the types of expressions that
|
|
can be performed in certain situations, in ExprTk any valid
|
|
expression can be used in any "value consuming" context. eg:
|
|
|
|
var y := 3;
|
|
for (var x := switch
|
|
{
|
|
case 1 : 7;
|
|
case 2 : -1 + ~{var x{};};
|
|
default : y > 2 ? 3 : 4;
|
|
};
|
|
x != while (y > 0) { y -= 1; };
|
|
x -= {
|
|
if (min(x,y) < 2 * max(x,y))
|
|
x + 2;
|
|
else
|
|
x + y - 3;
|
|
}
|
|
)
|
|
{
|
|
(x + y) / (x - y);
|
|
};
|
|
|
|
(30) It is recommended when prototyping expressions that the ExprTk
|
|
REPL be utilised, as it supports all the features available in
|
|
the library, including complete error analysis, benchmarking
|
|
and dependency dumps etc which allows for rapid
|
|
coding/prototyping and debug cycles without the hassle of
|
|
having to recompile test programs with expressions that have
|
|
been hard-coded. It is also a good source of truth for how the
|
|
library's various features can be applied.
|
|
|
|
(31) For performance considerations, one should assume the actions
|
|
of expression, symbol table and parser instance instantiation
|
|
and destruction, and the expression compilation process itself
|
|
to be of high latency. Hence none of them should be part of any
|
|
performance critical code paths, and should instead occur
|
|
entirely either before or after such code paths.
|
|
|
|
(32) Deep copying an expression instance for the purposes of
|
|
persisting to disk or otherwise transmitting elsewhere with the
|
|
intent to 'resurrect' the expression instance later on is not
|
|
possible due to the reasons described in the final note of
|
|
Section 10. The recommendation is to instead simply persist the
|
|
string form of the expression and compile the expression at
|
|
run-time on the target.
|
|
|
|
(33) The correctness and robustness of the ExprTk library is
|
|
maintained by having a comprehensive suite of unit tests and
|
|
functional tests all of which are run using sanitizers (ASAN,
|
|
UBSAN, LSAN, MSAN, TSAN). Additionally, continuous fuzz-testing
|
|
provided by Google OSS Fuzz, and static analysis via Synopsis
|
|
Coverity.
|
|
|
|
(34) The library name ExprTk is pronounced "Ex-Pee-Ar-Tee-Kay" or
|
|
simply "Mathematical Expression Toolkit"
|
|
|
|
|
|
(35) For general support, inquires or bug/issue reporting:
|
|
https://www.partow.net/programming/exprtk/index.html#support
|
|
|
|
(36) Before jumping in and using ExprTk, do take the time to peruse
|
|
the documentation and all of the examples, both in the main and
|
|
the extras distributions. Having an informed general view of
|
|
what can and can't be done, and how something should be done
|
|
with ExprTk, will likely result in a far more productive and
|
|
enjoyable programming experience.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 27 - SIMPLE EXPRTK EXAMPLE]
|
|
The following is a simple yet complete example demonstrating typical
|
|
usage of the ExprTk Library. The example instantiates a symbol table
|
|
object, adding to it three variables named x, y and z, and a custom
|
|
user defined function, that accepts only two parameters, named myfunc.
|
|
The example then proceeds to instantiate an expression object and
|
|
register to it the symbol table instance.
|
|
|
|
A parser is then instantiated, and the string representation of the
|
|
expression and the expression object are passed to the parser's
|
|
compile method for compilation. If an error occurred during
|
|
compilation, the compile method will return false, leading to a series
|
|
of error diagnostics being printed to stdout. Otherwise the newly
|
|
compiled expression is evaluated by invoking the expression object's
|
|
value method, and subsequently printing the result of the computation
|
|
to stdout.
|
|
|
|
|
|
--- snip ---
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
#include "exprtk.hpp"
|
|
|
|
template <typename T>
|
|
struct myfunc final : public exprtk::ifunction<T>
|
|
{
|
|
myfunc() : exprtk::ifunction<T>(2) {}
|
|
|
|
T operator()(const T& v1, const T& v2) override
|
|
{
|
|
return T(1) + (v1 * v2) / T(3);
|
|
}
|
|
};
|
|
|
|
int main()
|
|
{
|
|
typedef exprtk::symbol_table<double> symbol_table_t;
|
|
typedef exprtk::expression<double> expression_t;
|
|
typedef exprtk::parser<double> parser_t;
|
|
typedef exprtk::parser_error::type error_t;
|
|
|
|
const std::string expression_string =
|
|
"z := 2 myfunc([4 + sin(x / pi)^3],y ^ 2)";
|
|
|
|
double x = 1.1;
|
|
double y = 2.2;
|
|
double z = 3.3;
|
|
|
|
myfunc<double> mf;
|
|
|
|
symbol_table_t symbol_table;
|
|
symbol_table.add_constants();
|
|
symbol_table.add_variable("x",x);
|
|
symbol_table.add_variable("y",y);
|
|
symbol_table.add_variable("z",z);
|
|
symbol_table.add_function("myfunc",mf);
|
|
|
|
expression_t expression;
|
|
expression.register_symbol_table(symbol_table);
|
|
|
|
parser_t parser;
|
|
|
|
if (!parser.compile(expression_string,expression))
|
|
{
|
|
// A compilation error has occurred. Attempt to
|
|
// print all errors to stdout.
|
|
|
|
printf("Error: %s\tExpression: %s\n",
|
|
parser.error().c_str(),
|
|
expression_string.c_str());
|
|
|
|
for (std::size_t i = 0; i < parser.error_count(); ++i)
|
|
{
|
|
// Include the specific nature of each error
|
|
// and its position in the expression string.
|
|
|
|
error_t error = parser.get_error(i);
|
|
|
|
printf("Error: %02d Position: %02d "
|
|
"Type: [%s] "
|
|
"Message: %s "
|
|
"Expression: %s\n",
|
|
static_cast<int>(i),
|
|
static_cast<int>(error.token.position),
|
|
exprtk::parser_error::to_str(error.mode).c_str(),
|
|
error.diagnostic.c_str(),
|
|
expression_string.c_str());
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Evaluate the expression and obtain its result.
|
|
|
|
double result = expression.value();
|
|
|
|
printf("Result: %10.5f\n",result);
|
|
|
|
return 0;
|
|
}
|
|
--- snip ---
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 28 - BUILD OPTIONS]
|
|
When building ExprTk there are a number of defines that will enable or
|
|
disable certain features and capabilities. The defines can either be
|
|
part of a compiler command line switch or scoped around the include to
|
|
the ExprTk header. The defines are as follows:
|
|
|
|
(01) exprtk_enable_debugging
|
|
(02) exprtk_disable_cardinal_pow_optimisation
|
|
(03) exprtk_disable_comments
|
|
(04) exprtk_disable_break_continue
|
|
(05) exprtk_disable_sc_andor
|
|
(06) exprtk_disable_return_statement
|
|
(07) exprtk_disable_enhanced_features
|
|
(08) exprtk_disable_string_capabilities
|
|
(09) exprtk_disable_superscalar_unroll
|
|
(10) exprtk_disable_rtl_io
|
|
(11) exprtk_disable_rtl_io_file
|
|
(12) exprtk_disable_rtl_vecops
|
|
(13) exprtk_disable_caseinsensitivity
|
|
(14) exprtk_enable_range_runtime_checks
|
|
|
|
(01) exprtk_enable_debugging
|
|
This define will enable printing of debug information to stdout during
|
|
the compilation process.
|
|
|
|
(02) exprtk_disable_cardinal_pow_optimisation
|
|
This define will disable the optimisation invoked when constant
|
|
integers are used as powers in exponentiation expressions (eg: x^7).
|
|
|
|
(03) exprtk_disable_comments
|
|
This define will disable the ability for expressions to have comments.
|
|
Expressions that have comments when parsed with a build that has this
|
|
option, will result in a compilation failure.
|
|
|
|
(04) exprtk_disable_break_continue
|
|
This define will disable the loop-wise 'break' and 'continue'
|
|
capabilities. Any expression that contains those keywords will result
|
|
in a compilation failure.
|
|
|
|
(05) exprtk_disable_sc_andor
|
|
This define will disable the short-circuit '&' (and) and '|' (or)
|
|
operators
|
|
|
|
(06) exprtk_disable_return_statement
|
|
This define will disable use of return statements within expressions.
|
|
|
|
(07) exprtk_disable_enhanced_features
|
|
This define will disable all enhanced features such as strength
|
|
reduction and special function optimisations and expression specific
|
|
type instantiations. This feature will reduce compilation times and
|
|
binary sizes but will also result in massive performance degradation
|
|
of expression evaluations.
|
|
|
|
(08) exprtk_disable_string_capabilities
|
|
This define will disable all string processing capabilities. Any
|
|
expression that contains a string or string related syntax will result
|
|
in a compilation failure.
|
|
|
|
(09) exprtk_disable_superscalar_unroll
|
|
This define will set the loop unroll batch size to 4 operations per
|
|
loop instead of the default 8 operations. This define is used in
|
|
operations that involve vectors and aggregations over vectors. When
|
|
targeting non-superscalar architectures, it may be recommended to
|
|
build using this particular option if efficiency of evaluations is of
|
|
concern.
|
|
|
|
(10) exprtk_disable_rtl_io
|
|
This define will disable all of basic IO RTL package features. When
|
|
present, any attempt to register the basic IO RTL package with a given
|
|
symbol table will fail causing a compilation error.
|
|
|
|
(11) exprtk_disable_rtl_io_file
|
|
This define will disable the file I/O RTL package features. When
|
|
present, any attempts to register the file I/O package with a given
|
|
symbol table will fail causing a compilation error.
|
|
|
|
(12) exprtk_disable_rtl_vecops
|
|
This define will disable the extended vector operations RTL package
|
|
features. When present, any attempts to register the vector operations
|
|
package with a given symbol table will fail causing a compilation
|
|
error.
|
|
|
|
(13) exprtk_disable_caseinsensitivity
|
|
This define will disable case-insensitivity when matching variables
|
|
and functions. Furthermore all reserved and keywords will only be
|
|
acknowledged when in all lower-case.
|
|
|
|
(14) exprtk_enable_range_runtime_checks
|
|
This define will enable run-time checks pertaining to vector indexing
|
|
operations used in any of the vector-to-vector and vector-to-scalar
|
|
operations.
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 29 - FILES]
|
|
The source distribution of ExprTk is comprised of the following set of
|
|
files:
|
|
|
|
(00) Makefile
|
|
(01) readme.txt
|
|
(02) exprtk.hpp
|
|
(03) exprtk_test.cpp
|
|
(04) exprtk_benchmark.cpp
|
|
(05) exprtk_simple_example_01.cpp
|
|
(06) exprtk_simple_example_02.cpp
|
|
(07) exprtk_simple_example_03.cpp
|
|
(08) exprtk_simple_example_04.cpp
|
|
(09) exprtk_simple_example_05.cpp
|
|
(10) exprtk_simple_example_06.cpp
|
|
(11) exprtk_simple_example_07.cpp
|
|
(12) exprtk_simple_example_08.cpp
|
|
(13) exprtk_simple_example_09.cpp
|
|
(14) exprtk_simple_example_10.cpp
|
|
(15) exprtk_simple_example_11.cpp
|
|
(16) exprtk_simple_example_12.cpp
|
|
(17) exprtk_simple_example_13.cpp
|
|
(18) exprtk_simple_example_14.cpp
|
|
(19) exprtk_simple_example_15.cpp
|
|
(20) exprtk_simple_example_16.cpp
|
|
(21) exprtk_simple_example_17.cpp
|
|
(22) exprtk_simple_example_18.cpp
|
|
(23) exprtk_simple_example_19.cpp
|
|
(24) exprtk_simple_example_20.cpp
|
|
(25) exprtk_simple_example_21.cpp
|
|
(26) exprtk_simple_example_22.cpp
|
|
(27) exprtk_simple_example_23.cpp
|
|
(28) exprtk_simple_example_24.cpp
|
|
|
|
|
|
Details for each of the above examples can be found here:
|
|
|
|
https://www.partow.net/programming/exprtk/index.html#examples
|
|
|
|
|
|
Various extended and advanced examples using ExprTk are available
|
|
via the following:
|
|
|
|
(00) exprtk_american_option_binomial_model.cpp
|
|
(01) exprtk_binomial_coefficient.cpp
|
|
(02) exprtk_bsm_benchmark.cpp
|
|
(03) exprtk_calc.cpp
|
|
(04) exprtk_collatz.cpp
|
|
(05) exprtk_compilation_timeout.cpp
|
|
(06) exprtk_degree_trigonometry_example.cpp
|
|
(07) exprtk_exprgen.cpp
|
|
(08) exprtk_extract_dependents.cpp
|
|
(09) exprtk_e_10kdigits.cpp
|
|
(10) exprtk_factorize_fermat.cpp
|
|
(11) exprtk_factorize_pollard.cpp
|
|
(12) exprtk_fizzbuzz.cpp
|
|
(13) exprtk_funcall_benchmark.cpp
|
|
(14) exprtk_game_of_life.cpp
|
|
(15) exprtk_gcd.cpp
|
|
(16) exprtk_gnuplot.cpp
|
|
(17) exprtk_gnuplot_multi.cpp
|
|
(18) exprtk_groups_examples.cpp
|
|
(19) exprtk_immutable_symbol_table_example.cpp
|
|
(20) exprtk_import_packages.cpp
|
|
(21) exprtk_instruction_primer.cpp
|
|
(22) exprtk_jump_diffusion_process.cpp
|
|
(23) exprtk_loop_timeout_rtc.cpp
|
|
(24) exprtk_magic_square.cpp
|
|
(25) exprtk_mandelbrot.cpp
|
|
(26) exprtk_max_subarray_sum.cpp
|
|
(27) exprtk_maze_generator.cpp
|
|
(28) exprtk_miller_rabin_primality_test.cpp
|
|
(29) exprtk_montecarlo_e.cpp
|
|
(30) exprtk_montecarlo_option_pricing_model.cpp
|
|
(31) exprtk_montecarlo_pi.cpp
|
|
(32) exprtk_naive_primes.cpp
|
|
(33) exprtk_normal_random_marsaglia_method.cpp
|
|
(34) exprtk_nqueens_problem.cpp
|
|
(35) exprtk_nthroot_bisection.cpp
|
|
(36) exprtk_ornstein_uhlenbeck_process.cpp
|
|
(37) exprtk_pascals_triangle.cpp
|
|
(38) exprtk_pi_10kdigits.cpp
|
|
(39) exprtk_prime_sieve.cpp
|
|
(40) exprtk_prime_sieve_vectorized.cpp
|
|
(41) exprtk_pythagorean_triples.cpp
|
|
(42) exprtk_recursive_fibonacci.cpp
|
|
(43) exprtk_repl.cpp
|
|
(44) exprtk_riddle.cpp
|
|
(45) exprtk_rtc_overhead.cpp
|
|
(46) exprtk_sudoku_solver.cpp
|
|
(47) exprtk_sumofprimes.cpp
|
|
(48) exprtk_symtab_functions.cpp
|
|
(49) exprtk_testgen.cpp
|
|
(50) exprtk_tower_of_hanoi.cpp
|
|
(51) exprtk_truthtable_gen.cpp
|
|
(52) exprtk_vectorized_binomial_model.cpp
|
|
(53) exprtk_vectornorm.cpp
|
|
(54) exprtk_vector_benchmark.cpp
|
|
(55) exprtk_vector_benchmark_multithreaded.cpp
|
|
(56) exprtk_vector_resize_example.cpp
|
|
(57) exprtk_vector_resize_inline_example.cpp
|
|
(58) exprtk_wiener_process_pi.cpp
|
|
|
|
|
|
Details for each of the above examples can be found here:
|
|
|
|
https://partow.net/programming/exprtk/index.html#variousexamples
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
[SECTION 30 - LANGUAGE STRUCTURE]
|
|
The following are the various language structures available within
|
|
ExprTk and their structural representations.
|
|
|
|
(00) If Statement
|
|
(01) Else Statement
|
|
(02) Ternary Statement
|
|
(03) While Loop
|
|
(04) Repeat Until Loop
|
|
(05) For Loop
|
|
(06) Switch Statement
|
|
(07) Multi Subexpression Statement
|
|
(08) Multi Case-Consequent Statement
|
|
(09) Variable Definition Statement
|
|
(10) Vector Definition Statement
|
|
(11) String Definition Statement
|
|
(12) Range Statement
|
|
(13) Return Statement
|
|
|
|
|
|
(00) - If Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [if] ---> [(] ---> [condition] -+-> [,] -+ |
|
|
| | | |
|
|
| +---------------<---------------+ | |
|
|
| | | |
|
|
| | +------------------<------------------+ |
|
|
| | | |
|
|
| | +--> [consequent] ---> [,] ---> [alternative] ---> [)] |
|
|
| | |
|
|
| +--> [)] --+-> [{] ---> [expression*] ---> [}] --+ |
|
|
| | | |
|
|
| | +---------<----------+ |
|
|
| +----<-----+ | |
|
|
| | v |
|
|
| +--> [consequent] --> [;] -{*}-> [else-statement] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(01) - Else Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [else] -+-> [alternative] ---> [;] |
|
|
| | |
|
|
| +--> [{] ---> [expression*] ---> [}] |
|
|
| | |
|
|
| +--> [if-statement] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(02) - Ternary Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [condition] ---> [?] ---> [consequent] ---> [:] --+ |
|
|
| | |
|
|
| +------------------------<------------------------+ |
|
|
| | |
|
|
| +--> [alternative] --> [;] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(03) - While Loop
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [while] ---> [(] ---> [condition] ---> [)] ---+ |
|
|
| | |
|
|
| +----------------------<----------------------+ |
|
|
| | |
|
|
| +--> [{] ---> [expression*] ---> [}] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(04) - Repeat Until Loop
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [repeat] ---> [expression*] ---+ |
|
|
| | |
|
|
| +--------------<---------------+ |
|
|
| | |
|
|
| +--> [until] ---> [(] ---> [condition] --->[)] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(05) - For Loop
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [for] ---> [(] -+-> [initialise expression] --+--+ |
|
|
| | | | |
|
|
| +------------->---------------+ v |
|
|
| | |
|
|
| +-----------------------<------------------------+ |
|
|
| | |
|
|
| +--> [;] -+-> [condition] -+-> [;] ---+ |
|
|
| | | | |
|
|
| +------->--------+ v |
|
|
| | |
|
|
| +------------------<---------+--------+ |
|
|
| | | |
|
|
| +--> [increment expression] -+-> [)] --+ |
|
|
| | |
|
|
| +------------------<-------------------+ |
|
|
| | |
|
|
| +--> [{] ---> [expression*] ---> [}] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(06) - Switch Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [switch] ---> [{] ---+ |
|
|
| | |
|
|
| +---------<----------+-----------<-----------+ |
|
|
| | | |
|
|
| +--> [case] ---> [condition] ---> [:] ---+ | |
|
|
| | | |
|
|
| +-------------------<--------------------+ | |
|
|
| | | |
|
|
| +--> [consequent] ---> [;] --------->--------+ |
|
|
| | | |
|
|
| | | |
|
|
| +--> [default] ---> [consequent] ---> [;] ---+ |
|
|
| | | |
|
|
| +---------------------<----------------------+ |
|
|
| | |
|
|
| +--> [}] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(07) - Multi Subexpression Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| +--------------<---------------+ |
|
|
| | | |
|
|
| [~] ---> [{\(] -+-> [expression] -+-> [;\,] ---+ |
|
|
| | |
|
|
| +----------------<----------------+ |
|
|
| | |
|
|
| +--> [}\)] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(08) - Multi Case-Consequent Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [[*]] ---> [{] ---+ |
|
|
| | |
|
|
| +--------<--------+--------------<----------+ |
|
|
| | | |
|
|
| +--> [case] ---> [condition] ---> [:] ---+ | |
|
|
| | | |
|
|
| +-------------------<--------------------+ | |
|
|
| | | |
|
|
| +--> [consequent] ---> [;] ---+------>------+ |
|
|
| | |
|
|
| +--> [}] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(09) - Variable Definition Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [var] ---> [symbol] -+-> [:=] -+-> [expression] -+-> [;] |
|
|
| | | | |
|
|
| | +-----> [{}] -->--+ |
|
|
| | | |
|
|
| +------------->-------------+ |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(10) - Vector Definition Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [var] ---> [symbol] ---> [[] ---> [constant] ---> []] --+ |
|
|
| | |
|
|
| +---------------------------<---------------------------+ |
|
|
| | |
|
|
| | +--------->---------+ |
|
|
| | | | |
|
|
| +--> [:=] ---> [{] -+-+-> [expression] -+-> [}] ---> [;] |
|
|
| | | |
|
|
| +--<--- [,] <-----+ |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(11) - String Definition Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [var] --> [symbol] --> [:=] --> [str-expression] ---> [;] |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(12) - Range Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| +-------->--------+ |
|
|
| | | |
|
|
| [[] -+-> [expression] -+-> [:] -+-> [expression] -+--> []] |
|
|
| | | |
|
|
| +-------->--------+ |
|
|
| |
|
|
+-------------------------------------------------------------+
|
|
|
|
|
|
(13) - Return Statement
|
|
+-------------------------------------------------------------+
|
|
| |
|
|
| [return] ---> [[] -+-> [expression] -+-> []] ---> [;] |
|
|
| | | |
|
|
| +--<--- [,] <-----+ |
|
|
| |
|
|
+-------------------------------------------------------------+
|