C++ Mathematical Expression Toolkit Library Documentation (ExprTk) (original) (raw)
0000 | C++ Mathematical Expression Toolkit Library Documentation
0001 |
0002 | Section 00 - Introduction
0003 | Section 01 - Capabilities
0004 | Section 02 - Example Expressions
0005 | Section 03 - Copyright Notice
0006 | Section 04 - Downloads & Updates
0007 | Section 05 - Installation
0008 | Section 06 - Compilation
0009 | Section 07 - Compiler Compatibility
0010 | Section 08 - Built-In Operations & Functions
0011 | Section 09 - Fundamental Types
0012 | Section 10 - Components
0013 | Section 11 - Compilation Options
0014 | Section 12 - Expression Structures
0015 | Section 13 - Variable, Vector & String Definition
0016 | Section 14 - Vector Processing
0017 | Section 15 - User Defined Functions
0018 | Section 16 - Expression Dependents
0019 | Section 17 - Hierarchies Of Symbol Tables
0020 | Section 18 - Unknown Unknowns
0021 | Section 19 - Enabling & Disabling Features
0022 | Section 20 - Expression Return Values
0023 | Section 21 - Compilation Errors
0024 | Section 22 - Runtime Library Packages
0025 | Section 23 - Helpers & Utils
0026 | Section 24 - Runtime Checks
0027 | Section 25 - Benchmarking
0028 | Section 26 - Exprtk Notes
0029 | Section 27 - Simple Exprtk Example
0030 | Section 28 - Build Options
0031 | Section 29 - Files
0032 | Section 30 - Language Structure
0033 |
0034 |
0035 | [SECTION 00 - INTRODUCTION]
0036 | The C++ Mathematical Expression Toolkit Library (ExprTk) is a simple
0037 | to use, easy to integrate and extremely efficient run-time
0038 | mathematical expression parsing and evaluation engine. The parsing
0039 | engine supports numerous forms of functional and logic processing
0040 | semantics and is easily extensible.
0041 |
0042 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0043 |
0044 | [SECTION 01 - CAPABILITIES]
0045 | The ExprTk expression evaluator supports the following fundamental
0046 | arithmetic operations, functions and processes:
0047 |
0048 | (00) Types: Scalar, Vector, String
0049 |
0050 | (01) Basic operators: +, -, , /, %, ^
0051 |
0052 | (02) Assignment: :=, +=, -=, =, /=, %=
0053 |
0054 | (03) Equalities &
0055 | Inequalities: =, ==, <>, !=, <, <=, >, >=
0056 |
0057 | (04) Logic operators: and, mand, mor, nand, nor, not, or, shl, shr,
0058 | xnor, xor, true, false
0059 |
0060 | (05) Functions: abs, avg, ceil, clamp, equal, erf, erfc, exp,
0061 | expm1, floor, frac, log, log10, log1p, log2,
0062 | logn, max, min, mul, ncdf, not_equal, root,
0063 | round, roundn, sgn, sqrt, sum, swap, trunc
0064 |
0065 | (06) Trigonometry: acos, acosh, asin, asinh, atan, atanh, atan2,
0066 | cos, cosh, cot, csc, sec, sin, sinc, sinh,
0067 | tan, tanh, hypot, rad2deg, deg2grad, deg2rad,
0068 | grad2deg
0069 |
0070 | (07) Control
0071 | structures: if-then-else, ternary conditional, switch-case,
0072 | return-statement
0073 |
0074 | (08) Loop statements: while, for, repeat-until, break, continue
0075 |
0076 | (09) String
0077 | processing: in, like, ilike, concatenation
0078 |
0079 | (10) Optimisations: constant-folding, simple strength reduction and
0080 | dead code elimination
0081 |
0082 | (11) Runtime checks: vector bounds, string bounds, loop iteration,
0083 | execution-time bounds and compilation process
0084 | checkpointing, assert statements
0085 |
0086 | (12) Calculus: numerical integration and differentiation
0087 |
0088 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0089 |
0090 | [SECTION 02 - EXAMPLE EXPRESSIONS]
0091 | The following is a short listing of infix format based mathematical
0092 | expressions that can be parsed and evaluated using the ExprTk library.
0093 |
0094 | (01) sqrt(1 - (3 / x^2))
0095 | (02) clamp(-1, sin(2 * pi * x) + cos(y / 2 * pi), +1)
0096 | (03) sin(2.34e-3 * x)
0097 | (04) if(((x[2] + 2) == 3) and ((y + 5) <= 9),1 + w, 2 / z)
0098 | (05) inrange(-2,m,+2) == if(({-2 <= m} and [m <= +2]),1,0)
0099 | (06) ({1/1}*[1/2]+(1/3))-{1/4}^[1/5]+(1/6)-({1/7}+[1/8]*(1/9))
0100 | (07) a * exp(2.2 / 3.3 * t) + c
0101 | (08) z := x + sin(2.567 * pi / y)
0102 | (09) u := 2.123 * {pi * z} / (w := x + cos(y / pi))
0103 | (10) 2x + 3y + 4z + 5w == 2 * x + 3 * y + 4 * z + 5 * w
0104 | (11) 3(x + y) / 2.9 + 1.234e+12 == 3 * (x + y) / 2.9 + 1.234e+12
0105 | (12) (x + y)3.3 + 1 / 4.5 == [x + y] * 3.3 + 1 / 4.5
0106 | (13) (x + y[i])z + 1.1 / 2.7 == (x + y[i]) * z + 1.1 / 2.7
0107 | (14) (sin(x / pi) cos(2y) + 1) == (sin(x / pi) * cos(2 * y) + 1)
0108 | (15) 75x^17 + 25.1x^5 - 35x^4 - 15.2x^3 + 40x^2 - 15.3x + 1
0109 | (16) (avg(x,y) <= x + y ? x - y : x * y) + 2.345 * pi / x
0110 | (17) while (x <= 100) { x -= 1; }
0111 | (18) x <= 'abc123' and (y in 'AString') or ('1x2y3z' != z)
0112 | (19) ((x + 'abc') like '*123*') or ('a123b' ilike y)
0113 | (20) sgn(+1.2^3.4z / -5.6y) <= {-7.8^9 / -10.11x }
0114 |
0115 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0116 |
0117 | [SECTION 03 - COPYRIGHT NOTICE]
0118 | Free use of the C++ Mathematical Expression Toolkit Library is
0119 | permitted under the guidelines and in accordance with the most current
0120 | version of the MIT License.
0121 |
0122 | (1) https://www.opensource.org/licenses/MIT
0123 | (2) SPDX-License-Identifier: MIT
0124 | (3) SPDX-FileCopyrightText : Copyright (C) 1999-2024 Arash Partow
0125 |
0126 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0127 |
0128 | [SECTION 04 - DOWNLOADS & UPDATES]
0129 | The most recent version of the C++ Mathematical Expression Toolkit
0130 | Library including all updates and tests can be found at the following
0131 | locations:
0132 |
0133 | (1) Download: https://www.partow.net/programming/exprtk/index.html
0134 | (2) Mirror Repository: https://github.com/ArashPartow/exprtk
0135 | https://github.com/ArashPartow/exprtk-extras
0136 |
0137 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0138 |
0139 | [SECTION 05 - INSTALLATION]
0140 | The header file exprtk.hpp should be placed in a project or system
0141 | include path (e.g: /usr/include/).
0142 |
0143 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0144 |
0145 | [SECTION 06 - COMPILATION]
0146 | The ExprTk package contains the ExprTk header, a set of simple
0147 | examples and a benchmark and unit test suite. The following is a list
0148 | of commands to build the various components:
0149 |
0150 | (a) For a complete build: make clean all
0151 | (b) For a PGO build: make clean pgo
0152 | (c) To strip executables: make strip_bin
0153 | (d) Execute valgrind check: make valgrind_check
0154 |
0155 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0156 |
0157 | [SECTION 07 - COMPILER COMPATIBILITY]
0158 | ExprTk has been built error and warning free using the following set
0159 | of C++ compilers:
0160 |
0161 | (*) GNU Compiler Collection (3.5+)
0162 | (*) Clang/LLVM (1.1+)
0163 | (*) Microsoft Visual Studio C++ Compiler (7.1+)
0164 | (*) Intel C++ Compiler (8.x+)
0165 | (*) AMD Optimizing C++ Compiler (1.2+)
0166 | (*) Nvidia C++ Compiler (19.x+)
0167 | (*) PGI C++ (10.x+)
0168 | (*) Circle C++ (circa: b81c37d2bb227c)
0169 | (*) IBM XL C/C++ (9.x+)
0170 | (*) C++ Builder (XE4+)
0171 |
0172 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0173 |
0174 | [SECTION 08 - BUILT-IN OPERATIONS & FUNCTIONS]
0175 |
0176 | (0) Arithmetic & Assignment Operators
0177 | +----------+---------------------------------------------------------+
0178 | | OPERATOR | DEFINITION |
0179 | +----------+---------------------------------------------------------+
0180 | | + | Addition between x and y. (eg: x + y) |
0181 | +----------+---------------------------------------------------------+
0182 | | - | Subtraction between x and y. (eg: x - y) |
0183 | +----------+---------------------------------------------------------+
0184 | | * | Multiplication between x and y. (eg: x * y) |
0185 | +----------+---------------------------------------------------------+
0186 | | / | Division between x and y. (eg: x / y) |
0187 | +----------+---------------------------------------------------------+
0188 | | % | Modulus of x with respect to y. (eg: x % y) |
0189 | +----------+---------------------------------------------------------+
0190 | | ^ | x to the power of y. (eg: x ^ y) |
0191 | +----------+---------------------------------------------------------+
0192 | | := | Assign the value of x to y. Where y is either a variable|
0193 | | | or vector type. (eg: y := x) |
0194 | +----------+---------------------------------------------------------+
0195 | | += | Increment x by the value of the expression on the right |
0196 | | | hand side. Where x is either a variable or vector type. |
0197 | | | (eg: x += abs(y - z)) |
0198 | +----------+---------------------------------------------------------+
0199 | | -= | Decrement x by the value of the expression on the right |
0200 | | | hand side. Where x is either a variable or vector type. |
0201 | | | (eg: x[i] -= abs(y + z)) |
0202 | +----------+---------------------------------------------------------+
0203 | | *= | Assign the multiplication of x by the value of the |
0204 | | | expression on the righthand side to x. Where x is either|
0205 | | | a variable or vector type. |
0206 | | | (eg: x *= abs(y / z)) |
0207 | +----------+---------------------------------------------------------+
0208 | | /= | Assign the division of x by the value of the expression |
0209 | | | on the right-hand side to x. Where x is either a |
0210 | | | variable or vector type. (eg: x[i + j] /= abs(y * z)) |
0211 | +----------+---------------------------------------------------------+
0212 | | %= | Assign x modulo the value of the expression on the right|
0213 | | | hand side to x. Where x is either a variable or vector |
0214 | | | type. (eg: x[2] %= y ^ 2) |
0215 | +----------+---------------------------------------------------------+
0216 |
0217 | (1) Equalities & Inequalities
0218 | +----------+---------------------------------------------------------+
0219 | | OPERATOR | DEFINITION |
0220 | +----------+---------------------------------------------------------+
0221 | | == or = | True only if x is strictly equal to y. (eg: x == y) |
0222 | +----------+---------------------------------------------------------+
0223 | | <> or != | True only if x does not equal y. (eg: x <> y or x != y) |
0224 | +----------+---------------------------------------------------------+
0225 | | < | True only if x is less than y. (eg: x < y) |
0226 | +----------+---------------------------------------------------------+
0227 | | <= | True only if x is less than or equal to y. (eg: x <= y) |
0228 | +----------+---------------------------------------------------------+
0229 | | > | True only if x is greater than y. (eg: x > y) |
0230 | +----------+---------------------------------------------------------+
0231 | | >= | True only if x greater than or equal to y. (eg: x >= y) |
0232 | +----------+---------------------------------------------------------+
0233 |
0234 | (2) Boolean Operations
0235 | +----------+---------------------------------------------------------+
0236 | | OPERATOR | DEFINITION |
0237 | +----------+---------------------------------------------------------+
0238 | | true | True state or any value other than zero (typically 1). |
0239 | +----------+---------------------------------------------------------+
0240 | | false | False state, value of exactly zero. |
0241 | +----------+---------------------------------------------------------+
0242 | | and | Logical AND, True only if x and y are both true. |
0243 | | | (eg: x and y) |
0244 | +----------+---------------------------------------------------------+
0245 | | mand | Multi-input logical AND, True only if all inputs are |
0246 | | | true. Left to right short-circuiting of expressions. |
0247 | | | (eg: mand(x > y, z < w, u or v, w and x)) |
0248 | +----------+---------------------------------------------------------+
0249 | | mor | Multi-input logical OR, True if at least one of the |
0250 | | | inputs are true. Left to right short-circuiting of |
0251 | | | expressions. (eg: mor(x > y, z < w, u or v, w and x)) |
0252 | +----------+---------------------------------------------------------+
0253 | | nand | Logical NAND, True only if either x or y is false. |
0254 | | | (eg: x nand y) |
0255 | +----------+---------------------------------------------------------+
0256 | | nor | Logical NOR, True only if the result of x or y is false |
0257 | | | (eg: x nor y) |
0258 | +----------+---------------------------------------------------------+
0259 | | not | Logical NOT, Negate the logical sense of the input. |
0260 | | | (eg: not(x and y) == x nand y) |
0261 | +----------+---------------------------------------------------------+
0262 | | or | Logical OR, True if either x or y is true. (eg: x or y) |
0263 | +----------+---------------------------------------------------------+
0264 | | xor | Logical XOR, True only if the logical states of x and y |
0265 | | | differ. (eg: x xor y) |
0266 | +----------+---------------------------------------------------------+
0267 | | xnor | Logical XNOR, True iff the biconditional of x and y is |
0268 | | | satisfied. (eg: x xnor y) |
0269 | +----------+---------------------------------------------------------+
0270 | | & | Similar to AND but with left to right expression short |
0271 | | | circuiting optimisation. (eg: (x & y) == (y and x)) |
0272 | +----------+---------------------------------------------------------+
0273 | | | | Similar to OR but with left to right expression short |
0274 | | | circuiting optimisation. (eg: (x | y) == (y or x)) |
0275 | +----------+---------------------------------------------------------+
0276 |
0277 | (3) General Purpose Functions
0278 | +----------+---------------------------------------------------------+
0279 | | FUNCTION | DEFINITION |
0280 | +----------+---------------------------------------------------------+
0281 | | abs | Absolute value of x. (eg: abs(x)) |
0282 | +----------+---------------------------------------------------------+
0283 | | avg | Average of all the inputs. |
0284 | | | (eg: avg(x,y,z,w,u,v) == (x + y + z + w + u + v) / 6) |
0285 | +----------+---------------------------------------------------------+
0286 | | ceil | Smallest integer that is greater than or equal to x. |
0287 | +----------+---------------------------------------------------------+
0288 | | clamp | Clamp x in range between r0 and r1, where r0 < r1. |
0289 | | | (eg: clamp(r0,x,r1)) |
0290 | +----------+---------------------------------------------------------+
0291 | | equal | Equality test between x and y using normalised epsilon |
0292 | +----------+---------------------------------------------------------+
0293 | | erf | Error function of x. (eg: erf(x)) |
0294 | +----------+---------------------------------------------------------+
0295 | | erfc | Complimentary error function of x. (eg: erfc(x)) |
0296 | +----------+---------------------------------------------------------+
0297 | | exp | e to the power of x. (eg: exp(x)) |
0298 | +----------+---------------------------------------------------------+
0299 | | expm1 | e to the power of x minus 1, where x is very small. |
0300 | | | (eg: expm1(x)) |
0301 | +----------+---------------------------------------------------------+
0302 | | floor | Largest integer that is less than or equal to x. |
0303 | | | (eg: floor(x)) |
0304 | +----------+---------------------------------------------------------+
0305 | | frac | Fractional portion of x. (eg: frac(x)) |
0306 | +----------+---------------------------------------------------------+
0307 | | hypot | Hypotenuse of x and y (eg: hypot(x,y) = sqrt(x*x + y*y))|
0308 | +----------+---------------------------------------------------------+
0309 | | iclamp | Inverse-clamp x outside of the range r0 and r1. Where |
0310 | | | r0 < r1. If x is within the range it will snap to the |
0311 | | | closest bound. (eg: iclamp(r0,x,r1) |
0312 | +----------+---------------------------------------------------------+
0313 | | inrange | In-range returns 'true' when x is within the range r0 |
0314 | | | and r1. Where r0 < r1. (eg: inrange(r0,x,r1) |
0315 | +----------+---------------------------------------------------------+
0316 | | log | Natural logarithm of x. (eg: log(x)) |
0317 | +----------+---------------------------------------------------------+
0318 | | log10 | Base 10 logarithm of x. (eg: log10(x)) |
0319 | +----------+---------------------------------------------------------+
0320 | | log1p | Natural logarithm of 1 + x, where x is very small. |
0321 | | | (eg: log1p(x)) |
0322 | +----------+---------------------------------------------------------+
0323 | | log2 | Base 2 logarithm of x. (eg: log2(x)) |
0324 | +----------+---------------------------------------------------------+
0325 | | logn | Base N logarithm of x. where n is a positive integer. |
0326 | | | (eg: logn(x,8)) |
0327 | +----------+---------------------------------------------------------+
0328 | | max | Largest value of all the inputs. (eg: max(x,y,z,w,u,v)) |
0329 | +----------+---------------------------------------------------------+
0330 | | min | Smallest value of all the inputs. (eg: min(x,y,z,w,u)) |
0331 | +----------+---------------------------------------------------------+
0332 | | mul | Product of all the inputs. |
0333 | | | (eg: mul(x,y,z,w,u,v,t) == (x * y * z * w * u * v * t)) |
0334 | +----------+---------------------------------------------------------+
0335 | | ncdf | Normal cumulative distribution function. (eg: ncdf(x)) |
0336 | +----------+---------------------------------------------------------+
0337 | | not_equal| Not-equal test between x and y using normalised epsilon |
0338 | +----------+---------------------------------------------------------+
0339 | | pow | x to the power of y. (eg: pow(x,y) == x ^ y) |
0340 | +----------+---------------------------------------------------------+
0341 | | root | Nth-Root of x. where n is a positive integer. |
0342 | | | (eg: root(x,3) == x^(1/3)) |
0343 | +----------+---------------------------------------------------------+
0344 | | round | Round x to the nearest integer. (eg: round(x)) |
0345 | +----------+---------------------------------------------------------+
0346 | | roundn | Round x to n decimal places (eg: roundn(x,3)) |
0347 | | | where n > 0 and is an integer. |
0348 | | | (eg: roundn(1.2345678,4) == 1.2346) |
0349 | +----------+---------------------------------------------------------+
0350 | | sgn | Sign of x, -1 where x < 0, +1 where x > 0, else zero. |
0351 | | | (eg: sgn(x)) |
0352 | +----------+---------------------------------------------------------+
0353 | | sqrt | Square root of x, where x >= 0. (eg: sqrt(x)) |
0354 | +----------+---------------------------------------------------------+
0355 | | sum | Sum of all the inputs. |
0356 | | | (eg: sum(x,y,z,w,u,v,t) == (x + y + z + w + u + v + t)) |
0357 | +----------+---------------------------------------------------------+
0358 | | swap | Swap the values of the variables x and y and return the |
0359 | | <=> | current value of y. (eg: swap(x,y) or x <=> y) |
0360 | +----------+---------------------------------------------------------+
0361 | | trunc | Integer portion of x. (eg: trunc(x)) |
0362 | +----------+---------------------------------------------------------+
0363 |
0364 | (4) Trigonometry Functions
0365 | +----------+---------------------------------------------------------+
0366 | | FUNCTION | DEFINITION |
0367 | +----------+---------------------------------------------------------+
0368 | | acos | Arc cosine of x expressed in radians. Interval [-1,+1] |
0369 | | | (eg: acos(x)) |
0370 | +----------+---------------------------------------------------------+
0371 | | acosh | Inverse hyperbolic cosine of x expressed in radians. |
0372 | | | (eg: acosh(x)) |
0373 | +----------+---------------------------------------------------------+
0374 | | asin | Arc sine of x expressed in radians. Interval [-1,+1] |
0375 | | | (eg: asin(x)) |
0376 | +----------+---------------------------------------------------------+
0377 | | asinh | Inverse hyperbolic sine of x expressed in radians. |
0378 | | | (eg: asinh(x)) |
0379 | +----------+---------------------------------------------------------+
0380 | | atan | Arc tangent of x expressed in radians. Interval [-1,+1] |
0381 | | | (eg: atan(x)) |
0382 | +----------+---------------------------------------------------------+
0383 | | atan2 | Arc tangent of (x / y) expressed in radians. [-pi,+pi] |
0384 | | | eg: atan2(x,y) |
0385 | +----------+---------------------------------------------------------+
0386 | | atanh | Inverse hyperbolic tangent of x expressed in radians. |
0387 | | | (eg: atanh(x)) |
0388 | +----------+---------------------------------------------------------+
0389 | | cos | Cosine of x. (eg: cos(x)) |
0390 | +----------+---------------------------------------------------------+
0391 | | cosh | Hyperbolic cosine of x. (eg: cosh(x)) |
0392 | +----------+---------------------------------------------------------+
0393 | | cot | Cotangent of x. (eg: cot(x)) |
0394 | +----------+---------------------------------------------------------+
0395 | | csc | Cosecant of x. (eg: csc(x)) |
0396 | +----------+---------------------------------------------------------+
0397 | | sec | Secant of x. (eg: sec(x)) |
0398 | +----------+---------------------------------------------------------+
0399 | | sin | Sine of x. (eg: sin(x)) |
0400 | +----------+---------------------------------------------------------+
0401 | | sinc | Sine cardinal of x. (eg: sinc(x)) |
0402 | +----------+---------------------------------------------------------+
0403 | | sinh | Hyperbolic sine of x. (eg: sinh(x)) |
0404 | +----------+---------------------------------------------------------+
0405 | | tan | Tangent of x. (eg: tan(x)) |
0406 | +----------+---------------------------------------------------------+
0407 | | tanh | Hyperbolic tangent of x. (eg: tanh(x)) |
0408 | +----------+---------------------------------------------------------+
0409 | | deg2rad | Convert x from degrees to radians. (eg: deg2rad(x)) |
0410 | +----------+---------------------------------------------------------+
0411 | | deg2grad | Convert x from degrees to gradians. (eg: deg2grad(x)) |
0412 | +----------+---------------------------------------------------------+
0413 | | rad2deg | Convert x from radians to degrees. (eg: rad2deg(x)) |
0414 | +----------+---------------------------------------------------------+
0415 | | grad2deg | Convert x from gradians to degrees. (eg: grad2deg(x)) |
0416 | +----------+---------------------------------------------------------+
0417 |
0418 | (5) String Processing
0419 | +----------+---------------------------------------------------------+
0420 | | FUNCTION | DEFINITION |
0421 | +----------+---------------------------------------------------------+
0422 | | = , == | All common equality/inequality operators are applicable |
0423 | | !=, <> | to strings and are applied in a case sensitive manner. |
0424 | | <=, >= | In the following example x, y and z are of type string. |
0425 | | < , > | (eg: not((x <= 'AbC') and ('1x2y3z' <> y)) or (z == x) |
0426 | +----------+---------------------------------------------------------+
0427 | | in | True only if x is a substring of y. |
0428 | | | (eg: x in y or 'abc' in 'abcdefgh') |
0429 | +----------+---------------------------------------------------------+
0430 | | like | True only if the string x matches the pattern y. |
0431 | | | Available wildcard characters are '' and '?' denoting |
0432 | | | zero or more and zero or one matches respectively. |
0433 | | | (eg: x like y or 'abcdefgh' like 'a?dh') |
0434 | +----------+---------------------------------------------------------+
0435 | | ilike | True only if the string x matches the pattern y in a |
0436 | | | case insensitive manner. Available wildcard characters |
0437 | | | are '' and '?' denoting zero or more and zero or one |
0438 | | | matches respectively. |
0439 | | | (eg: x ilike y or 'a1B2c3D4e5F6g7H' ilike 'a?dh') |
0440 | +----------+---------------------------------------------------------+
0441 | | [r0:r1] | The closed interval[r0,r1] of the specified string. |
0442 | | | eg: Given a string x with a value of 'abcdefgh' then: |
0443 | | | 1. x[1:4] == 'bcde' |
0444 | | | 2. x[ :4] == x[:8 / 2] == 'abcde' |
0445 | | | 3. x[2 + 1: ] == x[3:] =='defgh' |
0446 | | | 4. x[ : ] == x[:] == 'abcdefgh' |
0447 | | | 5. x[4/2:3+1] == x[2:4] == 'cde' |
0448 | | | |
0449 | | | Note: Both r0 and r1 are assumed to be integers, where |
0450 | | | r0 <= r1. They may also be the result of an expression, |
0451 | | | in the event they have fractional components truncation |
0452 | | | shall be performed. (eg: 1.67 --> 1) |
0453 | +----------+---------------------------------------------------------+
0454 | | := | Assign the value of x to y. Where y is a mutable string |
0455 | | | or string range and x is either a string or a string |
0456 | | | range. eg: |
0457 | | | 1. y := x |
0458 | | | 2. y := 'abc' |
0459 | | | 3. y := x[:i + j] |
0460 | | | 4. y := '0123456789'[2:7] |
0461 | | | 5. y := '0123456789'[2i + 1:7] |
0462 | | | 6. y := (x := '0123456789'[2:7]) |
0463 | | | 7. y[i:j] := x |
0464 | | | 8. y[i:j] := (x + 'abcdefg'[8 / 4:5])[m:n] |
0465 | | | |
0466 | | | Note: For options 7 and 8 the shorter of the two ranges |
0467 | | | will denote the number characters that are to be copied.|
0468 | +----------+---------------------------------------------------------+
0469 | | + | Concatenation of x and y. Where x and y are strings or |
0470 | | | string ranges. eg |
0471 | | | 1. x + y |
0472 | | | 2. x + 'abc' |
0473 | | | 3. x + y[:i + j] |
0474 | | | 4. x[i:j] + y[2:3] + '0123456789'[2:7] |
0475 | | | 5. 'abc' + x + y |
0476 | | | 6. 'abc' + '1234567' |
0477 | | | 7. (x + 'a1B2c3D4' + y)[i:2j] |
0478 | +----------+---------------------------------------------------------+
0479 | | += | Append to x the value of y. Where x is a mutable string |
0480 | | | and y is either a string or a string range. eg: |
0481 | | | 1. x += y |
0482 | | | 2. x += 'abc' |
0483 | | | 3. x += y[:i + j] + 'abc' |
0484 | | | 4. x += '0123456789'[2:7] |
0485 | +----------+---------------------------------------------------------+
0486 | | <=> | Swap the values of x and y. Where x and y are mutable |
0487 | | | strings. (eg: x <=> y) |
0488 | +----------+---------------------------------------------------------+
0489 | | [] | The string size operator returns the size of the string |
0490 | | | being actioned. |
0491 | | | eg: |
0492 | | | 1. 'abc'[] == 3 |
0493 | | | 2. var max_str_length := max(s0[], s1[], s2[], s3[]) |
0494 | | | 3. ('abc' + 'd')[] == 6 |
0495 | | | 4. (('abc' + 'xyz')[1:4])[] == 4 |
0496 | +----------+---------------------------------------------------------+
0497 |
0498 | (6) Control Structures
0499 | +----------+---------------------------------------------------------+
0500 | |STRUCTURE | DEFINITION |
0501 | +----------+---------------------------------------------------------+
0502 | | if | If x is true then return y else return z. |
0503 | | | eg: |
0504 | | | 1. if (x, y, z) |
0505 | | | 2. if ((x + 1) > 2y, z + 1, w / v) |
0506 | | | 3. if (x > y) z; |
0507 | | | 4. if (x <= 2y) { z + w }; |
0508 | +----------+---------------------------------------------------------+
0509 | | if-else | The if-else/else-if statement. Subject to the condition |
0510 | | | branch the statement will return either the value of the|
0511 | | | consequent or the alternative branch. |
0512 | | | eg: |
0513 | | | 1. if (x > y) z; else w; |
0514 | | | 2. if (x > y) z; else if (w != u) v; |
0515 | | | 3. if (x < y) { z; w + 1; } else u; |
0516 | | | 4. if ((x != y) and (z > w)) |
0517 | | | { |
0518 | | | y := sin(x) / u; |
0519 | | | z := w + 1; |
0520 | | | } |
0521 | | | else if (x > (z + 1)) |
0522 | | | { |
0523 | | | w := abs (x - y) + z; |
0524 | | | u := (x + 1) > 2y ? 2u : 3u; |
0525 | | | } |
0526 | +----------+---------------------------------------------------------+
0527 | | switch | The first true case condition that is encountered will |
0528 | | | determine the result of the switch. If none of the case |
0529 | | | conditions hold true, the default action is assumed as |
0530 | | | the final return value. This is sometimes also known as |
0531 | | | a multi-way branch mechanism. |
0532 | | | eg: |
0533 | | | switch |
0534 | | | { |
0535 | | | case x > (y + z) : 2 * x / abs(y - z); |
0536 | | | case x < 3 : sin(x + y); |
0537 | | | default : 1 + x; |
0538 | | | } |
0539 | +----------+---------------------------------------------------------+
0540 | | while | The structure will repeatedly evaluate the internal |
0541 | | | statement(s) 'while' the condition is true. The final |
0542 | | | statement in the final iteration shall be used as the |
0543 | | | return value of the loop. |
0544 | | | eg: |
0545 | | | while ((x -= 1) > 0) |
0546 | | | { |
0547 | | | y := x + z; |
0548 | | | w := u + y; |
0549 | | | } |
0550 | +----------+---------------------------------------------------------+
0551 | | repeat/ | The structure will repeatedly evaluate the internal |
0552 | | until | statement(s) 'until' the condition is true. The final |
0553 | | | statement in the final iteration shall be used as the |
0554 | | | return value of the loop. |
0555 | | | eg: |
0556 | | | repeat |
0557 | | | y := x + z; |
0558 | | | w := u + y; |
0559 | | | until ((x += 1) > 100) |
0560 | +----------+---------------------------------------------------------+
0561 | | for | The structure will repeatedly evaluate the internal |
0562 | | | statement(s) while the condition is true. On each loop |
0563 | | | iteration, an 'incrementing' expression is evaluated. |
0564 | | | The conditional is mandatory whereas the initialiser |
0565 | | | and incrementing expressions are optional. |
0566 | | | eg: |
0567 | | | for (var x := 0; (x < n) and (x != y); x += 1) |
0568 | | | { |
0569 | | | y := y + x / 2 - z; |
0570 | | | w := u + y; |
0571 | | | } |
0572 | +----------+---------------------------------------------------------+
0573 | | break | Break terminates the execution of the nearest enclosed |
0574 | | break[] | loop, allowing for the execution to continue on external|
0575 | | | to the loop. The default break statement will set the |
0576 | | | return value of the loop to NaN, where as the return |
0577 | | | based form will set the value to that of the break |
0578 | | | expression. |
0579 | | | eg: |
0580 | | | while ((i += 1) < 10) |
0581 | | | { |
0582 | | | if (i < 5) |
0583 | | | j -= i + 2; |
0584 | | | else if (i % 2 == 0) |
0585 | | | break; |
0586 | | | else |
0587 | | | break[2i + 3]; |
0588 | | | } |
0589 | +----------+---------------------------------------------------------+
0590 | | continue | Continue results in the remaining portion of the nearest|
0591 | | | enclosing loop body to be skipped. |
0592 | | | eg: |
0593 | | | for (var i := 0; i < 10; i += 1) |
0594 | | | { |
0595 | | | if (i < 5) |
0596 | | | continue; |
0597 | | | j -= i + 2; |
0598 | | | } |
0599 | +----------+---------------------------------------------------------+
0600 | | return | Return immediately from within the current expression. |
0601 | | | With the option of passing back a variable number of |
0602 | | | values (scalar, vector or string). eg: |
0603 | | | 1. return [1]; |
0604 | | | 2. return [x, 'abx']; |
0605 | | | 3. return [x, x + y,'abx']; |
0606 | | | 4. return []; |
0607 | | | 5. if (x < y) |
0608 | | | return [x, x - y, 'result-set1', 123.456]; |
0609 | | | else |
0610 | | | return [y, x + y, 'result-set2']; |
0611 | +----------+---------------------------------------------------------+
0612 | | ?: | Ternary conditional statement, similar to that of the |
0613 | | | above denoted if-statement. |
0614 | | | eg: |
0615 | | | 1. x ? y : z |
0616 | | | 2. x + 1 > 2y ? z + 1 : (w / v) |
0617 | | | 3. min(x,y) > z ? (x < y + 1) ? x : y : (w * v) |
0618 | +----------+---------------------------------------------------------+
0619 | | ~ | Evaluate each sub-expression, then return as the result |
0620 | | | the value of the last sub-expression. This is sometimes |
0621 | | | known as multiple sequence point evaluation. |
0622 | | | eg: |
0623 | | | ~(i := x + 1, j := y / z, k := sin(w/u)) == (sin(w/u))) |
0624 | | | ~{i := x + 1; j := y / z; k := sin(w/u)} == (sin(w/u))) |
0625 | +----------+---------------------------------------------------------+
0626 | | [*] | Evaluate any consequent for which its case statement is |
0627 | | | true. The return value will be either zero or the result|
0628 | | | of the last consequent to have been evaluated. |
0629 | | | eg: |
0630 | | | [*] |
0631 | | | { |
0632 | | | case (x + 1) > (y - 2) : x := z / 2 + sin(y / pi); |
0633 | | | case (x + 2) < abs(y + 3) : w / 4 + min(5y,9); |
0634 | | | case (x + 3) == (y * 4) : y := abs(z / 6) + 7y; |
0635 | | | } |
0636 | +----------+---------------------------------------------------------+
0637 | | [] | The vector size operator returns the size of the vector |
0638 | | | being actioned. |
0639 | | | eg: |
0640 | | | 1. v[] |
0641 | | | 2. max_size := max(v0[],v1[],v2[],v3[]) |
0642 | +----------+---------------------------------------------------------+
0643 |
0644 | Note01: In the tables above, the symbols x, y, z, w, u and v where
0645 | appropriate may represent any of one the following:
0646 |
0647 | 1. Literal numeric/string value
0648 | 2. A variable
0649 | 3. A vector element
0650 | 4. A vector
0651 | 5. A string
0652 | 6. An expression comprised of [1], [2] or [3] (eg: 2 + x / vec[3])
0653 |
0654 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0655 |
0656 | [SECTION 09 - FUNDAMENTAL TYPES]
0657 | ExprTk supports three fundamental types which can be used freely in
0658 | expressions. The types are as follows:
0659 |
0660 | (1) Scalar
0661 | (2) Vector
0662 | (3) String
0663 |
0664 |
0665 | (1) Scalar Type
0666 | The scalar type is a singular numeric value. The underlying type is
0667 | that used to specialise the ExprTk components (float, double, long
0668 | double, MPFR et al).
0669 |
0670 |
0671 | (2) Vector Type
0672 | The vector type is a fixed size sequence of contiguous scalar values.
0673 | A vector can be indexed resulting in a scalar value. Operations
0674 | between a vector and scalar will result in a vector with a size equal
0675 | to that of the original vector, whereas operations between vectors
0676 | will result in a vector of size equal to that of the smaller of the
0677 | two. In both mentioned cases, the operations will occur element-wise.
0678 |
0679 |
0680 | (3) String Type
0681 | The string type is a variable length sequence of 8-bit chars. Strings
0682 | can be assigned and concatenated to one another, they can also be
0683 | manipulated via sub-ranges using the range definition syntax. Strings
0684 | however can not interact with scalar or vector types.
0685 |
0686 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0687 |
0688 | [SECTION 10 - COMPONENTS]
0689 | There are three primary components, that are specialised upon a given
0690 | numeric type, which make up the core of ExprTk. The components are as
0691 | follows:
0692 |
0693 | (1) Symbol Table exprtk::symbol_table
0694 | (2) Expression exprtk::expression
0695 | (3) Parser exprtk::parser
0696 |
0697 |
0698 | (1) Symbol Table
0699 | A structure that is used to store references to variables, constants
0700 | and functions that are to be used within expressions. Furthermore in
0701 | the context of composited recursive functions the symbol table can
0702 | also be thought of as a simple representation of a stack specific for
0703 | the expression(s) that reference it. The following is a list of the
0704 | types a symbol table can handle:
0705 |
0706 | (a) Numeric variables
0707 | (b) Numeric constants
0708 | (c) Numeric vector elements
0709 | (d) String variables
0710 | (e) String constants
0711 | (f) Functions
0712 | (g) Vararg functions
0713 |
0714 |
0715 | During the compilation process if an expression is found to require
0716 | any of the elements noted above, the expression's associated
0717 | symbol_table will be queried for the element and if present a
0718 | reference to the element will be embedded within the expression's AST.
0719 | This allows for the original element to be modified independently of
0720 | the expression instance and to also allow the expression to be
0721 | evaluated using the current value of the element.
0722 |
0723 | The example below demonstrates the relationship between variables,
0724 | symbol_table and expression. Note the variables are modified as they
0725 | normally would in a program, and when the expression is evaluated the
0726 | current values assigned to the variables shall be used.
0727 |
0728 | typedef exprtk::symbol_table symbol_table_t;
0729 | typedef exprtk::expression expression_t;
0730 | typedef exprtk::parser parser_t;
0731 |
0732 | double x = 0;
0733 | double y = 0;
0734 |
0735 | symbol_table_t symbol_table;
0736 | expression_t expression;
0737 | parser_t parser;
0738 |
0739 | std::string expression_string = "x * y + 3"
0740 |
0741 | symbol_table.add_variable("x",x);
0742 | symbol_table.add_variable("y",y);
0743 |
0744 | expression.register_symbol_table(symbol_table);
0745 |
0746 | parser.compile(expression_string,expression);
0747 |
0748 | x = 1.0;
0749 | y = 2.0;
0750 | expression.value(); // 1 * 2 + 3
0751 |
0752 | x = 3.7;
0753 | expression.value(); // 3.7 * 2 + 3
0754 |
0755 | y = -9.0;
0756 | expression.value(); // 3.7 * -9 + 3
0757 |
0758 | // 'x * -9 + 3' for x in range of [0,100) in steps of 0.0001
0759 | for (x = 0.0; x < 100.0; x += 0.0001)
0760 | {
0761 | expression.value(); // x * -9 + 3
0762 | }
0763 |
0764 |
0765 | Note02: Any variable reference provided to a given symbol_table
0766 | instance, must have a lifetime at least as long as the lifetime of the
0767 | symbol_table instance. In the event the variable reference is
0768 | invalidated before the symbol_table or any dependent expression
0769 | instances have been destructed, then any associated expression
0770 | evaluations or variable referencing via the symbol_table instance will
0771 | result in undefined behaviour.
0772 |
0773 | The following bit of code instantiates a symbol_table and expression
0774 | instance, then proceeds to demonstrate various ways in which
0775 | references to variables can be added to the symbol_table, and how
0776 | those references are subsequently invalidated resulting in various
0777 | forms of undefined behaviour.
0778 |
0779 | typedef exprtk::symbol_table symbol_table_t;
0780 | typedef exprtk::expression expression_t;
0781 |
0782 | symbol_table_t symbol_table;
0783 | expression_t expression;
0784 |
0785 | std::deque y {1.1, 2.2, 3.3};
0786 | std::vector z {4.4, 5.5, 6.6};
0787 | double w = new double(123.456);
0788 |
0789 | {
0790 | double x = 123.4567;
0791 | symbol_table.add_variable("x", x);
0792 | } // Reference to variable x has been invalidated
0793 |
0794 |
0795 | symbol_table.add_variable("y", y.back());
0796 |
0797 | y.pop_back(); // Reference to variable y has been invalidated
0798 |
0799 |
0800 | symbol_table.add_variable("z", z.front());
0801 |
0802 | z.erase(z.begin());
0803 | // Reference to variable z has been invalidated
0804 |
0805 | symbol_table.add_variable("w", w);
0806 |
0807 | delete w; // Reference to variable w has been invalidated
0808 |
0809 | const std::string expression_string = "x + y / z * w"
0810 |
0811 | // Compilation of expression will succeed
0812 | parser.compile(expression_string,expression);
0813 |
0814 | expression.value();
0815 | // Evaluation will result in undefined behaviour
0816 | // due to 'x' and 'w' having been destroyed.
0817 |
0818 | symbol_table.get_variable("x")->ref() = 135.791;
0819 | // Assignment will result in undefined behaviour
0820 |
0821 |
0822 | A compiled expression that references variables from a symbol_table is
0823 | dependent on that symbol_table instance and the variables it holds
0824 | being valid.
0825 |
0826 | typedef exprtk::symbol_table symbol_table_t;
0827 | typedef exprtk::expression expression_t;
0828 |
0829 | symbol_table_t symbol_table;
0830 | expression_t expression;
0831 |
0832 | double x = 123.456;
0833 |
0834 | symbol_table.add_variable("x", x);
0835 |
0836 | const std::string expression_string = "(x + 1) / 2"
0837 |
0838 | // Compilation of the expression will succeed
0839 | parser.compile(expression_string,expression);
0840 |
0841 | // Clear all variables from symbol_table
0842 | symbol_table.clear();
0843 |
0844 | expression.value();
0845 | // Evaluation will result in undefined behaviour
0846 | // because the reference to 'x' having been destroyed
0847 | // during the clearing of the symbol_table
0848 |
0849 |
0850 | In the above example, an expression is compiled that references
0851 | variable "x". As part of the compilation process the node holding the
0852 | variable "x" is obtained from the symbol_table and embedded in the AST
0853 | of the expression - in short the expression is now referencing the
0854 | node that holds the variable "x". The following diagram depicts the
0855 | dependencies between the variable x, the symbol table and the
0856 | expression:
0857 |
0858 | +--[Symbol Table]--+
0859 | | |
0860 | | +- ------+ |
0861 | | | x-node | |
0862 | | +-----A--+ | +--[Expression]--+
0863 | +---|---|----------+ | +---------+ |
0864 | v | | | A.S.T | |
0865 | | +--------<--------[.] | |
0866 | +-----+ | +---------+ |
0867 | | +----------------+
0868 | +-v-[variable]---+
0869 | | x: 123.456 |
0870 | +----------------+
0871 |
0872 |
0873 | When the clear method is called on the symbol table the X-Node is
0874 | destroyed, so now the expression is referencing a node that has been
0875 | destroyed. From this point onwards any attempts to reference the
0876 | expression instance will result in undefined behaviour. Simply put the
0877 | above example violates the requirement that the lifetime of any
0878 | objects referenced by expressions should exceed the lifetime of the
0879 | expression instance.
0880 |
0881 | typedef exprtk::symbol_table symbol_table_t;
0882 | typedef exprtk::expression expression_t;
0883 |
0884 | symbol_table_t symbol_table;
0885 | expression_t expression;
0886 |
0887 | double x = 123.456;
0888 |
0889 | symbol_table.add_variable("x", x);
0890 |
0891 | const std::string expression_string = "(x + 1) / 2"
0892 |
0893 | // Compilation of the expression will succeed
0894 | parser.compile(expression_string,expression);
0895 |
0896 | expression.value();
0897 |
0898 | // Release the expression and its dependents
0899 | expression.release();
0900 |
0901 | // Clear all variables from symbol_table
0902 | symbol_table.clear();
0903 |
0904 | expression.value();
0905 | // Will return null_node value of NaN
0906 |
0907 |
0908 | In the above example the expression is released before the associated
0909 | symbol_table is cleared of its variables, which resolves the undefined
0910 | behaviour issue noted in the previous example.
0911 |
0912 | Note03: It is possible to register multiple symbol_tables with a
0913 | single expression object. In the event an expression has multiple
0914 | symbol tables, and where there exists conflicts between symbols, the
0915 | compilation stage will resolve the conflicts based on the order of
0916 | registration of the symbol_tables to the expression. For a more
0917 | expansive discussion please review section [17 - Hierarchies Of Symbol
0918 | Tables]
0919 |
0920 | typedef exprtk::symbol_table symbol_table_t;
0921 | typedef exprtk::expression expression_t;
0922 | typedef exprtk::parser parser_t;
0923 |
0924 | symbol_table_t symbol_table0;
0925 | symbol_table_t symbol_table1;
0926 |
0927 | expression_t expression;
0928 | parser_t parser;
0929 |
0930 | double x0 = 123.0;
0931 | double x1 = 678.0;
0932 |
0933 | std::string expression_string = "x + 1"
0934 |
0935 | symbol_table0.add_variable("x",x0);
0936 | symbol_table1.add_variable("x",x1);
0937 |
0938 | expression.register_symbol_table(symbol_table0);
0939 | expression.register_symbol_table(symbol_table1);
0940 |
0941 | parser.compile(expression_string,expression);
0942 |
0943 | expression.value(); // 123 + 1
0944 |
0945 |
0946 | The symbol table supports adding references to external instances of
0947 | types that can be accessed within expressions via the following
0948 | methods:
0949 |
0950 | 1. bool add_variable (const std::string& name, scalar_t& )
0951 | 2. bool add_constant (const std::string& name, const scalar_t& )
0952 | 3. bool add_stringvar (const std::string& name, std::string& )
0953 | 4. bool add_vector (const std::string& name, vector_type& )
0954 | 5. bool add_function (const std::string& name, function_t& )
0955 | 6. bool create_stringvar(const std::string& name,const std::string&)
0956 | 7. bool create_variable (const std::string& name, const T& )
0957 |
0958 |
0959 | Note04: The 'vector' type must be comprised from a contiguous array of
0960 | scalars with a size that is larger than zero. The vector type itself
0961 | can be any one of the following:
0962 |
0963 | 1. std::vector
0964 | 2. scalar_t(&v)[N]
0965 | 3. scalar_t and array size
0966 | 4. exprtk::vector_view
0967 |
0968 |
0969 | When registering a variable, vector, string or function with an
0970 | instance of a symbol_table, the call to 'add_...' may fail and return
0971 | a false result due to one or more of the following reasons:
0972 |
0973 | 1. Variable name contains invalid characters or is ill-formed
0974 | 2. Variable name conflicts with a reserved word (eg: 'while')
0975 | 3. Variable name conflicts with a previously registered variable
0976 | 4. A vector of size (length) zero is being registered
0977 | 5. A free function exceeding fifteen parameters is being registered
0978 | 6. The symbol_table instance is in an invalid state
0979 |
0980 |
0981 | Note05: The symbol_table has a method called clear, which when invoked
0982 | will clear all variables, vectors, strings and functions registered
0983 | with the symbol_table instance. If this method is to be called, then
0984 | one must make sure that all compiled expression instances that
0985 | reference variables belonging to that symbol_table instance are
0986 | released (aka call release method on expression) before calling the
0987 | clear method on the symbol_table instance, otherwise undefined
0988 | behaviours will occur.
0989 |
0990 | A further property of symbol tables is that they can be classified at
0991 | instantiation as either being mutable (by default) or immutable. This
0992 | property determines if variables, vectors or strings registered with
0993 | the symbol table can undergo modifications within expressions that
0994 | reference them. The following demonstrates construction of an
0995 | immutable symbol table instance:
0996 |
0997 | symbol_table_t immutable_symbol_table
0998 | (symbol_table_t::symtab_mutability_type::e_immutable);
0999 |
1000 |
1001 | When a symbol table, that has been constructed as being immutable, is
1002 | registered with an expression, any statements in the expression string
1003 | that modify the variables that are managed by the immutable symbol
1004 | table will result in a compilation error. The operations that trigger
1005 | the mutability constraint are the following assignment operators:
1006 |
1007 | 1. Assignment: :=
1008 | 2. Assign operation: +=, -=, =, /= , %=
1009 |
1010 | const std::string expression_str = "x += x + 123.456"
1011 |
1012 | symbol_table_t immutable_symbol_table
1013 | (symbol_table_t::symtab_mutability_type::e_immutable);
1014 |
1015 | T x = 0.0;
1016 |
1017 | immutable_symbol_table.add_variable("x" , x);
1018 |
1019 | expression_t expression;
1020 | expression.register_symbol_table(immutable_symbol_table);
1021 |
1022 | parser_t parser;
1023 |
1024 | parser.compile(expression_str, expression);
1025 | // Compile error because of assignment to variable x
1026 |
1027 |
1028 | In the above example, variable x is registered to an immutable symbol
1029 | table, making it an immutable variable within the context of any
1030 | expressions that reference it. The expression string being compiled
1031 | uses the addition assignment operator which will modify the value of
1032 | variable x. The compilation process detects this semantic violation
1033 | and proceeds to halt compilation and return the appropriate error.
1034 |
1035 | One of the main reasons for this functionality is that, one may want
1036 | the immutability properties that come with constness of a variable
1037 | such as scalars, vectors and strings, but not necessarily the
1038 | accompanying compile time const-folding optimisations, that would
1039 | result in the value of the variables being retrieved only once at
1040 | compile time, causing external updates to the variables to not be part
1041 | of the expression evaluation.
1042 |
1043 | symbol_table_t immutable_symbol_table
1044 | (symbol_table_t::symtab_mutability_type::e_immutable);
1045 |
1046 | T x = 0.0;
1047 |
1048 | const std::string expression_str = "x + (y + y)"
1049 |
1050 | immutable_symbol_table.add_variable("x" , x );
1051 | immutable_symbol_table.add_constant("y" , 123.0);
1052 |
1053 | expression_t expression;
1054 | expression.register_symbol_table(immutable_symbol_table);
1055 |
1056 | parser_t parser;
1057 | parser.compile(expression_str, expression);
1058 |
1059 | for (; x < 10.0; ++x)
1060 | {
1061 | const auto expected_value = x + (123.0 + 123.0);
1062 | const auto result_value = expression.value();
1063 | assert(expression.value() != expected_value);
1064 | }
1065 |
1066 |
1067 | In the above example, there are two variables X and Y. Where Y is a
1068 | constant and X is a normal variable. Both are registered with a symbol
1069 | table that is immutable. The expression when compiled will result in
1070 | the "(y + y)" part being const-folded at compile time to the literal
1071 | value of 246. Whereas the current value of X, being updated via the
1072 | for-loop, externally to the expression and the symbol table shall be
1073 | observable to the expression upon each evaluation.
1074 |
1075 |
1076 | (2) Expression
1077 | A structure that holds an Abstract Syntax Tree or AST for a specified
1078 | expression and is used to evaluate said expression. Evaluation of the
1079 | expression is accomplished by performing a post-order traversal of the
1080 | AST. If a compiled Expression uses variables or user defined
1081 | functions, it will have an associated Symbol Table, which will contain
1082 | references to said variables, functions or strings. An example AST
1083 | structure for the denoted expression is as follows:
1084 |
1085 | Expression: z := (x + y^-2.345) * sin(pi / min(w - 7.3,v))
1086 |
1087 | [Root]
1088 | |
1089 | [Assignment]
1090 | ________/ _____
1091 | / \
1092 | Variable(z) [Multiplication]
1093 | ____________/ ___________
1094 | / \
1095 | / [Unary-Function(sin)]
1096 | [Addition] |
1097 | ____/ ____ [Division]
1098 | / \ ___/ ___
1099 | Variable(x) [Exponentiation] / \
1100 | ______/ ______ Constant(pi) [Binary-Function(min)]
1101 | / \ ____/ ___
1102 | Variable(y) [Negation] / \
1103 | | / Variable(v)
1104 | Constant(2.345) /
1105 | /
1106 | [Subtraction]
1107 | ____/ ____
1108 | / \
1109 | Variable(w) Constant(7.3)
1110 |
1111 |
1112 | The above denoted AST shall be evaluated in the following order:
1113 |
1114 | (01) Load Variable (z) (10) Load Constant (7.3)
1115 | (02) Load Variable (x) (11) Subtraction (09 & 10)
1116 | (03) Load Variable (y) (12) Load Variable (v)
1117 | (04) Load Constant (2.345) (13) Min (11 & 12)
1118 | (05) Negation (04) (14) Division (08 & 13)
1119 | (06) Exponentiation (03 & 05) (15) Sin (14)
1120 | (07) Addition (02 & 06) (16) Multiplication (07 & 15)
1121 | (08) Load Constant (pi) (17) Assignment (01 & 16)
1122 | (09) Load Variable (w)
1123 |
1124 |
1125 | Generally an expression in ExprTk can be thought of as a free function
1126 | similar to those found in imperative languages. This form of pseudo
1127 | function will have a name, it may have a set of one or more inputs and
1128 | will return at least one value as its result. Furthermore the function
1129 | when invoked, may cause a side-effect that changes the state of the
1130 | host program.
1131 |
1132 | As an example the following is a pseudo-code definition of a free
1133 | function that performs a computation taking four inputs, modifying one
1134 | of them and returning a value based on some arbitrary calculation:
1135 |
1136 | ResultType foo(InputType x, InputType y, InputType z, InputType w)
1137 | {
1138 | w = 2 * x^y + z; // Side-Effect
1139 | return abs(x - y) / z; // Return Result
1140 | }
1141 |
1142 |
1143 | Given the above definition the following is a functionally equivalent
1144 | version using ExprTk:
1145 |
1146 | const std::string foo_str =
1147 | " w := 2 * x^y + z; "
1148 | " abs(x - y) / z; "
1149 |
1150 | T x, y, z, w;
1151 |
1152 | symbol_table_t symbol_table;
1153 | symbol_table.add_variable("x",x);
1154 | symbol_table.add_variable("y",y);
1155 | symbol_table.add_variable("z",z);
1156 | symbol_table.add_variable("w",w);
1157 |
1158 | expression_t foo;
1159 | foo.register_symbol_table(symbol_table);
1160 |
1161 | parser_t parser;
1162 | if (!parser.compile(foo_str,foo))
1163 | {
1164 | // Error in expression...
1165 | return;
1166 | }
1167 |
1168 | T result = foo.value();
1169 |
1170 |
1171 | (3) Parser
1172 | A component which takes as input a string representation of an
1173 | expression and attempts to compile said input with the result being an
1174 | instance of Expression. If an error is encountered during the
1175 | compilation process, the parser will stop compiling and return an
1176 | error status code, with a more detailed description of the error(s)
1177 | and its location within the input provided by the 'get_error'
1178 | interface.
1179 |
1180 | Note06: The exprtk::expression and exprtk::symbol_table components are
1181 | reference counted entities. Copy constructing or assigning to or from
1182 | either component will result in a shallow copy and a reference count
1183 | increment, rather than a complete replication. Furthermore the
1184 | expression and symbol_table components being Default-Constructible,
1185 | Copy-Constructible and Copy-Assignable make them compatible with
1186 | various C++ standard library containers and adaptors such as
1187 | std::vector, std::map, std::stack etc.
1188 |
1189 | The following is an example of two unique expressions, after having
1190 | been instantiated and compiled, one expression is assigned to the
1191 | other. The diagrams depict their initial and post assignment states,
1192 | including which control block each expression references and their
1193 | associated reference counts.
1194 |
1195 |
1196 | exprtk::expression e0; // constructed expression, eg: x + 1
1197 | exprtk::expression e1; // constructed expression, eg: 2z + y
1198 |
1199 | +-----[ e0 cntrl block]----+ +-----[ e1 cntrl block]-----+
1200 | | 1. Expression Node 'x+1' | | 1. Expression Node '2z+y' |
1201 | | 2. Ref Count: 1 |<-+ | 2. Ref Count: 1 |<-+
1202 | +--------------------------+ | +---------------------------+ |
1203 | | |
1204 | +--[ e0 expression]--+ | +--[ e1 expression]--+ |
1205 | | 1. Reference to ]------+ | 1. Reference to ]-------+
1206 | | e0 Control Block | | e1 Control Block |
1207 | +--------------------+ +--------------------+
1208 |
1209 |
1210 | e0 = e1; // e0 and e1 are now 2z+y
1211 |
1212 | +-----[ e1 cntrl block]-----+
1213 | | 1. Expression Node '2z+y' |
1214 | +----------->| 2. Ref Count: 2 |<----------+
1215 | | +---------------------------+ |
1216 | | |
1217 | | +--[ e0 expression]--+ +--[ e1 expression]--+ |
1218 | +---[ 1. Reference to | | 1. Reference to ]---+
1219 | | e1 Control Block | | e1 Control Block |
1220 | +--------------------+ +--------------------+
1221 |
1222 | The reason for the above complexity and restrictions of deep copies
1223 | for the expression and symbol_table components is because expressions
1224 | may include user defined variables or functions. These are embedded as
1225 | references into the expression's AST. When copying an expression, said
1226 | references need to also be copied. If the references are blindly
1227 | copied, it will then result in two or more identical expressions
1228 | utilising the exact same references for variables. This obviously is
1229 | not the default assumed scenario and will give rise to non-obvious
1230 | behaviours when using the expressions in various contexts such as
1231 | multi-threading et al.
1232 |
1233 | The prescribed method for cloning an expression is to compile it from
1234 | its string form. Doing so will allow the 'user' to properly consider
1235 | the exact source of user defined variables and functions.
1236 |
1237 | Note07: The exprtk::parser is a non-copyable and non-thread safe
1238 | component, and should only be shared via either a reference, a shared
1239 | pointer or a std::ref mechanism, and considerations relating to
1240 | synchronisation taken into account where appropriate. The parser
1241 | represents an object factory, specifically a factory of expressions,
1242 | and generally should not be instantiated solely on a per expression
1243 | compilation basis.
1244 |
1245 | The following diagram and example depicts the flow of data and
1246 | operations for compiling multiple expressions via the parser and
1247 | inserting the newly minted exprtk::expression instances into a
1248 | std::vector.
1249 |
1250 | +----[exprtk::parser]---+
1251 | | Expression Factory |
1252 | | parser_t::compile(...)|
1253 | +--> ......... ->--+
1254 | | +-----------------------+ |
1255 | Expressions in | | Expressions as
1256 | string form ^ V exprtk::expression
1257 | | | instances
1258 | [s0:'x+1']--->--+ | | +-[e0: x+1]
1259 | | | | |
1260 | [s1:'2z+y']-->--+--+ +->+-[e1: 2z+y]
1261 | | |
1262 | [s2:'sin(k+w)']-+ +-[e2: sin(k+w)]
1263 |
1264 |
1265 | const std::string expression_str[3] =
1266 | {
1267 | "x + 1",
1268 | "2x + y",
1269 | "sin(k + w)"
1270 | };
1271 |
1272 | std::vector expression_list;
1273 |
1274 | parser_t parser;
1275 | expression_t expression;
1276 | symbol_table_t symbol_table;
1277 |
1278 | expression.register_symbol_table(symbol_table);
1279 |
1280 | for (std::size_t i = 0; i < 3; ++i)
1281 | {
1282 | if (parser.compile(expression_str[i],expression))
1283 | {
1284 | expression_list.push_back(expression);
1285 | }
1286 | else
1287 | std::cout << "Error in " << expression_str[i] << "\n"
1288 | }
1289 |
1290 | for (auto& e : expression_list)
1291 | {
1292 | e.value();
1293 | }
1294 |
1295 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1296 |
1297 | [SECTION 11 - COMPILATION OPTIONS]
1298 | The exprtk::parser when being instantiated takes as input a set of
1299 | options to be used during the compilation process of expressions.
1300 | An example instantiation of exprtk::parser where only the joiner,
1301 | commutative and strength reduction options are enabled is as follows:
1302 |
1303 | typedef exprtk::parser::settings_t settings_t;
1304 |
1305 | const std::size_t compile_options =
1306 | settings_t::e_joiner +
1307 | settings_t::e_commutative_check +
1308 | settings_t::e_strength_reduction;
1309 |
1310 | parser_t parser(compile_options);
1311 |
1312 |
1313 | Currently nine types of compile time options are supported, and
1314 | enabled by default. The options and their explanations are as follows:
1315 |
1316 | (1) Replacer
1317 | (2) Joiner
1318 | (3) Numeric Check
1319 | (4) Bracket Check
1320 | (5) Sequence Check
1321 | (6) Commutative Check
1322 | (7) Strength Reduction Check
1323 | (8) Stack And Node Depth Check
1324 | (9) Expression Size Check
1325 |
1326 |
1327 | (1) Replacer (e_replacer)
1328 | Enable replacement of specific tokens with other tokens. For example
1329 | the token "true" of type symbol shall be replaced with the numeric
1330 | token of value one.
1331 |
1332 | (a) (x < y) == true ---> (x < y) == 1
1333 | (b) false == (x > y) ---> 0 == (x > y)
1334 |
1335 |
1336 | (2) Joiner (e_joiner)
1337 | Enable joining of multi-character operators that may have been
1338 | incorrectly disjoint in the string representation of the specified
1339 | expression. For example the consecutive tokens of ">" "=" will become
1340 | ">=" representing the "greater than or equal to" operator. If not
1341 | properly resolved the original form will cause a compilation error.
1342 | The following is a listing of the scenarios that the joiner can
1343 | handle:
1344 |
1345 | (a) '>' '=' ---> '>=' (gte)
1346 | (b) '<' '=' ---> '<=' (lte)
1347 | (c) '=' '=' ---> '==' (equal)
1348 | (d) '!' '=' ---> '!=' (not-equal)
1349 | (e) '<' '>' ---> '<>' (not-equal)
1350 | (f) ':' '=' ---> ':=' (assignment)
1351 | (g) '+' '=' ---> '+=' (addition assignment)
1352 | (h) '-' '=' ---> '-=' (subtraction assignment)
1353 | (i) '' '=' ---> '=' (multiplication assignment)
1354 | (j) '/' '=' ---> '/=' (division assignment)
1355 | (k) '%' '=' ---> '%=' (modulo assignment)
1356 | (l) '+' '-' ---> '-' (subtraction)
1357 | (m) '-' '+' ---> '-' (subtraction)
1358 | (n) '-' '-' ---> '+' (addition)
1359 | (o) '<=' '>' ---> '<=>' (swap)
1360 |
1361 |
1362 | An example of the transformation that takes place is as follows:
1363 |
1364 | (a) (x > = y) and (z ! = w) ---> (x >= y) and (z != w)
1365 |
1366 |
1367 | (3) Numeric Check (e_numeric_check)
1368 | Enable validation of tokens representing numeric types so as to catch
1369 | any errors prior to the costly process of the main compilation step
1370 | commencing.
1371 |
1372 |
1373 | (4) Bracket Check (e_bracket_check)
1374 | Enable the check for validating the ordering of brackets in the
1375 | specified expression.
1376 |
1377 |
1378 | (5) Sequence Check (e_sequence_check)
1379 | Enable the check for validating that sequences of either pairs or
1380 | triplets of tokens make sense. For example the following sequence of
1381 | tokens when encountered will raise an error:
1382 |
1383 | (a) (x + * 3) ---> sequence error
1384 |
1385 |
1386 | (6) Commutative Check (e_commutative_check)
1387 | Enable the check that will transform sequences of pairs of tokens that
1388 | imply a multiplication operation. The following are some examples of
1389 | such transformations:
1390 |
1391 | (a) 2x ---> 2 * x
1392 | (b) 25x^3 ---> 25 * x^3
1393 | (c) 3(x + 1) ---> 3 * (x + 1)
1394 | (d) (x + 1)4 ---> (x + 1) * 4
1395 | (e) 5foo(x,y) ---> 5 * foo(x,y)
1396 | (f) foo(x,y)6 + 1 ---> foo(x,y) * 6 + 1
1397 | (g) (4((2x)3)) ---> 4 * ((2 * x) * 3)
1398 | (h) w / (x - y)z ---> w / (x - y) * z
1399 |
1400 |
1401 | (7) Strength Reduction Check (e_strength_reduction)
1402 | Enable the use of strength reduction optimisations during the
1403 | compilation process. In ExprTk strength reduction optimisations
1404 | predominantly involve transforming sub-expressions into other forms
1405 | that are algebraically equivalent yet less costly to compute. The
1406 | following are examples of the various transformations that can occur:
1407 |
1408 | (a) (x / y) / z ---> x / (y * z)
1409 | (b) (x / y) / (z / w) ---> (x * w) / (y * z)
1410 | (c) (2 * x) - (2 * y) ---> 2 * (x - y)
1411 | (d) (2 / x) / (3 / y) ---> (2 / 3) / (x * y)
1412 | (e) (2 * x) * (3 * y) ---> 6 * (x * y)
1413 | (f) (2 * x) * (2 - 4 / 2) ---> 0
1414 | (g) (3 - 6 / 2) / (2 * x) ---> 0
1415 | (h) avg(x,y,z) * (2 - 4 / 2) ---> 0
1416 |
1417 |
1418 | Note08: When using strength reduction in conjunction with expressions
1419 | whose inputs or sub-expressions may result in values nearing either of
1420 | the bounds of the underlying numeric type (eg: double), there may be
1421 | the possibility of a decrease in the precision of results.
1422 |
1423 | In the following example the given expression which represents an
1424 | attempt at computing the average between x and y will be transformed
1425 | as follows:
1426 |
1427 | (0.5 * x) + (y * 0.5) ---> 0.5 * (x + y)
1428 |
1429 | There may be situations where the above transformation will cause
1430 | numerical overflows and that the original form of the expression is
1431 | desired over the strength reduced form. In these situations it is best
1432 | to turn off strength reduction optimisations or to use a type with a
1433 | larger numerical bound.
1434 |
1435 |
1436 | (8) Stack And Node Depth Check
1437 | ExprTk incorporates a recursive descent parser. When parsing
1438 | expressions comprising inner sub-expressions, the recursive nature of
1439 | the parsing process causes the stack to grow. If the expression causes
1440 | the stack to grow beyond the stack size limit, this would lead to a
1441 | stackoverflow and its associated stack corruption and security
1442 | vulnerability issues.
1443 |
1444 | Similarly to parsing, evaluating an expression may cause the stack to
1445 | grow. Such things like user defined functions, composite functions and
1446 | the general nature of the AST being evaluated can cause the stack to
1447 | grow, and may result in potential stackoverflow issues as denoted
1448 | above.
1449 |
1450 | ExprTk provides a set of checks that prevent both of the above denoted
1451 | problems at compile time. These checks rely on two specific limits
1452 | being set on the parser settings instance, these limits are:
1453 |
1454 | 1. max_stack_depth (default: 400 )
1455 | 2. max_node_depth (default: 10000)
1456 |
1457 |
1458 | The following demonstrates how these two parser parameters can be set:
1459 |
1460 | parser_t parser;
1461 |
1462 | parser.settings().set_max_stack_depth(100);
1463 | parser.settings().set_max_node_depth(200);
1464 |
1465 |
1466 | In the above code, during parsing if the stack depth reaches or
1467 | exceeds 100 levels, the parsing process will immediately halt and
1468 | return with a failure. Similarly, during synthesizing the AST nodes,
1469 | if the compilation process detects an AST tree depth exceeding 200
1470 | levels the parsing process will halt and return a parsing failure.
1471 |
1472 |
1473 | (9) Expression Size Check
1474 | ExprTk allows for expression local variables, vectors and strings to
1475 | be defined. As such the amount of data in terms of bytes consumed by
1476 | the expression locals can also be limited, so as to prevent
1477 | expressions once compiled from consuming large amounts of data.
1478 |
1479 | The maximum number of bytes an expression's locally defined variables
1480 | may use can be set via the parser settings prior to compilation as
1481 | follows:
1482 |
1483 | using expression_t = exprtk::expression;
1484 | using parser_t = exprtk::parser;
1485 |
1486 | expression_t expression;
1487 | parser_t parser;
1488 |
1489 | parser.settings().set_max_total_local_symbol_size_bytes(16);
1490 |
1491 | const std::string expression1 = "var x := 1; var y := 1;"
1492 | const std::string expression2 = "var x := 1; var v[2] := [1];"
1493 |
1494 | expression_t expression;
1495 |
1496 | parser.compile(expression1, expression); // compilation success
1497 | parser.compile(expression2, expression); // compilation error
1498 |
1499 | In the above example, the expression's max total local symbol size has
1500 | been set to 16 bytes. The numeric type being used is double which is 8
1501 | bytes per instance. The first expression (expression1) compiles
1502 | successfully because the total local symbol size is 16 bytes, 8 bytes
1503 | for each of the variables x and y.
1504 |
1505 | However the second expression (expression2) fails to compile. This is
1506 | because the total number of bytes needed for the expression is 24
1507 | bytes, 8 bytes for the variable x and 16 bytes for the vector v, as
1508 | such exceeding the previously set limit of 16 bytes. The error
1509 | diagnostic generated by the parser on compilation failure will look
1510 | like the following:
1511 |
1512 | ERR161 - Adding vector 'v' of size 2 bytes will exceed max total
1513 | local symbol size of: 16 bytes, current total size: 8 bytes
1514 |
1515 |
1516 | (10) Vector Size Check
1517 | When defining an expression local vector, ExprTk uses a default
1518 | maximum vector size of two billion elements. One may want to limit the
1519 | maximum vector size to be either smaller or larger than the specified
1520 | default value. The maximum size value can be changed via the parser
1521 | settings.
1522 |
1523 | parser_t parser;
1524 |
1525 | parser.settings().set_max_local_vector_size(1000000);
1526 |
1527 | std::string expression1 = "var v[1e6] := [123]"
1528 | std::string expression2 = "var v[1e9] := [123]"
1529 |
1530 | expression_t expression;
1531 |
1532 | parser.compile(expression1, expression); // compilation success
1533 | parser.compile(expression2, expression); // compilation error
1534 |
1535 |
1536 | In the above code, the maximum local vector size is set to one million
1537 | elements. During compilation of an expression if there is a vector
1538 | definition where the vector's size exceeds the maximum allowed vector
1539 | size a compilation error shall be emitted. The error diagnostic
1540 | generated by the parser on compilation failure will look like the
1541 | following:
1542 |
1543 | ERR160 - Invalid vector size. Must be an integer in the
1544 | range [0,1000000], size: 1000000000
1545 |
1546 |
1547 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1548 |
1549 | [SECTION 12 - EXPRESSION STRUCTURES]
1550 | Exprtk supports mathematical expressions in numerous forms based on a
1551 | simple imperative programming model. This section will cover the
1552 | following topics related to general structure and programming of
1553 | expressions using ExprTk:
1554 |
1555 | (1) Multi-Statement Expressions
1556 | (2) Statements And Side-Effects
1557 | (3) Conditional Statements
1558 | (4) Special Functions
1559 |
1560 |
1561 | (1) Multi-Statement Expressions
1562 | Expressions in ExprTk can be comprised of one or more statements, which
1563 | may sometimes be called sub-expressions. The following are two
1564 | examples of expressions stored in std::string variables, the first a
1565 | single statement and the second a multi-statement expression:
1566 |
1567 | std::string single_statement = " z := x + y "
1568 |
1569 | std::string multi_statement = " var temp := x; "
1570 | " x := y + z; "
1571 | " y := temp; "
1572 |
1573 |
1574 | In a multi-statement expression, the final statement will determine
1575 | the overall result of the expression. In the following multi-statement
1576 | expression, the result of the expression when evaluated will be '2.3',
1577 | which will also be the value stored in the 'y' variable.
1578 |
1579 | z := x + y;
1580 | y := 2.3;
1581 |
1582 |
1583 | As demonstrated in the expression above, statements within an
1584 | expression are separated using the semi-colon ';' operator. In the
1585 | event two statements are not separated by a semi-colon, and the
1586 | implied multiplication feature is active (enabled by default), the
1587 | compiler will assume a multiplication operation between the two
1588 | statements.
1589 |
1590 | In the following example we have a multi-statement expression composed
1591 | of two variable definitions and initialisations for variables x and y
1592 | and two seemingly separate mathematical operations.
1593 |
1594 | var x:= 2;
1595 | var y:= 3;
1596 | x + 1
1597 | y * 2
1598 |
1599 |
1600 | However the result of the expression will not be 6 as may have been
1601 | assumed based on the calculation of 'y * 2', but rather the result
1602 | will be 8. This is because the compiler will have conjoined the two
1603 | mathematical statements into one via a multiplication operation. The
1604 | expression when compiled will actually evaluate as the following:
1605 |
1606 | var x:= 2;
1607 | var y:= 3;
1608 | x + 1 * y * 2; // 2 + 1 * 3 * 2 == 8
1609 |
1610 |
1611 | In ExprTk any valid statement will itself return a value. This value
1612 | can further be used in conjunction with other statements. This
1613 | includes language structures such as if-statements, loops (for, while)
1614 | and the switch statement. Typically the last statement executed in the
1615 | given construct (conditional, loop etc), will be the value that is
1616 | returned.
1617 |
1618 | In the following example, the return value of the expression will be
1619 | 11, which is the sum of the variable 'x' and the final value computed
1620 | within the loop body upon its last iteration:
1621 |
1622 | var x := 1;
1623 | x + for (var i := x; i < 10; i += 1)
1624 | {
1625 | i / 2;
1626 | i + 1;
1627 | }
1628 |
1629 |
1630 | (2) Statements And Side-Effects
1631 | Statements themselves may have side effects, which in-turn affects the
1632 | proceeding statements in multi-statement expressions.
1633 |
1634 | A statement is said to have a side-effect if it causes the state of
1635 | the expression to change in some way - this includes but is not
1636 | limited to the modification of the state of external variables used
1637 | within the expression. Currently the following actions being present
1638 | in a statement will cause it to have a side-effect:
1639 |
1640 | (a) Assignment operation (explicit or potentially)
1641 | (b) Invoking a user-defined function that has side-effects
1642 |
1643 | The following are examples of expressions where the side-effect status
1644 | of the statements (sub-expressions) within the expressions have been
1645 | noted:
1646 |
1647 | +-+----------------------+------------------------------+
1648 | |#| Expression | Side Effect Status |
1649 | +-+----------------------+------------------------------+
1650 | |0| x + y | False |
1651 | +-+----------------------+------------------------------+
1652 | |1| z := x + y | True - Due to assignment |
1653 | +-+----------------------+------------------------------+
1654 | |2| abs(x - y) | False |
1655 | +-+----------------------+------------------------------+
1656 | |3| abs(x - y); | False |
1657 | | | z := (x += y); | True - Due to assignments |
1658 | +-+----------------------+------------------------------+
1659 | |4| var t := abs(x - y); | True - Due to initialisation |
1660 | | | t + x; | False |
1661 | | | z := (x += y); | True - Due to assignments |
1662 | +-+----------------------+------------------------------+
1663 | |5| foo(x - y) | True - user defined function |
1664 | +-+----------------------+------------------------------+
1665 |
1666 |
1667 | Note09: In example 5 from the above set, it is assumed the user
1668 | defined function foo has been registered as having a side-effect. By
1669 | default all user defined functions are assumed to have side-effects,
1670 | unless they are configured in their constructors to not have side-
1671 | effects using the 'disable_has_side_effects' free function. For more
1672 | information review Section 15 - User Defined Functions sub-section 7
1673 | Function Side-Effects.
1674 |
1675 | At this point we can see that there will be expressions composed of
1676 | certain kinds of statements that when executed will not affect the
1677 | nature of the expression's result. These statements are typically
1678 | called 'dead code'. These statements though not influencing the final
1679 | result will still be executed and as such they will consume processing
1680 | time that could otherwise be saved. Consequently ExprTk attempts to
1681 | detect and remove such statements from expressions.
1682 |
1683 | The 'Dead Code Elimination' (DCE) optimisation process, which is
1684 | enabled by default, will remove any statements that are determined to
1685 | not have a side-effect in a multi-statement expression, excluding the
1686 | final or last statement.
1687 |
1688 | By default the final statement in an expression will always be present
1689 | regardless of its side-effect status, as it is the statement whose
1690 | value shall be used as the result of the expression.
1691 |
1692 | In order to further explain the actions taken during the DCE process,
1693 | lets review the following expression:
1694 |
1695 | var x := 2; // Statement 1
1696 | var y := x + 2; // Statement 2
1697 | x + y; // Statement 3
1698 | y := x + 3y; // Statement 4
1699 | x - y; // Statement 5
1700 |
1701 |
1702 | The above expression has five statements. Three of them (1, 2 and 4)
1703 | actively have side-effects. The first two are variable declaration and
1704 | initialisations, where as the third is due to an assignment operation.
1705 | There are two statements (3 and 5), that do not explicitly have
1706 | side-effects, however the latter, statement 5, is the final statement
1707 | in the expression and hence will be assumed to have a side-effect.
1708 |
1709 | During compilation when the DCE optimisation is applied to the above
1710 | expression, statement 3 will be removed from the expression, as it has
1711 | no bearing on the final result of expression, the rest of the
1712 | statements will all remain. The optimised form of the expression is as
1713 | follows:
1714 |
1715 | var x := 2; // Statement 1
1716 | var y := x + 2; // Statement 2
1717 | y := x + 3y; // Statement 3
1718 | x - y; // Statement 4
1719 |
1720 |
1721 | (3) Conditional Statements (If-Then-Else)
1722 | ExprTk supports two forms of conditional branching or otherwise known
1723 | as if-statements. The first form, is a simple function based
1724 | conditional statement, that takes exactly three input expressions:
1725 | condition, consequent and alternative. The following is an example
1726 | expression that utilises the function based if-statement.
1727 |
1728 | x := if (y < z, y + 1, 2 * z)
1729 |
1730 |
1731 | In the example above, if the condition 'y < z' is true, then the
1732 | consequent 'y + 1' will be evaluated, its value shall be returned and
1733 | subsequently assigned to the variable 'x'. Otherwise the alternative
1734 | '2 * z' will be evaluated and its value will be returned. This is
1735 | essentially the simplest form of an if-then-else statement. A simple
1736 | variation of the expression where the value of the if-statement is
1737 | used within another statement is as follows:
1738 |
1739 | x := 3 * if (y < z, y + 1, 2 * z) / 2
1740 |
1741 |
1742 | The second form of if-statement resembles the standard syntax found in
1743 | most imperative languages. There are two variations of the statement:
1744 |
1745 | (a) If-Statement
1746 | (b) If-Then-Else Statement
1747 |
1748 |
1749 | (a) If-Statement
1750 | This version of the conditional statement returns the value of the
1751 | consequent expression when the condition expression is true, else it
1752 | will return a quiet NaN value as its result.
1753 |
1754 | Example 1:
1755 | x := if (y < z) y + 3;
1756 |
1757 | Example 2:
1758 | x := if (y < z)
1759 | {
1760 | y + 3
1761 | };
1762 |
1763 | The two example expressions above are equivalent. If the condition
1764 | 'y < z' is true, the 'x' variable shall be assigned the value of the
1765 | consequent 'y + 3', otherwise it will be assigned the value of quiet
1766 | NaN. As previously discussed, if-statements are value returning
1767 | constructs, and if not properly terminated using a semi-colon, will
1768 | end-up combining with the next statement via a multiplication
1769 | operation. The following example will NOT result in the expected value
1770 | of 'w + x' being returned:
1771 |
1772 | x := if (y < z) y + 3 // missing semi-colon ';'
1773 | w + x
1774 |
1775 |
1776 | When the above supposed multi-statement expression is compiled, the
1777 | expression will have a multiplication inserted between the two
1778 | 'intended' statements resulting in the unanticipated expression:
1779 |
1780 | x := (if (y < z) y + 3) * w + x
1781 |
1782 |
1783 | The solution to the above situation is to simply terminate the
1784 | conditional statement with a semi-colon as follows:
1785 |
1786 | x := if (y < z) y + 3;
1787 | w + x
1788 |
1789 |
1790 | (b) If-Then-Else Statement
1791 | The second variation of the if-statement is to allow for the use of
1792 | Else and Else-If cascading statements. Examples of such statements are
1793 | as follows:
1794 |
1795 | Example 1: Example 2: Example 3:
1796 | if (x < y) if (x < y) if (x > y + 1)
1797 | z := x + 3; { y := abs(x - z);
1798 | else y := z + x; else
1799 | y := x - z; z := x + 3; {
1800 | } y := z + x;
1801 | else z := x + 3;
1802 | y := x - z; };
1803 |
1804 |
1805 | Example 4: Example 5: Example 6:
1806 | if (2 * x < max(y,3)) if (x < y) if (x < y or (x + z) > y)
1807 | { z := x + 3; {
1808 | y := z + x; else if (2y != z) z := x + 3;
1809 | z := x + 3; { y := x - z;
1810 | } z := x + 3; }
1811 | else if (2y - z) y := x - z; else if (abs(2y - z) >= 3)
1812 | y := x - z; } y := x - z;
1813 | else else
1814 | x * x; {
1815 | z := abs(x * x);
1816 | x * y * z;
1817 | };
1818 |
1819 |
1820 | In the case where there is no final else statement and the flow
1821 | through the conditional arrives at this final point, the same rules
1822 | apply to this form of if-statement as to the previous. That is a quiet
1823 | NaN shall be returned as the result of the if-statement. Furthermore
1824 | the same requirements of terminating the statement with a semi-colon
1825 | apply.
1826 |
1827 | (4) Special Functions
1828 | The purpose of special functions in ExprTk is to provide compiler
1829 | generated equivalents of common mathematical expressions which can be
1830 | invoked by using the 'special function' syntax (eg: $f12(x,y,z) or
1831 | $f82(x,y,z,w)).
1832 |
1833 | Special functions dramatically decrease the total evaluation time of
1834 | expressions which would otherwise have been written using the common
1835 | form by reducing the total number of nodes in the evaluation tree of
1836 | an expression and by also leveraging the compiler's ability to
1837 | correctly optimise such expressions for a given architecture.
1838 |
1839 | 3-Parameter 4-Parameter
1840 | +-------------+-------------+ +--------------+------------------+
1841 | | Prototype | Operation | | Prototype | Operation |
1842 | +-------------+-------------+ +--------------+------------------+
1843 | f00(x,y,z)∣(x+y)/zf00(x,y,z) | (x + y) / z f00(x,y,z)∣(x+y)/zf48(x,y,z,w) | x + ((y + z) / w)
1844 | f01(x,y,z)∣(x+y)∗zf01(x,y,z) | (x + y) * z f01(x,y,z)∣(x+y)∗zf49(x,y,z,w) | x + ((y + z) * w)
1845 | f02(x,y,z)∣(x+y)−zf02(x,y,z) | (x + y) - z f02(x,y,z)∣(x+y)−zf50(x,y,z,w) | x + ((y - z) / w)
1846 | f03(x,y,z)∣(x+y)+zf03(x,y,z) | (x + y) + z f03(x,y,z)∣(x+y)+zf51(x,y,z,w) | x + ((y - z) * w)
1847 | f04(x,y,z)∣(x−y)+zf04(x,y,z) | (x - y) + z f04(x,y,z)∣(x−y)+zf52(x,y,z,w) | x + ((y * z) / w)
1848 | f05(x,y,z)∣(x−y)/zf05(x,y,z) | (x - y) / z f05(x,y,z)∣(x−y)/zf53(x,y,z,w) | x + ((y * z) * w)
1849 | f06(x,y,z)∣(x−y)∗zf06(x,y,z) | (x - y) * z f06(x,y,z)∣(x−y)∗zf54(x,y,z,w) | x + ((y / z) + w)
1850 | f07(x,y,z)∣(x∗y)+zf07(x,y,z) | (x * y) + z f07(x,y,z)∣(x∗y)+zf55(x,y,z,w) | x + ((y / z) / w)
1851 | f08(x,y,z)∣(x∗y)−zf08(x,y,z) | (x * y) - z f08(x,y,z)∣(x∗y)−zf56(x,y,z,w) | x + ((y / z) * w)
1852 | f09(x,y,z)∣(x∗y)/zf09(x,y,z) | (x * y) / z f09(x,y,z)∣(x∗y)/zf57(x,y,z,w) | x - ((y + z) / w)
1853 | f10(x,y,z)∣(x∗y)∗zf10(x,y,z) | (x * y) * z f10(x,y,z)∣(x∗y)∗zf58(x,y,z,w) | x - ((y + z) * w)
1854 | f11(x,y,z)∣(x/y)+zf11(x,y,z) | (x / y) + z f11(x,y,z)∣(x/y)+zf59(x,y,z,w) | x - ((y - z) / w)
1855 | f12(x,y,z)∣(x/y)−zf12(x,y,z) | (x / y) - z f12(x,y,z)∣(x/y)−zf60(x,y,z,w) | x - ((y - z) * w)
1856 | f13(x,y,z)∣(x/y)/zf13(x,y,z) | (x / y) / z f13(x,y,z)∣(x/y)/zf61(x,y,z,w) | x - ((y * z) / w)
1857 | f14(x,y,z)∣(x/y)∗zf14(x,y,z) | (x / y) * z f14(x,y,z)∣(x/y)∗zf62(x,y,z,w) | x - ((y * z) * w)
1858 | f15(x,y,z)∣x/(y+z)f15(x,y,z) | x / (y + z) f15(x,y,z)∣x/(y+z)f63(x,y,z,w) | x - ((y / z) / w)
1859 | f16(x,y,z)∣x/(y−z)f16(x,y,z) | x / (y - z) f16(x,y,z)∣x/(y−z)f64(x,y,z,w) | x - ((y / z) * w)
1860 | f17(x,y,z)∣x/(y∗z)f17(x,y,z) | x / (y * z) f17(x,y,z)∣x/(y∗z)f65(x,y,z,w) | ((x + y) * z) - w
1861 | f18(x,y,z)∣x/(y/z)f18(x,y,z) | x / (y / z) f18(x,y,z)∣x/(y/z)f66(x,y,z,w) | ((x - y) * z) - w
1862 | f19(x,y,z)∣x∗(y+z)f19(x,y,z) | x * (y + z) f19(x,y,z)∣x∗(y+z)f67(x,y,z,w) | ((x * y) * z) - w
1863 | f20(x,y,z)∣x∗(y−z)f20(x,y,z) | x * (y - z) f20(x,y,z)∣x∗(y−z)f68(x,y,z,w) | ((x / y) * z) - w
1864 | f21(x,y,z)∣x∗(y∗z)f21(x,y,z) | x * (y * z) f21(x,y,z)∣x∗(y∗z)f69(x,y,z,w) | ((x + y) / z) - w
1865 | f22(x,y,z)∣x∗(y/z)f22(x,y,z) | x * (y / z) f22(x,y,z)∣x∗(y/z)f70(x,y,z,w) | ((x - y) / z) - w
1866 | f23(x,y,z)∣x−(y+z)f23(x,y,z) | x - (y + z) f23(x,y,z)∣x−(y+z)f71(x,y,z,w) | ((x * y) / z) - w
1867 | f24(x,y,z)∣x−(y−z)f24(x,y,z) | x - (y - z) f24(x,y,z)∣x−(y−z)f72(x,y,z,w) | ((x / y) / z) - w
1868 | f25(x,y,z)∣x−(y/z)f25(x,y,z) | x - (y / z) f25(x,y,z)∣x−(y/z)f73(x,y,z,w) | (x * y) + (z * w)
1869 | f26(x,y,z)∣x−(y∗z)f26(x,y,z) | x - (y * z) f26(x,y,z)∣x−(y∗z)f74(x,y,z,w) | (x * y) - (z * w)
1870 | f27(x,y,z)∣x+(y∗z)f27(x,y,z) | x + (y * z) f27(x,y,z)∣x+(y∗z)f75(x,y,z,w) | (x * y) + (z / w)
1871 | f28(x,y,z)∣x+(y/z)f28(x,y,z) | x + (y / z) f28(x,y,z)∣x+(y/z)f76(x,y,z,w) | (x * y) - (z / w)
1872 | f29(x,y,z)∣x+(y+z)f29(x,y,z) | x + (y + z) f29(x,y,z)∣x+(y+z)f77(x,y,z,w) | (x / y) + (z / w)
1873 | f30(x,y,z)∣x+(y−z)f30(x,y,z) | x + (y - z) f30(x,y,z)∣x+(y−z)f78(x,y,z,w) | (x / y) - (z / w)
1874 | f31(x,y,z)∣x∗y2+zf31(x,y,z) | x * y^2 + z f31(x,y,z)∣x∗y2+zf79(x,y,z,w) | (x / y) - (z * w)
1875 | f32(x,y,z)∣x∗y3+zf32(x,y,z) | x * y^3 + z f32(x,y,z)∣x∗y3+zf80(x,y,z,w) | x / (y + (z * w))
1876 | f33(x,y,z)∣x∗y4+zf33(x,y,z) | x * y^4 + z f33(x,y,z)∣x∗y4+zf81(x,y,z,w) | x / (y - (z * w))
1877 | f34(x,y,z)∣x∗y5+zf34(x,y,z) | x * y^5 + z f34(x,y,z)∣x∗y5+zf82(x,y,z,w) | x * (y + (z * w))
1878 | f35(x,y,z)∣x∗y6+zf35(x,y,z) | x * y^6 + z f35(x,y,z)∣x∗y6+zf83(x,y,z,w) | x * (y - (z * w))
1879 | f36(x,y,z)∣x∗y7+zf36(x,y,z) | x * y^7 + z f36(x,y,z)∣x∗y7+zf84(x,y,z,w) | xy^2 + zw^2
1880 | f37(x,y,z)∣x∗y8+zf37(x,y,z) | x * y^8 + z f37(x,y,z)∣x∗y8+zf85(x,y,z,w) | xy^3 + zw^3
1881 | f38(x,y,z)∣x∗y9+zf38(x,y,z) | x * y^9 + z f38(x,y,z)∣x∗y9+zf86(x,y,z,w) | xy^4 + zw^4
1882 | f39(x,y,z)∣x∗log(y)+zf39(x,y,z) | x * log(y)+z f39(x,y,z)∣x∗log(y)+zf87(x,y,z,w) | xy^5 + zw^5
1883 | f40(x,y,z)∣x∗log(y)−zf40(x,y,z) | x * log(y)-z f40(x,y,z)∣x∗log(y)−zf88(x,y,z,w) | xy^6 + zw^6
1884 | f41(x,y,z)∣x∗log10(y)+zf41(x,y,z) | x * log10(y)+z f41(x,y,z)∣x∗log10(y)+zf89(x,y,z,w) | xy^7 + zw^7
1885 | f42(x,y,z)∣x∗log10(y)−zf42(x,y,z) | x * log10(y)-z f42(x,y,z)∣x∗log10(y)−zf90(x,y,z,w) | xy^8 + zw^8
1886 | f43(x,y,z)∣x∗sin(y)+zf43(x,y,z) | x * sin(y)+z f43(x,y,z)∣x∗sin(y)+zf91(x,y,z,w) | xy^9 + zw^9
1887 | f44(x,y,z)∣x∗sin(y)−zf44(x,y,z) | x * sin(y)-z f44(x,y,z)∣x∗sin(y)−zf92(x,y,z,w) | (x and y) ? z : w
1888 | f45(x,y,z)∣x∗cos(y)+zf45(x,y,z) | x * cos(y)+z f45(x,y,z)∣x∗cos(y)+zf93(x,y,z,w) | (x or y) ? z : w
1889 | f46(x,y,z)∣x∗cos(y)−zf46(x,y,z) | x * cos(y)-z f46(x,y,z)∣x∗cos(y)−zf94(x,y,z,w) | (x < y) ? z : w
1890 | f47(x,y,z)∣x?y:zf47(x,y,z) | x ? y : z f47(x,y,z)∣x?y:zf95(x,y,z,w) | (x <= y) ? z : w
1891 | $f96(x,y,z,w) | (x > y) ? z : w
1892 | $f97(x,y,z,w) | (x >= y) ? z : w
1893 | $f98(x,y,z,w) | (x == y) ? z : w
1894 | $f99(x,y,z,w) | xsin(y)+zcos(w)
1895 |
1896 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1897 |
1898 | [SECTION 13 - VARIABLE, VECTOR & STRING DEFINITION]
1899 | ExprTk supports the definition of expression local variables, vectors
1900 | and strings. The definitions must be unique as shadowing is not
1901 | allowed and object lifetimes are based on scope. Definitions use the
1902 | following general form:
1903 |
1904 | var := ;
1905 |
1906 | (1) Variable Definition
1907 | Variables are of numeric type denoting a single value. They can be
1908 | explicitly initialised to a value, otherwise they will be defaulted to
1909 | zero. The following are examples of variable definitions:
1910 |
1911 | (a) Initialise x to zero
1912 | var x;
1913 |
1914 | (b) Initialise y to three
1915 | var y := 3;
1916 |
1917 | (c) Initialise z to the expression
1918 | var z := if (max(1, x + y) > 2, w, v);
1919 |
1920 | (d) Initialise const literal n
1921 | var n := 12 / 3;
1922 |
1923 |
1924 | (2) Vector Definition
1925 | Vectors are arrays of a common numeric type. The elements in a vector
1926 | can be explicitly initialised, otherwise they will all be defaulted to
1927 | zero. The following are examples of vector definitions:
1928 |
1929 | (a) Initialise all values to zero
1930 | var x[3];
1931 |
1932 | (b) Initialise all values to zero
1933 | var x[3] := {};
1934 |
1935 | (c) Initialise all values to given value or expression
1936 | var x[3] := [ 42 ];
1937 | var y[x[]] := [ 123 + 3y + sin(w / z) ];
1938 |
1939 | (d) Initialise all values iota style
1940 | var v[4] := [ 0 : +1]; // 0, 1, 2, 3
1941 | var v[5] := [-3 : -2]; // -3, -5, -7, -9, -11
1942 |
1943 | (e) Initialise the first two values, all other elements to zero
1944 | var x[3] := { (1 + x[2]) / x[], (sin(y[0] / x[]) + 3) / x[] };
1945 |
1946 | (f) Initialise the first three (all) values
1947 | const var size := 3;
1948 | var x[size] := { 1, 2, 3 };
1949 |
1950 | (g) Initialise vector from a vector
1951 | var x[4] := { 1, 2, 3, 4 };
1952 | var y[3] := x;
1953 | var w[5] := { 1, 2 }; // 1, 2, 0, 0, 0
1954 |
1955 | (h) Initialise vector from a smaller vector
1956 | var x[3] := { 1, 2, 3 };
1957 | var y[5] := x; // 1, 2, 3, ??, ??
1958 |
1959 | (i) Non-initialised vector
1960 | var x[3] := null; // ?? ?? ??
1961 |
1962 | (j) Error as there are too many initialisers
1963 | var x[3] := { 1, 2, 3, 4 };
1964 |
1965 | (k) Error as a vector of size zero is not allowed.
1966 | var x[0];
1967 |
1968 |
1969 | (3) String Definition
1970 | Strings are sequences comprised of 8-bit characters. They can only be
1971 | defined with an explicit initialisation value. The following are
1972 | examples of string variable definitions:
1973 |
1974 | (a) Initialise to a string
1975 | var x := 'abc';
1976 |
1977 | (b) Initialise to an empty string
1978 | var x := '';
1979 |
1980 | (c) Initialise to a string expression
1981 | var x := 'abc' + '123';
1982 |
1983 | (d) Initialise to a string range
1984 | var x := 'abc123'[2:4];
1985 |
1986 | (e) Initialise to another string variable
1987 | var x := 'abc';
1988 | var y := x;
1989 |
1990 | (f) Initialise to another string variable range
1991 | var x := 'abc123';
1992 | var y := x[2:4];
1993 |
1994 | (g) Initialise to a string expression
1995 | var x := 'abc';
1996 | var y := x + '123';
1997 |
1998 | (h) Initialise to a string expression range
1999 | var x := 'abc';
2000 | var y := (x + '123')[1:3];
2001 |
2002 |
2003 | (4) Return Value
2004 | Variable and vector definitions have a return value. In the case of
2005 | variable definitions, the value to which the variable is initialised
2006 | will be returned. Where as for vectors, the value of the first element
2007 | (eg: v[0]) shall be returned.
2008 |
2009 | 8 == ((var x := 7;) + 1)
2010 | 4 == (var y[3] := {4, 5, 6};)
2011 |
2012 |
2013 | (5) Variable/Vector Assignment
2014 | The value of a variable can be assigned to a vector and a vector or a
2015 | vector expression can be assigned to a variable.
2016 |
2017 | (a) Variable To Vector:
2018 | Every element of the vector is assigned the value of the variable
2019 | or expression.
2020 | var x := 3;
2021 | var y[3] := { 1, 2, 3 };
2022 | y := x + 1;
2023 |
2024 | (b) Vector To Variable:
2025 | The variable is assigned the value of the first element of the
2026 | vector (aka vec[0])
2027 | var x := 3;
2028 | var y[3] := { 1, 2, 3 };
2029 | x := y + 1;
2030 |
2031 |
2032 | Note10: During the expression compilation phase, tokens are classified
2033 | based on the following priorities:
2034 |
2035 | (a) Reserved keywords or operators (+, -, and, or, etc)
2036 | (b) Base functions (abs, sin, cos, min, max etc)
2037 | (c) Symbol table variables
2038 | (d) Expression local defined variables
2039 | (e) Symbol table functions
2040 | (f) Unknown symbol resolver based variables
2041 |
2042 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2043 |
2044 | [SECTION 14 - VECTOR PROCESSING]
2045 | ExprTk provides support for various forms of vector oriented
2046 | arithmetic, inequalities and processing. The various supported pairs
2047 | are as follows:
2048 |
2049 | (a) vector and vector (eg: v0 + v1)
2050 | (b) vector and scalar (eg: v + 33)
2051 | (c) scalar and vector (eg: 22 * v)
2052 |
2053 | The following is a list of operations that can be used in conjunction
2054 | with vectors:
2055 |
2056 | (a) Arithmetic: +, -, , /, %
2057 | (b) Exponentiation: vector ^ scalar
2058 | (c) Assignment: :=, +=, -=, =, /=, %=, <=>
2059 | (d) Inequalities: <, <=, >, >=, ==, =, equal
2060 | (e) Boolean logic: and, nand, nor, or, xnor, xor
2061 | (f) Unary operations:
2062 | abs, acos, acosh, asin, asinh, atan, atanh, ceil, cos, cosh,
2063 | cot, csc, deg2grad, deg2rad, erf, erfc, exp, expm1, floor,
2064 | frac, grad2deg, log, log10, log1p, log2, rad2deg, round, sec,
2065 | sgn, sin, sinc, sinh, sqrt, swap, tan, tanh, trunc,
2066 | thresholding
2067 | (g) Aggregate and Reduce operations:
2068 | avg, max, min, mul, dot, dotk, sum, sumk, count, all_true,
2069 | all_false, any_true, any_false
2070 | (h) Transformation operations:
2071 | copy, diff, reverse, rotate-left/right, shift-left/right, sort,
2072 | nth_element
2073 | (i) BLAS-L1:
2074 | axpy, axpby, axpyz, axpbyz, axpbz
2075 |
2076 | Note11: When one of the above described operations is being performed
2077 | between two vectors, the operation will only span the size of the
2078 | smallest vector. The elements of the larger vector outside of the
2079 | range will not be included. The operation itself will be processed
2080 | element-wise over values of the smaller of the two ranges.
2081 |
2082 | The following simple example demonstrates the vector processing
2083 | capabilities by computing the dot-product of the vectors v0 and v1 and
2084 | then assigning it to the variable v0dotv1:
2085 |
2086 | var v0[3] := { 1, 2, 3 };
2087 | var v1[3] := { 4, 5, 6 };
2088 | var v0dotv1 := sum(v0 * v1);
2089 |
2090 |
2091 | The following is a for-loop based implementation that is equivalent to
2092 | the previously mentioned dot-product computation expression:
2093 |
2094 | var v0[3] := { 1, 2, 3 };
2095 | var v1[3] := { 4, 5, 6 };
2096 | var v0dotv1;
2097 |
2098 | for (var i := 0; i < min(v0[],v1[]); i += 1)
2099 | {
2100 | v0dotv1 += (v0[i] * v1[i]);
2101 | }
2102 |
2103 |
2104 | Note12: When the aggregate or reduction operations denoted above are
2105 | used in conjunction with a vector or vector expression, the return
2106 | value is not a vector but rather a single value.
2107 |
2108 | var x[3] := { 1, 2, 3 };
2109 |
2110 | sum(x) == 6
2111 | sum(1 + 2x) == 15
2112 | avg(3x + 1) == 7
2113 | min(1 / x) == (1 / 3)
2114 | max(x / 2) == (3 / 2)
2115 | sum(x > 0 and x < 5) == x[]
2116 |
2117 |
2118 | When utilising external user defined vectors via the symbol table as
2119 | opposed to expression local defined vectors, the typical 'add_vector'
2120 | method from the symbol table will register the entirety of the vector
2121 | that is passed. The following example attempts to evaluate the sum of
2122 | elements of the external user defined vector within a typical yet
2123 | trivial expression:
2124 |
2125 | const std::string reduce_program = " sum(2 * v + 1) "
2126 |
2127 | std::vector v0 { T(1.1), T(2.2), ..... , T(99.99) };
2128 |
2129 | symbol_table_t symbol_table;
2130 | symbol_table.add_vector("v",v);
2131 |
2132 | expression_t expression;
2133 | expression.register_symbol_table(symbol_table);
2134 |
2135 | parser_t parser;
2136 | parser.compile(reduce_program,expression);
2137 |
2138 | T sum = expression.value();
2139 |
2140 |
2141 | For the most part, this is a very common use-case. However there may
2142 | be situations where one may want to evaluate the same vector oriented
2143 | expression many times over, but using different vectors or sub ranges
2144 | of the same vector of the same size to that of the original upon every
2145 | evaluation.
2146 |
2147 | The usual solution is to either recompile the expression for the new
2148 | vector instance, or to copy the contents from the new vector to the
2149 | symbol table registered vector and then perform the evaluation. When
2150 | the vectors are large or the re-evaluation attempts are numerous,
2151 | these solutions can become rather time consuming and generally
2152 | inefficient.
2153 |
2154 | std::vector v1 { T(2.2), T(2.2), ..... , T(2.2) };
2155 | std::vector v2 { T(3.3), T(3.3), ..... , T(3.3) };
2156 | std::vector v3 { T(4.4), T(4.4), ..... , T(4.4) };
2157 |
2158 | std::vector<std::vector> vv { v1, v2, v3 };
2159 | ...
2160 | T sum = T(0);
2161 |
2162 | for (auto& new_vec : vv)
2163 | {
2164 | v = new_vec; // update vector
2165 | sum += expression.value();
2166 | }
2167 |
2168 |
2169 | A solution to the above 'efficiency' problem, is to use the
2170 | exprtk::vector_view object. The vector_view is instantiated with a
2171 | size and backing based upon a vector. Upon evaluations if the backing
2172 | needs to be 'updated' to either another vector or sub-range, the
2173 | vector_view instance can be efficiently rebased, and the expression
2174 | evaluated as normal.
2175 |
2176 | exprtk::vector_view view = exprtk::make_vector_view(v,v.size());
2177 |
2178 | symbol_table_t symbol_table;
2179 | symbol_table.add_vector("v",view);
2180 |
2181 | ...
2182 |
2183 | T sum = T(0);
2184 |
2185 | for (auto& new_vec : vv)
2186 | {
2187 | view.rebase(new_vec.data()); // update vector
2188 | sum += expression.value();
2189 | }
2190 |
2191 |
2192 | Another useful feature of exprtk::vector_view is that all such vectors
2193 | can have their sizes modified (or "resized"). The resizing of the
2194 | associated vectors can happen either between or during evaluations.
2195 |
2196 | std::vector v = { 1, 2, 3, 4, 5, 6, 7, 8 };
2197 | exprtk::vector_view view = exprtk::make_vector_view(v,v.size());
2198 |
2199 | symbol_table_t symbol_table;
2200 | symbol_table.add_vector("v",view);
2201 |
2202 | const std::string expression_string = "v[]"
2203 |
2204 | expression_t expression;
2205 | expression.register_symbol_table(symbol_table);
2206 |
2207 | parser_t parser;
2208 | parser.compile(expression_string, expression);
2209 |
2210 | for (std::size_t i = 1; i <= v.size(); ++i)
2211 | {
2212 | vv.set_size(i);
2213 | expression.value();
2214 | }
2215 |
2216 |
2217 | In the example above, a vector_view is instantiated with a std::vector
2218 | instance with eight elements and registered to the given symbol_table.
2219 | An expression is then compiled, which in this case simply returns the
2220 | size of the vector at that point in time. The expression is evaluated
2221 | eight times (size of vector times), where upon each iteration the size
2222 | of the vector is changed with values ranging from one to eight.
2223 |
2224 | Note13: When modifying the size of a vector, the new size must be at
2225 | least one or larger and must not exceed the original size of the
2226 | vector_view when it was instantiated.
2227 |
2228 | Note14: The lifetime of any parser, symbol_table or expression
2229 | instance must not exceed that of any vector_view instance that has
2230 | been registered with it. Furthermore the lifetime of a vector_view
2231 | must not exceed that of the underlying vector instance it is
2232 | associated with.
2233 |
2234 | Note15: In a multi-threaded context the rebase function should not be
2235 | called during associated expression evaluation, as this will lead to
2236 | undefined behaviour (eg: torn reads and writes).
2237 |
2238 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2239 |
2240 | [SECTION 15 - USER DEFINED FUNCTIONS]
2241 | ExprTk provides a means whereby custom functions can be defined and
2242 | utilised within expressions. The concept requires the user to
2243 | provide a reference to the function coupled with an associated name
2244 | that will be invoked within expressions. Functions may take numerous
2245 | inputs but will always return a single value of the underlying numeric
2246 | type.
2247 |
2248 | During expression compilation when required the reference to the
2249 | function shall be obtained from the associated symbol_table and be
2250 | embedded into the expression.
2251 |
2252 | There are six types of function interface:
2253 |
2254 | +---+----------------------+--------------+----------------------+
2255 | | # | Name | Return Type | Input Types |
2256 | +---+----------------------+--------------+----------------------+
2257 | | 1 | ifunction | Scalar | Scalar |
2258 | | 2 | ivararg_function | Scalar | Scalar |
2259 | | 3 | igeneric_function | Scalar | Scalar,Vector,String |
2260 | | 4 | igeneric_function II | String | Scalar,Vector,String |
2261 | | 5 | igeneric_function III| String/Scalar| Scalar,Vector,String |
2262 | | 6 | function_compositor | Scalar | Scalar |
2263 | +---+----------------------+--------------+----------------------+
2264 |
2265 | (1) ifunction
2266 | This interface supports zero to 20 input parameters of only the scalar
2267 | type (numbers). The usage requires a custom function be derived from
2268 | ifunction and to override one of the 21 function operators. As part of
2269 | the constructor the custom function will define how many parameters it
2270 | expects to handle. The following example defines a 3 parameter
2271 | function called 'foo':
2272 |
2273 | template
2274 | struct foo final : public exprtk::ifunction
2275 | {
2276 | foo() : exprtk::ifunction(3)
2277 | {}
2278 |
2279 | T operator()(const T& v1, const T& v2, const T& v3) override
2280 | {
2281 | return T(1) + (v1 * v2) / T(v3);
2282 | }
2283 | };
2284 |
2285 |
2286 | (2) ivararg_function
2287 | This interface supports a variable number of scalar arguments as input
2288 | into the function. The function operator interface uses a std::vector
2289 | specialised upon type T to facilitate parameter passing. The following
2290 | example defines a vararg function called 'boo':
2291 |
2292 | template
2293 | struct boo final : public exprtk::ivararg_function
2294 | {
2295 | inline T operator()(const std::vector& arglist) override
2296 | {
2297 | T result = T(0);
2298 |
2299 | for (std::size_t i = 0; i < arglist.size(); ++i)
2300 | {
2301 | result += arglist[i] / arglist[i > 0 ? (i - 1) : 0];
2302 | }
2303 |
2304 | return result;
2305 | }
2306 | };
2307 |
2308 |
2309 | (3) igeneric_function
2310 | This interface supports a variable number of arguments and types as
2311 | input into the function. The function operator interface uses a
2312 | std::vector specialised upon the type_store type to facilitate
2313 | parameter passing.
2314 |
2315 | Scalar <-- function(i_0, i_1, i_2....., i_N)
2316 |
2317 |
2318 | The fundamental types that can be passed into the function as
2319 | parameters and their views are as follows:
2320 |
2321 | (1) Scalar - scalar_view
2322 | (2) Vector - vector_view
2323 | (3) String - string_view
2324 |
2325 |
2326 | The above denoted type views provide non-const reference-like access
2327 | to each parameter, as such modifications made to the input parameters
2328 | will persist after the function call has completed. The following
2329 | example defines a generic function called 'too':
2330 |
2331 | template
2332 | struct too final : public exprtk::igeneric_function
2333 | {
2334 | typedef typename exprtk::igeneric_function::parameter_list_t
2335 | parameter_list_t;
2336 |
2337 | too()
2338 | {}
2339 |
2340 | inline T operator()(parameter_list_t parameters) override
2341 | {
2342 | for (std::size_t i = 0; i < parameters.size(); ++i)
2343 | {
2344 | ...
2345 | }
2346 |
2347 | return T(0);
2348 | }
2349 | };
2350 |
2351 |
2352 | In the example above, the input 'parameters' to the function operator,
2353 | parameter_list_t, is a type of std::vector of type_store. Each
2354 | type_store instance has a member called 'type' which holds the
2355 | enumeration pertaining to the underlying type of the type_store. There
2356 | are three type enumerations:
2357 |
2358 | (1) e_scalar - literals, variables, vector elements, expressions
2359 | eg: 123.456, x, vec[3x + 1], 2x + 3
2360 |
2361 | (2) e_vector - vectors, vector expressions
2362 | eg: vec1, 2 * vec1 + vec2 / 3
2363 |
2364 | (3) e_string - strings, string literals and range variants of both
2365 | eg: 'AString', s0, 'AString'[x:y], s1[1 + x:] + 'AString'
2366 |
2367 |
2368 | Each of the parameters can be accessed using its designated view.
2369 | A typical loop for processing the parameters is as follows:
2370 |
2371 | inline T operator()(parameter_list_t parameters)
2372 | {
2373 | typedef typename exprtk::igeneric_function::generic_type
2374 | generic_type;
2375 |
2376 | typedef typename generic_type::scalar_view scalar_t;
2377 | typedef typename generic_type::vector_view vector_t;
2378 | typedef typename generic_type::string_view string_t;
2379 |
2380 | for (std::size_t i = 0; i < parameters.size(); ++i)
2381 | {
2382 | generic_type& gt = parameters[i];
2383 |
2384 | if (generic_type::e_scalar == gt.type)
2385 | {
2386 | scalar_t x(gt);
2387 | ...
2388 | }
2389 | else if (generic_type::e_vector == gt.type)
2390 | {
2391 | vector_t vector(gt);
2392 | ...
2393 | }
2394 | else if (generic_type::e_string == gt.type)
2395 | {
2396 | string_t string(gt);
2397 | ...
2398 | }
2399 | }
2400 |
2401 | return T(0);
2402 | }
2403 |
2404 |
2405 | Most often than not a custom generic function will require a specific
2406 | sequence of parameters, rather than some arbitrary sequence of types.
2407 | In those situations, ExprTk can perform compile-time type checking to
2408 | validate that function invocations are carried out using the correct
2409 | sequence of parameters. Furthermore performing the checks at compile
2410 | -time rather than at run-time (aka every time the function is invoked)
2411 | will result in expression evaluation performance gains.
2412 |
2413 | Compile-time type checking of input parameters can be requested by
2414 | passing a string to the constructor of the igeneric_function that
2415 | represents the required sequence of parameter types. When no parameter
2416 | sequence is provided, it is implied the function can accept a variable
2417 | number of parameters comprised of any of the fundamental types.
2418 |
2419 | Each fundamental type has an associated character. The following is a
2420 | listing of said characters and their meanings:
2421 |
2422 | (1) T - Scalar
2423 | (2) V - Vector
2424 | (3) S - String
2425 | (4) Z - Zero or no parameters
2426 | (5) ? - Any type (Scalar, Vector or String)
2427 | (6) * - Wildcard operator
2428 | (7) | - Parameter sequence delimiter
2429 |
2430 |
2431 | No other characters other than the seven denoted above may be included
2432 | in the parameter sequence definition. If any such invalid characters
2433 | do exist, registration of the associated generic function to a symbol
2434 | table ('add_function' method) will fail. If the parameter sequence is
2435 | modified resulting in it becoming invalid after having been added to
2436 | the symbol table but before the compilation step, a compilation error
2437 | will be incurred.
2438 |
2439 | The following example demonstrates a simple generic function
2440 | implementation with a user specified parameter sequence:
2441 |
2442 | template
2443 | struct moo final : public exprtk::igeneric_function
2444 | {
2445 | typedef typename exprtk::igeneric_function::parameter_list_t
2446 | parameter_list_t;
2447 |
2448 | moo()
2449 | : exprtk::igeneric_function("SVTT")
2450 | {}
2451 |
2452 | inline T operator()(parameter_list_t parameters) override
2453 | {
2454 | ...
2455 | }
2456 | };
2457 |
2458 |
2459 | In the example above the generic function 'moo' expects exactly four
2460 | parameters in the following sequence:
2461 |
2462 | (1) String
2463 | (2) Vector
2464 | (3) Scalar
2465 | (4) Scalar
2466 |
2467 | Note16: The 'Z' or no parameter option may not be used in conjunction
2468 | with any other type option in a parameter sequence. When incorporated
2469 | in the parameter sequence list, the 'No Parameter' option indicates
2470 | that the function may be invoked without any parameters being passed.
2471 | For more information refer to the section: 'Zero Parameter Functions'
2472 |
2473 |
2474 | (4) igeneric_function II
2475 | This interface is identical to the igeneric_function, in that in can
2476 | consume an arbitrary number of parameters of varying type, but the
2477 | difference being that the function returns a string and as such is
2478 | treated as a string when invoked within expressions. As a result the
2479 | function call can alias a string and interact with other strings in
2480 | situations such as concatenation and equality operations.
2481 |
2482 | String <-- function(i_0, i_1, i_2....., i_N)
2483 |
2484 |
2485 | The following example defines a generic function named 'toupper' with
2486 | the string return type function operator being explicitly overridden:
2487 |
2488 | template
2489 | struct toupper final : public exprtk::igeneric_function
2490 | {
2491 | typedef exprtk::igeneric_function igenfunct_t;
2492 | typedef typename igenfunct_t::generic_type generic_t;
2493 | typedef typename igenfunct_t::parameter_list_t parameter_list_t;
2494 | typedef typename generic_t::string_view string_t;
2495 |
2496 | toupper()
2497 | : exprtk::igeneric_function("S",igenfunct_t::e_rtrn_string)
2498 | {}
2499 |
2500 | inline T operator()(std::string& result,
2501 | parameter_list_t parameters) override
2502 | {
2503 | result.clear();
2504 |
2505 | string_t string(parameters[0]);
2506 |
2507 | for (std::size_t i = 0; i < string.size(); ++i)
2508 | {
2509 | result += std::toupper(string[i]);
2510 | }
2511 |
2512 | return T(0);
2513 | }
2514 | };
2515 |
2516 |
2517 | In the example above the generic function 'toupper' expects only one
2518 | input parameter of type string, as noted by the parameter sequence
2519 | string passed during the constructor. Furthermore a second parameter
2520 | is passed to the constructor indicating that it should be treated as a
2521 | string returning function - by default it is assumed to be a scalar
2522 | returning function.
2523 |
2524 | When executed, the function will return as a result a copy of the
2525 | input string converted to uppercase form. An example expression using
2526 | the toupper function registered as the symbol 'toupper' is as follows:
2527 |
2528 | "'ABCDEF' == toupper('aBc') + toupper('DeF')"
2529 |
2530 |
2531 | Note17: When adding a string type returning generic function to a
2532 | symbol table the 'add_function' is invoked. The example below
2533 | demonstrates how this can be done:
2534 |
2535 | toupper tu;
2536 |
2537 | exprtk::symbol_table symbol_table;
2538 |
2539 | symbol_table.add_function("toupper",tu);
2540 |
2541 |
2542 | Note18: Two further refinements to the type checking facility are the
2543 | possibilities of a variable number of common types which can be
2544 | accomplished by using a wildcard '' and a special 'any type' which is
2545 | done using the '?' character. It should be noted that the wildcard
2546 | operator is associated with the previous type in the sequence and
2547 | implies one or more of that type.
2548 |
2549 | template
2550 | struct zoo final : public exprtk::igeneric_function
2551 | {
2552 | typedef typename exprtk::igeneric_function::parameter_list_t
2553 | parameter_list_t;
2554 |
2555 | zoo()
2556 | : exprtk::igeneric_function("SVTV?")
2557 | {}
2558 |
2559 | inline T operator()(parameter_list_t parameters) override
2560 | {
2561 | ...
2562 | }
2563 | };
2564 |
2565 |
2566 | In the example above the generic function 'zoo' expects at least five
2567 | parameters in the following sequence:
2568 |
2569 | (1) String
2570 | (2) Vector
2571 | (3) One or more Scalars
2572 | (4) Vector
2573 | (5) Any type (one type of either a scalar, vector or string)
2574 |
2575 |
2576 | A final piece of type checking functionality is available for the
2577 | scenarios where a single function name is intended to be used for
2578 | multiple distinct parameter sequences, another name for this feature
2579 | is function overloading. The parameter sequences are passed to the
2580 | constructor as a single string delimited by the pipe '|' character.
2581 | Two specific overrides of the function operator are provided one for
2582 | standard generic functions and one for string returning functions. The
2583 | overrides are as follows:
2584 |
2585 | // Scalar <-- function(psi,i_0,i_1,....,i_N)
2586 | inline T operator()(const std::size_t& ps_index,
2587 | parameter_list_t parameters)
2588 | {
2589 | ...
2590 | }
2591 |
2592 | // String <-- function(psi,i_0,i_1,....,i_N)
2593 | inline T operator()(const std::size_t& ps_index,
2594 | std::string& result,
2595 | parameter_list_t parameters)
2596 | {
2597 | ...
2598 | }
2599 |
2600 |
2601 | When the function operator is invoked the 'ps_index' parameter will
2602 | have as its value the index of the parameter sequence that matches the
2603 | specific invocation. This way complex and time consuming type checking
2604 | conditions need not be executed in the function itself but rather a
2605 | simple and efficient dispatch to a specific implementation for that
2606 | particular parameter sequence can be performed.
2607 |
2608 | template
2609 | struct roo final : public exprtk::igeneric_function
2610 | {
2611 | typedef typename exprtk::igeneric_function::parameter_list_t
2612 | parameter_list_t;
2613 |
2614 | moo()
2615 | : exprtk::igeneric_function("SVTT|SS|TTV|S?VS")
2616 | {}
2617 |
2618 | inline T operator()(const std::size_t& ps_index,
2619 | parameter_list_t parameters) override
2620 | {
2621 | ...
2622 | }
2623 | };
2624 |
2625 |
2626 | In the example above there are four distinct parameter sequences that
2627 | can be processed by the generic function 'roo'. Any other parameter
2628 | sequences will cause a compilation error. The four valid sequences are
2629 | as follows:
2630 |
2631 | Sequence-0 Sequence-1 Sequence-2 Sequence-3
2632 | 'SVTT' 'SS' 'TTV' 'S?VS'
2633 | (1) String (1) String (1) Scalar (1) String
2634 | (2) Vector (2) String (2) Scalar (2) Any Type
2635 | (3) Scalar (3) Vector (3) One or more Vectors
2636 | (4) Scalar (4) String
2637 |
2638 |
2639 | (5) igeneric_function III
2640 | In this section we will discuss an extension of the igeneric_function
2641 | interface that will allow for the overloading of a user defined custom
2642 | function, where by it can return either a scalar or string value type
2643 | depending on the input parameter sequence with which the function is
2644 | invoked.
2645 |
2646 | template
2647 | struct foo final : public exprtk::igeneric_function
2648 | {
2649 | typedef typename exprtk::igeneric_function::parameter_list_t
2650 | parameter_list_t;
2651 |
2652 | foo()
2653 | : exprtk::igeneric_function
2654 | (
2655 | "T:T|S:TS",
2656 | igfun_t::e_rtrn_overload
2657 | )
2658 | {}
2659 |
2660 | // Scalar value returning invocations
2661 | inline T operator()(const std::size_t& ps_index,
2662 | parameter_list_t parameters) override
2663 | {
2664 | ...
2665 | }
2666 |
2667 | // String value returning invocations
2668 | inline T operator()(const std::size_t& ps_index,
2669 | std::string& result,
2670 | parameter_list_t& parameters) override
2671 | {
2672 | ...
2673 | }
2674 | };
2675 |
2676 |
2677 | In the example above the custom user defined function "foo" can be
2678 | invoked by using either one of two input parameter sequences, which
2679 | are defined as follows:
2680 |
2681 | Sequence-0 Sequence-1
2682 | 'T' -> T 'TS' -> S
2683 | (1) Scalar (1) Scalar
2684 | (2) String
2685 |
2686 |
2687 | The parameter sequence definitions are identical to the previously
2688 | defined igeneric_function, with the exception of the inclusion of the
2689 | return type - which can only be either a scalar T or a string S.
2690 |
2691 |
2692 | (6) function_compositor
2693 | The function compositor is a factory that allows one to define and
2694 | construct a function using ExprTk syntax. The functions are limited to
2695 | returning a single scalar value and consuming up to six parameters as
2696 | input.
2697 |
2698 | All composited functions are registered with a symbol table, allowing
2699 | them to call other functions and use variables that have been
2700 | registered with the symbol table instance. Furthermore the functions
2701 | can be recursive in nature due to the inherent function prototype
2702 | forwarding that occurs during construction. The following example
2703 | defines, by using two different methods, composited functions and
2704 | implicitly registering the functions with the denoted symbol table.
2705 |
2706 | typedef exprtk::symbol_table symbol_table_t;
2707 | typedef exprtk::function_compositor compositor_t;
2708 | typedef typename compositor_t::function function_t;
2709 |
2710 | T avogadro = T(6.022e23);
2711 |
2712 | symbol_table_t symbol_table;
2713 |
2714 | symbol_table.add_constant("avogadro", avogadro);
2715 |
2716 | compositor_t compositor(symbol_table);
2717 |
2718 | // Define function koo0(v1, v2) { ... }
2719 | compositor.add(
2720 | function_t("koo0"),
2721 | .vars("v1", "v2")
2722 | .expression
2723 | (
2724 | " 1 + cos(v1 * v2) / avogadro; "
2725 | ));
2726 |
2727 | // Define function koo1(x, y, z) { ... }
2728 | compositor.add(
2729 | function_t()
2730 | .name("koo1")
2731 | .var("x").var("y").var("z")
2732 | .expression
2733 | (
2734 | "1 + koo0(x * y, 3) / z;"
2735 | ));
2736 |
2737 |
2738 | A function compositor can also be instantiated without a symbol_table.
2739 | When this is the case an internal symbol_table is used for holding the
2740 | references to the composited functions.
2741 |
2742 | compositor_t compositor;
2743 |
2744 | // Define function koo2(v1, v2) { ... }
2745 | compositor.add(
2746 | function_t("koo2"),
2747 | .vars("v1", "v2", "v3")
2748 | .expression
2749 | ( " abs(v1 * v2) / v3; " ));
2750 |
2751 |
2752 | When wanting to reference functions from the compositor above in an
2753 | expression, the compositor's symbol_table will need to be registered
2754 | with the expression prior to compilation, as is demonstrated in the
2755 | following code:
2756 |
2757 | expression_t expression;
2758 | .
2759 | .
2760 | expression.register_symbol_table(compositor.symbol_table());
2761 |
2762 |
2763 | In the situation where more than one symbol table's contents will be
2764 | required by the functions being composited, then those symbol tables
2765 | can be registered as auxiliary symbol tables with the compositor:
2766 |
2767 | symbol_table_t global_symbol_table;
2768 | symbol_table_t local_symbol_table;
2769 | .
2770 | .
2771 | .
2772 | compositor_t compositor;
2773 |
2774 | compositor.add_auxiliary_symtab(global_symbol_table);
2775 | compositor.add_auxiliary_symtab(local_symbol_table );
2776 |
2777 | Note19: In the event, that two or more symbol tables contain similarly
2778 | named variables, vectors, strings or functions, the order of
2779 | registration with the compositor shall determine the symbol table from
2780 | which the target symbol will be referenced.
2781 |
2782 |
2783 | (7) Using Functions In Expressions
2784 | For the above denoted custom and composited functions to be used in an
2785 | expression, an instance of each function needs to be registered with a
2786 | symbol_table that has been associated with the expression instance.
2787 | The following demonstrates how all the pieces are put together:
2788 |
2789 | typedef exprtk::symbol_table symbol_table_t;
2790 | typedef exprtk::expression expression_t;
2791 | typedef exprtk::parser parser_t;
2792 | typedef exprtk::function_compositor compositor_t;
2793 | typedef typename compositor_t::function function_t;
2794 |
2795 | foo f;
2796 | boo b;
2797 | too t;
2798 | toupper tu;
2799 |
2800 | symbol_table_t symbol_table;
2801 | compositor_t compositor(symbol_table);
2802 |
2803 | symbol_table.add_function("foo",f);
2804 | symbol_table.add_function("boo",b);
2805 | symbol_table.add_function("too",t);
2806 |
2807 | symbol_table
2808 | .add_function("toupper", tu, symbol_table_t::e_ft_strfunc);
2809 |
2810 | compositor.add(
2811 | function_t("koo")
2812 | .var("v1")
2813 | .var("v2")
2814 | .expression
2815 | (
2816 | "1 + cos(v1 * v2) / 3;"
2817 | ));
2818 |
2819 | expression_t expression;
2820 | expression.register_symbol_table(symbol_table);
2821 |
2822 | const std::string expression_str =
2823 | " if (foo(1,2,3) + boo(1) > boo(1/2, 2/3, 3/4, 4/5)) "
2824 | " koo(3,4); "
2825 | " else "
2826 | " too(2 * v1 + v2 / 3, 'abcdef'[2:4], 3.3); "
2827 | " "
2828 |
2829 | parser_t parser;
2830 | parser.compile(expression_str,expression);
2831 |
2832 | expression.value();
2833 |
2834 |
2835 | (8) Function Side-Effects
2836 | All function calls are assumed to have side-effects by default. This
2837 | assumption implicitly disables constant folding optimisations when all
2838 | parameters being passed to the function are deduced as being constants
2839 | at compile time.
2840 |
2841 | If it is certain that the function being registered does not have any
2842 | side-effects and can be correctly constant folded where appropriate,
2843 | then during the construction of the function the side-effect trait of
2844 | the function can be disabled.
2845 |
2846 | template
2847 | struct foo final : public exprtk::ifunction
2848 | {
2849 | foo() : exprtk::ifunction(3)
2850 | {
2851 | exprtk::disable_has_side_effects(this);
2852 | }
2853 |
2854 | T operator()(const T& v1, const T& v2, const T& v3) override
2855 | { ... }
2856 | };
2857 |
2858 |
2859 | (9) Zero Parameter Functions
2860 | When either an ifunction, ivararg_function or igeneric_function
2861 | derived type is defined with zero number of parameters, there are two
2862 | calling conventions within expressions that are allowed. For a
2863 | function named 'foo' with zero input parameters the calling styles are
2864 | as follows:
2865 |
2866 | (1) x + sin(foo()- 2) / y
2867 | (2) x + sin(foo - 2) / y
2868 |
2869 |
2870 | By default the zero parameter trait is disabled. In order to enable
2871 | it, a process similar to that of enabling of the side-effect trait is
2872 | carried out:
2873 |
2874 | template
2875 | struct foo final : public exprtk::ivararg_function
2876 | {
2877 | foo()
2878 | {
2879 | exprtk::enable_zero_parameters(this);
2880 | }
2881 |
2882 | inline T operator()(const std::vector& arglist) override
2883 | { ... }
2884 | };
2885 |
2886 |
2887 | Note20: For the igeneric_function type, there also needs to be a 'Z'
2888 | parameter sequence defined in order for the zero parameter trait to
2889 | properly take effect otherwise a compilation error will occur.
2890 |
2891 |
2892 | (10) Free Functions
2893 | The ExprTk symbol table supports the registration of free functions
2894 | and lambdas (anonymous functors) for use in expressions. The basic
2895 | requirements are similar to those found in ifunction derived user
2896 | defined functions. This includes support for free functions using
2897 | anywhere from zero up to fifteen input parameters of scalar type, with
2898 | a return type that is also scalar. Furthermore such functions will by
2899 | default be assumed to have side-effects and hence will not participate
2900 | in constant folding optimisations.
2901 |
2902 | In the following example, a one input parameter free function named
2903 | 'compute1', a two input parameter template free function named
2904 | 'compute2' and a three input parameter lambda named 'compute3' will be
2905 | registered with the given symbol_table instance:
2906 |
2907 | double compute1(double v0)
2908 | {
2909 | return 2.0 * std::abs(v0);
2910 | }
2911 |
2912 | template
2913 | T compute2(T v0, T v1)
2914 | {
2915 | return 2.0 * v0 + v1 / 3.0;
2916 | }
2917 | .
2918 | .
2919 | .
2920 |
2921 | typedef exprtk::symbol_table symbol_table_t;
2922 |
2923 | symbol_table_t symbol_table;
2924 |
2925 | symbol_table.add_function("compute1", compute1);
2926 | symbol_table.add_function("compute2", compute2);
2927 |
2928 | symbol_table.add_function(
2929 | "compute3",
2930 | [](double v0, double v1, double v2) -> double
2931 | { return v0 / v1 + v2; });
2932 |
2933 |
2934 | Note21: Similar to variables registered with symbol_table instances,
2935 | for any of the following function providers:
2936 |
2937 | 1. ifunction
2938 | 2. ivararg_function
2939 | 3. igeneric_function
2940 | 4. function_compositor
2941 | 5. Free function
2942 | 7. Lambda
2943 |
2944 |
2945 | Their instance lifetimes must exceed the symbol_tables and expressions
2946 | they are registered with. In the event that is not the case, the
2947 | expected result shall be undefined behaviour.
2948 |
2949 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2950 |
2951 | [SECTION 16 - EXPRESSION DEPENDENTS]
2952 | Any expression that is not a literal (aka constant) will have
2953 | dependencies. The types of 'dependencies' an expression can have are
2954 | as follows:
2955 |
2956 | (a) Variables
2957 | (b) Vectors
2958 | (c) Strings
2959 | (d) Functions
2960 | (e) Assignments
2961 |
2962 |
2963 | In the following example the denoted expression has its various
2964 | dependencies listed:
2965 |
2966 | z := abs(x + sin(2 * pi / y))
2967 |
2968 | (a) Variables: x, y, z and pi
2969 | (b) Functions: abs, sin
2970 | (c) Assignments: z
2971 |
2972 |
2973 | ExprTk allows for the derivation of expression dependencies via the
2974 | 'dependent_entity_collector' (DEC). When activated either through
2975 | 'compile_options' at the construction of the parser or through calls
2976 | to enabler methods just prior to compilation, the DEC will proceed to
2977 | collect any of the relevant types that are encountered during the
2978 | parsing phase. Once the compilation process has successfully
2979 | completed, the caller can then obtain a list of symbols and their
2980 | associated types from the DEC.
2981 |
2982 | The kinds of questions one can ask regarding the dependent entities
2983 | within an expression are as follows:
2984 |
2985 | * What user defined variables, vectors or strings are used?
2986 | * What functions or custom user functions are used?
2987 | * Which variables, vectors or strings have values assigned to them?
2988 |
2989 |
2990 | The following example demonstrates usage of the DEC in determining the
2991 | dependents of the given expression:
2992 |
2993 | typedef typename parser_t::
2994 | dependent_entity_collector::symbol_t symbol_t;
2995 |
2996 | const std::string expression_string =
2997 | "z := abs(x + sin(2 * pi / y))"
2998 |
2999 | T x,y,z;
3000 |
3001 | parser_t parser;
3002 | symbol_table_t symbol_table;
3003 |
3004 | symbol_table.add_variable("x",x);
3005 | symbol_table.add_variable("y",y);
3006 | symbol_table.add_variable("z",z);
3007 |
3008 | expression_t expression;
3009 | expression.register_symbol_table(symbol_table);
3010 |
3011 | // Collect only variable and function symbols
3012 | parser.dec().collect_variables() = true;
3013 | parser.dec().collect_functions() = true;
3014 |
3015 | if (!parser.compile(expression_string,expression))
3016 | {
3017 | // error....
3018 | }
3019 |
3020 | std::deque symbol_list;
3021 |
3022 | parser.dec().symbols(symbol_list);
3023 |
3024 | for (std::size_t i = 0; i < symbol_list.size(); ++i)
3025 | {
3026 | const symbol_t& symbol = symbol_list[i];
3027 |
3028 | switch (symbol.second)
3029 | {
3030 | case parser_t::e_st_variable : ... break;
3031 | case parser_t::e_st_vector : ... break;
3032 | case parser_t::e_st_string : ... break;
3033 | case parser_t::e_st_function : ... break;
3034 | }
3035 | }
3036 |
3037 |
3038 | Note22: The 'symbol_t' type is a std::pair comprising of the symbol
3039 | name (std::string) and the associated type of the symbol as denoted by
3040 | the cases in the switch statement.
3041 |
3042 | Having particular symbols (variable or function) present in an
3043 | expression is one form of dependency. Another and just as interesting
3044 | and important type of dependency is that of assignments. Assignments
3045 | are the set of dependent symbols that 'may' have their values modified
3046 | within an expression. The following are example expressions and their
3047 | associated assignments:
3048 |
3049 | Assignments Expression
3050 | (1) x x := y + z
3051 | (2) x, y x += y += z
3052 | (3) x, y, z x := y += sin(z := w + 2)
3053 | (4) w, z if (x > y, z := x + 2, w := 'A String')
3054 | (5) None x + y + z
3055 |
3056 |
3057 | Note23: In expression 4, both variables 'w' and 'z' are denoted as
3058 | being assignments even though only one of them can ever be modified at
3059 | the time of evaluation. Furthermore the determination of which of the
3060 | two variables the modification will occur upon can only be known with
3061 | certainty at evaluation time and not beforehand, hence both are listed
3062 | as being candidates for assignment.
3063 |
3064 | The following builds upon the previous example demonstrating the usage
3065 | of the DEC in determining the 'assignments' of the given expression:
3066 |
3067 | // Collect assignments
3068 | parser.dec().collect_assignments() = true;
3069 |
3070 | if (!parser.compile(expression_string,expression))
3071 | {
3072 | // error....
3073 | }
3074 |
3075 | std::deque symbol_list;
3076 |
3077 | parser.dec().assignment_symbols(symbol_list);
3078 |
3079 | for (std::size_t i = 0; i < symbol_list.size(); ++i)
3080 | {
3081 | symbol_t& symbol = symbol_list[i];
3082 |
3083 | switch (symbol.second)
3084 | {
3085 | case parser_t::e_st_variable : ... break;
3086 | case parser_t::e_st_vector : ... break;
3087 | case parser_t::e_st_string : ... break;
3088 | }
3089 | }
3090 |
3091 |
3092 | Note24: The assignments will only consist of variable types and as
3093 | such will not contain symbols denoting functions.
3094 |
3095 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3096 |
3097 | [SECTION 17 - HIERARCHIES OF SYMBOL TABLES]
3098 | Most situations will only require a single symbol_table instance to be
3099 | associated with a given expression instance.
3100 |
3101 | However as an expression can have more than one symbol table instance
3102 | associated with itself, when building more complex systems that
3103 | utilise many expressions where each can in turn utilise one or more
3104 | variables from a large set of potential variables, functions or
3105 | constants, it becomes evident that grouping variables into layers of
3106 | symbol_tables will simplify and streamline the overall process.
3107 |
3108 | A recommended hierarchy of symbol tables is the following:
3109 |
3110 | (a) Global constant value symbol table
3111 | (b) Global non side-effect functions symbol table
3112 | (c) Global variable symbol table
3113 | (d) Expression specific variable symbol table
3114 |
3115 |
3116 | (a) Global constant value symbol table
3117 | This symbol table will contain constant variables denoting immutable
3118 | values. These variables can be made available to all expressions, and
3119 | in turn expressions will assume the values themselves will never be
3120 | modified for the duration of the process run-time. Examples of such
3121 | variables are:
3122 |
3123 | (1) pi or e
3124 | (2) speed_of_light
3125 | (3) avogadro_number
3126 | (4) num_cpus
3127 |
3128 |
3129 | (b) Global non side-effect functions symbol table
3130 | This symbol table will contain only user defined functions that will
3131 | not incur any side-effects that are observable to any of the
3132 | expressions that invoke them. These functions shall be thread-safe or
3133 | threading invariant and will not maintain any form of state between
3134 | invocations. Examples of such functions are:
3135 |
3136 | (1) calc_volume_of_sphere(r)
3137 | (2) distance(x0,y0,x1,y1)
3138 |
3139 |
3140 | (c) Global variable symbol table
3141 | This symbol table will contain variables that will be accessible to
3142 | all associated expressions and will not be specific or exclusive to
3143 | any one expression. This variant differs from (a) in that the values
3144 | of the variables can change (or be updated) between evaluations of
3145 | expressions - but through properly scheduled evaluations are
3146 | guaranteed to never change during the evaluation of any dependent
3147 | expressions. Furthermore it is assumed that these variables will be
3148 | used in a read-only context and that no expressions will attempt to
3149 | modify these variables via assignments or other means.
3150 |
3151 | (1) price_of_stock_xyz
3152 | (2) outside_temperature or inside_temperature
3153 | (3) fuel_in_tank
3154 | (4) num_customers_in_store
3155 | (5) num_items_on_shelf
3156 |
3157 |
3158 | (d) Expression specific variable symbol table
3159 | This symbol_table is the most common form, and is used to store
3160 | variables that are specific and exclusive to a particular expression.
3161 | That is to say references to variables in this symbol_table will not
3162 | be part of another expression. Though it may be possible to have
3163 | expressions that contain the variables with the same name, in that
3164 | case those variables will be distinctly different. Which would mean if
3165 | a particular expression were to be compiled twice, each expression
3166 | would have its own unique symbol_table which in turn would have its
3167 | own instances of those variables. Examples of such variables could be:
3168 |
3169 | (1) x or y
3170 | (2) customer_name
3171 |
3172 |
3173 | The following is a diagram depicting a possible variant of the denoted
3174 | symbol table hierarchies. In the diagram there are two unique
3175 | expressions, each of which have a reference to the Global constant,
3176 | functions and variables symbol tables and an exclusive reference to a
3177 | local symbol table.
3178 |
3179 | +-------------------------+ +-------------------------+
3180 | | Global Constants | | Global Functions |
3181 | | Symbol Table | | Symbol Table |
3182 | +----o--o-----------------+ +--------------------o----+
3183 | | | |
3184 | | | +-------+
3185 | | +------------------->----------------------------+ |
3186 | | +----------------------------+ | |
3187 | | | Global Variables | | |
3188 | | +------o Symbol Table o-----+ | V
3189 | | | +----------------------------+ | | |
3190 | | | | | |
3191 | | | +----------------+ +----------------+ | | |
3192 | | | | Symbol Table 0 | | Symbol Table 1 | | V |
3193 | | | +--o-------------+ +--o-------------+ | | |
3194 | | | | | | | |
3195 | | | | | | | |
3196 | +--V--V----V---------+ +-V---------------V--+ | |
3197 | | Expression 0 | | Expression 1 |<--+--+
3198 | | '2 * sin(x) - y' | | 'k + abs(x - y)' |
3199 | +--------------------+ +--------------------+
3200 |
3201 |
3202 | Bringing all of the above together, in the following example the
3203 | hierarchy of symbol tables are instantiated and initialised. An
3204 | expression that makes use of various elements of each symbol table is
3205 | then compiled and later on evaluated:
3206 |
3207 | typedef exprtk::symbol_table symbol_table_t;
3208 | typedef exprtk::expression expression_t;
3209 |
3210 | // Setup global constants symbol table
3211 | symbol_table_t glbl_const_symbol_table;
3212 | glbl_const_symbtab.add_constants(); // pi, epsilon and inf
3213 | glbl_const_symbtab.add_constant("speed_of_light",299e6);
3214 | glbl_const_symbtab.add_constant("avogadro_number",6e23);
3215 |
3216 | // Setup global function symbol table
3217 | symbol_table_t glbl_funcs_symbol_table;
3218 | glbl_func_symbtab.add_function('distance',distance);
3219 | glbl_func_symbtab.add_function('calc_spherevol',calc_sphrvol);
3220 |
3221 | ......
3222 |
3223 | // Setup global variable symbol table
3224 | symbol_table_t glbl_variable_symbol_table;
3225 | glbl_variable_symbtab.add_variable('temp_outside',thermo.outside);
3226 | glbl_variable_symbtab.add_variable('temp_inside' ,thermo.inside );
3227 | glbl_variable_symbtab.add_variable('num_cstmrs',store.num_cstmrs);
3228 |
3229 | ......
3230 |
3231 | double x,y,z;
3232 |
3233 | // Setup expression specific symbol table
3234 | symbol_table_t symbol_table;
3235 | symbol_table.add_variable('x',x);
3236 | symbol_table.add_variable('y',y);
3237 | symbol_table.add_variable('z',z);
3238 |
3239 | expression_t expression;
3240 |
3241 | // Register the various symbol tables
3242 | expression
3243 | .register_symbol_table(symbol_table);
3244 |
3245 | expression
3246 | .register_symbol_table(glbl_funcs_symbol_table);
3247 |
3248 | expression
3249 | .register_symbol_table(glbl_const_symbol_table);
3250 |
3251 | expression
3252 | .register_symbol_table(glbl_variable_symbol_table);
3253 |
3254 | const std::string expression_str =
3255 | "abs(temp_inside - temp_outside) + 2 * speed_of_light / x"
3256 |
3257 | parser_t parser;
3258 | parser.compile(expression_str,expression);
3259 |
3260 | ......
3261 |
3262 | while (keep_evaluating)
3263 | {
3264 | ....
3265 |
3266 | T result = expression.value();
3267 |
3268 | ....
3269 | }
3270 |
3271 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3272 |
3273 | [SECTION 18 - UNKNOWN UNKNOWNS]
3274 | In this section we will discuss the process of handling expressions
3275 | with a mix of known and unknown variables. Initially a discussion into
3276 | the types of expressions that exist will be provided, then a series of
3277 | possible solutions will be presented for each scenario.
3278 |
3279 | When parsing an expression, there may be situations where one is not
3280 | fully aware of what if any variables will be used prior to the
3281 | expression being compiled.
3282 |
3283 | This can become problematic, as in the default scenario it is assumed
3284 | the symbol_table that is registered with the expression instance will
3285 | already possess the externally available variables, functions and
3286 | constants needed during the compilation of the expression.
3287 |
3288 | In the event there are symbols in the expression that can't be mapped
3289 | to either a reserved word, or located in the associated
3290 | symbol_table(s), an "Undefined symbol" error will be raised and the
3291 | compilation process will fail.
3292 |
3293 | The numerous scenarios that can occur when compiling an expression
3294 | with ExprTk generally fall into one of the following three categories:
3295 |
3296 | (a) No external variables
3297 | (b) Predetermined set of external variables
3298 | (c) Unknown set of variables
3299 |
3300 |
3301 | (a) No external variables
3302 | These are expressions that contain no external variables but may
3303 | contain local variables. As local variables cannot be accessed
3304 | externally from the expression, it is assumed that such expressions
3305 | will not have a need for a symbol_table and furthermore expressions
3306 | which don't make use of functions that have side-effects will be
3307 | evaluated completely at compile time resulting in a constant return
3308 | value. The following are examples of such expressions:
3309 |
3310 | (1) 1 + 2
3311 | (2) var x := 3; 2 * x - 3
3312 | (3) var x := 3; var y := abs(x - 8); x - y / 7
3313 |
3314 |
3315 | (b) Predetermined set of external variables
3316 | These are expressions that are comprised of externally available
3317 | variables and functions and will only compile successfully if the
3318 | symbols that correspond to the variables and functions are already
3319 | defined in their associated symbol_table(s). This is by far the most
3320 | common scenario when using ExprTk.
3321 |
3322 | As an example, one may have three external variables: x, y and z which
3323 | have been registered with the associated symbol_table, and will then
3324 | need to compile and evaluate expressions comprised of any subset of
3325 | these three variables. The following are a few examples of such
3326 | expressions:
3327 |
3328 | (1) 1 + x
3329 | (2) x / y
3330 | (3) 2 * x * y / z
3331 |
3332 |
3333 | In this scenario one can use the 'dependent_entity_collector'
3334 | component as described in [Section 16] to further determine which of
3335 | the registered variables were actually used in the given expression.
3336 | As an example once the set of utilised variables are known, any
3337 | further 'attention' can be restricted to only those variables when
3338 | evaluating the expression. This can be quite useful when dealing with
3339 | expressions that can draw from a set of hundreds or even thousands of
3340 | variables.
3341 |
3342 |
3343 | (c) Unknown set of variables
3344 | These are expressions that are comprised of symbols other than the
3345 | standard ExprTk reserved words or what has been registered with their
3346 | associated symbol_table, and will normally fail compilation due to the
3347 | associated symbol_table not having a reference to them. As such this
3348 | scenario can be seen as a combination of scenario B, where one may
3349 | have a symbol_table with registered variables, but would also like to
3350 | handle the situation of variables that aren't present in said
3351 | symbol_table.
3352 |
3353 | When dealing with expressions of category (c), one must perform all of
3354 | the following:
3355 |
3356 | (1) Determine the variables used in the expression
3357 | (2) Populate a symbol_table(s) with the entities from (1)
3358 | (3) Compile the expression
3359 | (4) Provide a means by which the entities from (1) can be modified
3360 |
3361 |
3362 | Depending on the nature of processing, steps (1) and (2) can be done
3363 | either independently of each other or combined into one. The following
3364 | example will initially look at solving the problem of unknown
3365 | variables with the latter method using the 'unknown_symbol_resolver'
3366 | component.
3367 |
3368 | typedef exprtk::symbol_table symbol_table_t;
3369 | typedef exprtk::expression expression_t;
3370 | typedef exprtk::parser parser_t;
3371 |
3372 | T x = T(123.456);
3373 | T y = T(789.123);
3374 |
3375 | symbol_table_t unknown_var_symbol_table;
3376 |
3377 | symbol_table_t symbol_table;
3378 | symbol_table.add_variable("x",x);
3379 | symbol_table.add_variable("y",y);
3380 |
3381 | expression_t expression;
3382 | expression.register_symbol_table(unknown_var_symbol_table);
3383 | expression.register_symbol_table(symbol_table);
3384 |
3385 | parser_t parser;
3386 | parser.enable_unknown_symbol_resolver();
3387 |
3388 | const std::string expression_str = "x + abs(y / 3k) * z + 2"
3389 |
3390 | parser.compile(expression_str,expression);
3391 |
3392 |
3393 | In the example above, the symbols 'k' and 'z' will be treated as
3394 | unknown symbols. The parser in the example is set to handle unknown
3395 | symbols using the built-in default unknown_symbol_resolver (USR). The
3396 | default USR will automatically resolve any unknown symbols as a
3397 | variable (scalar type). The new variables will be added to the primary
3398 | symbol_table, which in this case is the 'unknown_var_symbol_table'
3399 | instance. Once the compilation has completed successfully, the
3400 | variables that were resolved during compilation can be accessed from
3401 | the primary symbol_table using the 'get_variable_list' and
3402 | 'variable_ref' methods and then if needed can be modified accordingly
3403 | after which the expression itself can be evaluated.
3404 |
3405 | std::vectorstd::string variable_list;
3406 |
3407 | unknown_var_symbol_table.get_variable_list(variable_list);
3408 |
3409 | for (const auto& var_name : variable_list)
3410 | {
3411 | T& v = unknown_var_symbol_table.variable_ref(var_name);
3412 |
3413 | v = ...;
3414 | }
3415 |
3416 | ...
3417 |
3418 | expression.value();
3419 |
3420 |
3421 | Note25: As previously mentioned the default USR will automatically
3422 | assume any unknown symbol to be a valid scalar variable, and will then
3423 | proceed to add said symbol as a variable to the primary symbol_table
3424 | of the associated expression during the compilation process. However a
3425 | problem that may arise, is that expressions that are parsed with the
3426 | USR enabled, but contain 'typos' or otherwise syntactic errors may
3427 | inadvertently compile successfully due to the simplistic nature of the
3428 | default USR. The following are some example expressions:
3429 |
3430 | (1) 1 + abz(x + 1)
3431 | (2) sine(y / 2) - coz(3x)
3432 |
3433 |
3434 | The two expressions above contain misspelt symbols (abz, sine, coz)
3435 | which if implied multiplications and default USR are enabled during
3436 | compilation will result in them being assumed to be valid 'variables',
3437 | which obviously is not the intended outcome by the user. A possible
3438 | solution to this problem is for one to implement their own specific
3439 | USR that will perform a user defined business logic in determining if
3440 | an encountered unknown symbol should be treated as a variable or if it
3441 | should raise a compilation error. The following example demonstrates a
3442 | simple user defined USR:
3443 |
3444 | typedef exprtk::symbol_table symbol_table_t;
3445 | typedef exprtk::expression expression_t;
3446 | typedef exprtk::parser parser_t;
3447 |
3448 | template
3449 | struct my_usr final : public parser_t::unknown_symbol_resolver
3450 | {
3451 | typedef typename parser_t::unknown_symbol_resolver usr_t;
3452 |
3453 | bool process(const std::string& unknown_symbol,
3454 | typename usr_t::usr_symbol_type& st,
3455 | T& default_value,
3456 | std::string& error_message) override
3457 | {
3458 | if (0 != unknown_symbol.find("var_"))
3459 | {
3460 | error_message = "Invalid symbol: " + unknown_symbol;
3461 | return false;
3462 | }
3463 |
3464 | st = usr_t::e_usr_variable_type;
3465 | default_value = T(123.123);
3466 |
3467 | return true;
3468 | }
3469 | };
3470 |
3471 | ...
3472 |
3473 | T x = T(123.456);
3474 | T y = T(789.123);
3475 |
3476 | symbol_table_t unknown_var_symbol_table;
3477 |
3478 | symbol_table_t symbol_table;
3479 | symbol_table.add_variable("x",x);
3480 | symbol_table.add_variable("y",y);
3481 |
3482 | expression_t expression;
3483 | expression.register_symbol_table(unknown_var_symbol_table);
3484 | expression.register_symbol_table(symbol_table);
3485 |
3486 | my_usr musr;
3487 |
3488 | parser_t parser;
3489 | parser.enable_unknown_symbol_resolver(&musr);
3490 |
3491 | std::string expression_str = "var_x + abs(var_y - 3) * var_z"
3492 |
3493 | parser.compile(expression_str,expression);
3494 |
3495 |
3496 | In the example above, a user specified USR is defined, and is
3497 | registered with the parser enabling the USR functionality.
3498 | Subsequently during the compilation process when an unknown symbol is
3499 | encountered, the USR's process method will be invoked. The USR in the
3500 | example will only 'accept' unknown symbols that have a prefix of
3501 | 'var_' as being valid variables, all other unknown symbols will result
3502 | in a compilation error being raised.
3503 |
3504 | In the example above the callback of the USR that is invoked during
3505 | the unknown symbol resolution process only allows for scalar variables
3506 | to be defined and resolved - as that is the simplest and most common
3507 | form.
3508 |
3509 | There is a further extended version of the callback that can be
3510 | overridden that will allow for more control and choice over the type
3511 | of symbol being resolved. The following is an example definition of
3512 | said extended callback:
3513 |
3514 | template
3515 | struct my_usr final : public parser_t::unknown_symbol_resolver
3516 | {
3517 | typedef typename parser_t::unknown_symbol_resolver usr_t;
3518 |
3519 | my_usr()
3520 | : usr_t(usr_t::e_usrmode_extended)
3521 | {}
3522 |
3523 | bool process(const std::string& unknown_symbol,
3524 | symbol_table_t& symbol_table,
3525 | std::string& error_message) override
3526 | {
3527 | bool result = false;
3528 |
3529 | if (0 == unknown_symbol.find("var_"))
3530 | {
3531 | // Default value of zero
3532 | result = symbol_table.create_variable(unknown_symbol,0);
3533 |
3534 | if (!result)
3535 | {
3536 | error_message = "Failed to create variable..."
3537 | }
3538 | }
3539 | else if (0 == unknown_symbol.find("str_"))
3540 | {
3541 | // Default value of empty string
3542 | result = symbol_table.create_stringvar(unknown_symbol,"");
3543 |
3544 | if (!result)
3545 | {
3546 | error_message = "Failed to create string variable..."
3547 | }
3548 | }
3549 | else
3550 | error_message = "Indeterminable symbol type."
3551 |
3552 | return result;
3553 | }
3554 | };
3555 |
3556 |
3557 | In the example above, the USR callback when invoked will pass the
3558 | primary symbol table associated with the expression being parsed. The
3559 | symbol resolution business logic can then determine under what
3560 | conditions a symbol will be resolved including its type (scalar,
3561 | string, vector etc) and default value. When the callback successfully
3562 | returns the symbol parsing and resolution process will again be
3563 | executed by the parser. The idea here is that given the primary symbol
3564 | table will now have the previously detected unknown symbol registered,
3565 | it will be correctly resolved and the general parsing processing can
3566 | then resume as per normal.
3567 |
3568 | Note26: In order to have the USR's extended mode callback be invoked
3569 | it is necessary to pass the e_usrmode_extended enum value during the
3570 | constructor of the user defined USR.
3571 |
3572 | Note27: The primary symbol table for an expression is the first symbol
3573 | table to be registered with that instance of the expression.
3574 |
3575 | Note28: For a successful symbol resolution using the normal USR all of
3576 | the following are required:
3577 |
3578 | (1) Only if successful shall the process method return TRUE
3579 | (2) The default_value parameter will have been set
3580 | (3) The error_message parameter will be empty
3581 | (4) usr_symbol_type input parameter field will be set to either:
3582 | () e_usr_variable_type
3583 | () e_usr_constant_type
3584 |
3585 | Note29: For a successful symbol resolution using the extended USR all
3586 | of the following are required:
3587 |
3588 | (1) Only if successful shall the process method return TRUE
3589 | (2) symbol_table parameter will have had the newly resolved
3590 | variable or string added to it
3591 | (3) error_message parameter will be empty
3592 |
3593 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3594 |
3595 | [SECTION 19 - ENABLING & DISABLING FEATURES]
3596 | The parser can be configured via its settings instance to either allow
3597 | or disallow certain features that are available within the ExprTk
3598 | grammar. The features fall into one of the following six categories:
3599 |
3600 | (1) Base Functions
3601 | (2) Control Flow Structures
3602 | (3) Logical Operators
3603 | (4) Arithmetic Operators
3604 | (5) Inequality Operators
3605 | (6) Assignment Operators
3606 |
3607 |
3608 | (1) Base Functions
3609 | The list of available base functions is as follows:
3610 |
3611 | abs, acos, acosh, asin, asinh, atan, atanh, atan2, avg, ceil,
3612 | clamp, cos, cosh, cot, csc, equal, erf, erfc, exp, expm1,
3613 | floor, frac, hypot, iclamp, like, log, log10, log2, logn,
3614 | log1p, mand, max, min, mod, mor, mul, ncdf, pow, root, round,
3615 | roundn, sec, sgn, sin, sinc, sinh, sqrt, sum, swap, tan, tanh,
3616 | trunc, not_equal, inrange, deg2grad, deg2rad, rad2deg, grad2deg
3617 |
3618 |
3619 | The above mentioned base functions can be either enabled or disabled
3620 | 'all' at once, as is demonstrated below:
3621 |
3622 | parser_t parser;
3623 | expression_t expression;
3624 |
3625 | parser.settings().disable_all_base_functions();
3626 |
3627 | parser
3628 | .compile("2 * abs(2 - 3)",expression); // compilation failure
3629 |
3630 | parser.settings().enable_all_base_functions();
3631 |
3632 | parser
3633 | .compile("2 * abs(2 - 3)",expression); // compilation success
3634 |
3635 |
3636 | One can also enable or disable specific base functions. The following
3637 | example demonstrates the disabling of the trigonometric functions
3638 | 'sin' and 'cos':
3639 |
3640 | parser_t parser;
3641 | expression_t expression;
3642 |
3643 | parser.settings()
3644 | .disable_base_function(settings_t::e_bf_sin)
3645 | .disable_base_function(settings_t::e_bf_cos);
3646 |
3647 | parser
3648 | .compile("(sin(x) / cos(x)) == tan(x)",expression); // failure
3649 |
3650 | parser.settings()
3651 | .enable_base_function(settings_t::e_bf_sin)
3652 | .enable_base_function(settings_t::e_bf_cos);
3653 |
3654 | parser
3655 | .compile("(sin(x) / cos(x)) == tan(x)",expression); // success
3656 |
3657 |
3658 | (2) Control Flow Structures
3659 | The list of available control flow structures is as follows:
3660 |
3661 | (a) If or If-Else
3662 | (b) Switch statement
3663 | (c) For Loop
3664 | (d) While Loop
3665 | (e) Repeat Loop
3666 |
3667 |
3668 | The above mentioned control flow structures can be either enabled
3669 | or disabled 'all' at once, as is demonstrated below:
3670 |
3671 | parser_t parser;
3672 | expression_t expression;
3673 |
3674 | const std::string program =
3675 | " var x := 0; "
3676 | " for (var i := 0; i < 10; i += 1) "
3677 | " { "
3678 | " x += i; "
3679 | " } "
3680 |
3681 | parser.settings().disable_all_control_structures();
3682 |
3683 | parser
3684 | .compile(program,expression); // compilation failure
3685 |
3686 | parser.settings().enable_all_control_structures();
3687 |
3688 | parser
3689 | .compile(program,expression); // compilation success
3690 |
3691 |
3692 | One can also enable or disable specific control flow structures. The
3693 | following example demonstrates the disabling of the for-loop control
3694 | flow structure:
3695 |
3696 | parser_t parser;
3697 | expression_t expression;
3698 |
3699 | const std::string program =
3700 | " var x := 0; "
3701 | " for (var i := 0; i < 10; i += 1) "
3702 | " { "
3703 | " x += i; "
3704 | " } "
3705 |
3706 | parser.settings()
3707 | .disable_control_structure(settings_t::e_ctrl_for_loop);
3708 |
3709 | parser
3710 | .compile(program,expression); // failure
3711 |
3712 | parser.settings()
3713 | .enable_control_structure(settings_t::e_ctrl_for_loop);
3714 |
3715 | parser
3716 | .compile(program,expression); // success
3717 |
3718 |
3719 | (3) Logical Operators
3720 | The list of available logical operators is as follows:
3721 |
3722 | and, nand, nor, not, or, xnor, xor, &, |
3723 |
3724 |
3725 | The above mentioned logical operators can be either enabled or
3726 | disabled 'all' at once, as is demonstrated below:
3727 |
3728 | parser_t parser;
3729 | expression_t expression;
3730 |
3731 | parser.settings().disable_all_logic_ops();
3732 |
3733 | parser
3734 | .compile("1 or not(0 and 1)",expression); // compilation failure
3735 |
3736 | parser.settings().enable_all_logic_ops();
3737 |
3738 | parser
3739 | .compile("1 or not(0 and 1)",expression); // compilation success
3740 |
3741 |
3742 | One can also enable or disable specific logical operators. The
3743 | following example demonstrates the disabling of the 'and' logical
3744 | operator:
3745 |
3746 | parser_t parser;
3747 | expression_t expression;
3748 |
3749 | parser.settings()
3750 | .disable_logic_operation(settings_t::e_logic_and);
3751 |
3752 | parser
3753 | .compile("1 or not(0 and 1)",expression); // failure
3754 |
3755 | parser.settings()
3756 | .enable_logic_operation(settings_t::e_logic_and);
3757 |
3758 | parser
3759 | .compile("1 or not(0 and 1)",expression); // success
3760 |
3761 |
3762 | (4) Arithmetic Operators
3763 | The list of available arithmetic operators is as follows:
3764 |
3765 | +, -, *, /, %, ^
3766 |
3767 |
3768 | The above mentioned arithmetic operators can be either enabled or
3769 | disabled 'all' at once, as is demonstrated below:
3770 |
3771 | parser_t parser;
3772 | expression_t expression;
3773 |
3774 | parser.settings().disable_all_arithmetic_ops();
3775 |
3776 | parser
3777 | .compile("1 + 2 / 3",expression); // compilation failure
3778 |
3779 | parser.settings().enable_all_arithmetic_ops();
3780 |
3781 | parser
3782 | .compile("1 + 2 / 3",expression); // compilation success
3783 |
3784 |
3785 | One can also enable or disable specific arithmetic operators. The
3786 | following example demonstrates the disabling of the addition '+'
3787 | arithmetic operator:
3788 |
3789 | parser_t parser;
3790 | expression_t expression;
3791 |
3792 | parser.settings()
3793 | .disable_arithmetic_operation(settings_t::e_arith_add);
3794 |
3795 | parser
3796 | .compile("1 + 2 / 3",expression); // failure
3797 |
3798 | parser.settings()
3799 | .enable_arithmetic_operation(settings_t::e_arith_add);
3800 |
3801 | parser
3802 | .compile("1 + 2 / 3",expression); // success
3803 |
3804 |
3805 | (5) Inequality Operators
3806 | The list of available inequality operators is as follows:
3807 |
3808 | <, <=, >, >=, ==, =, != <>
3809 |
3810 |
3811 | The above mentioned inequality operators can be either enabled or
3812 | disabled 'all' at once, as is demonstrated below:
3813 |
3814 | parser_t parser;
3815 | expression_t expression;
3816 |
3817 | parser.settings().disable_all_inequality_ops();
3818 |
3819 | parser
3820 | .compile("1 < 3",expression); // compilation failure
3821 |
3822 | parser.settings().enable_all_inequality_ops();
3823 |
3824 | parser
3825 | .compile("1 < 3",expression); // compilation success
3826 |
3827 |
3828 | One can also enable or disable specific inequality operators. The
3829 | following example demonstrates the disabling of the less-than '<'
3830 | inequality operator:
3831 |
3832 | parser_t parser;
3833 | expression_t expression;
3834 |
3835 | parser.settings()
3836 | .disable_inequality_operation(settings_t::e_ineq_lt);
3837 |
3838 | parser
3839 | .compile("1 < 3",expression); // failure
3840 |
3841 | parser.settings()
3842 | .enable_inequality_operation(settings_t::e_ineq_lt);
3843 |
3844 | parser
3845 | .compile("1 < 3",expression); // success
3846 |
3847 |
3848 | (6) Assignment Operators
3849 | The list of available assignment operators is as follows:
3850 |
3851 | :=, +=, -=, *=, /=, %=
3852 |
3853 |
3854 | The above mentioned assignment operators can be either enabled or
3855 | disabled 'all' at once, as is demonstrated below:
3856 |
3857 | T x = T(0);
3858 |
3859 | parser_t parser;
3860 | expression_t expression;
3861 | symbol_table_t symbol_table;
3862 |
3863 | symbol_table.add_variable("x",x);
3864 |
3865 | expression.register_symbol_table(symbol_table);
3866 |
3867 | parser.settings().disable_all_assignment_ops();
3868 |
3869 | parser
3870 | .compile("x := 3",expression); // compilation failure
3871 |
3872 | parser.settings().enable_all_assignment_ops();
3873 |
3874 | parser
3875 | .compile("x := 3",expression); // compilation success
3876 |
3877 |
3878 | One can also enable or disable specific assignment operators. The
3879 | following example demonstrates the disabling of the '+=' addition
3880 | assignment operator:
3881 |
3882 | T x = T(0);
3883 |
3884 | parser_t parser;
3885 | expression_t expression;
3886 | symbol_table_t symbol_table;
3887 |
3888 | symbol_table.add_variable("x",x);
3889 |
3890 | expression.register_symbol_table(symbol_table);
3891 |
3892 | parser.settings()
3893 | .disable_assignment_operation(settings_t::e_assign_addass);
3894 |
3895 | parser
3896 | .compile("x += 3",expression); // failure
3897 |
3898 | parser.settings()
3899 | .enable_assignment_operation(settings_t::e_assign_addass);
3900 |
3901 | parser
3902 | .compile("x += 3",expression); // success
3903 |
3904 |
3905 | Note30: In the event of a base function being disabled, one can
3906 | redefine the base function using the standard custom function
3907 | definition process. In the following example the 'sin' function is
3908 | disabled then redefined as a function taking degree input.
3909 |
3910 | template
3911 | struct sine_deg final : public exprtk::ifunction
3912 | {
3913 | sine_deg() : exprtk::ifunction(1) {}
3914 |
3915 | inline T operator()(const T& v) override
3916 | {
3917 | const T pi = exprtk::details::numeric::constant::pi;
3918 | return std::sin((v * T(pi)) / T(180));
3919 | }
3920 | };
3921 |
3922 | ...
3923 |
3924 | typedef exprtk::symbol_table symbol_table_t;
3925 | typedef exprtk::expression expression_t;
3926 | typedef exprtk::parser parser_t;
3927 |
3928 | typedef typename parser_t::settings_store settings_t;
3929 |
3930 | sine_deg sine;
3931 |
3932 | symbol_table.add_reserved_function("sin",sine);
3933 |
3934 | expression_t expression;
3935 |
3936 | expression.register_symbol_table(symbol_table);
3937 |
3938 | parser_t parser;
3939 |
3940 | parser.settings()
3941 | .disable_base_function(settings_t::e_bf_sin);
3942 |
3943 | parser.compile("1 + sin(30)",expression);
3944 |
3945 |
3946 | In the example above, the custom 'sin' function is registered with the
3947 | symbol_table using the method 'add_reserved_function'. This is done so
3948 | as to bypass the checks for reserved words that are carried out on the
3949 | provided symbol names when calling the standard 'add_function' method.
3950 | Normally if a user specified symbol name conflicts with any of the
3951 | ExprTk reserved words, the add_function call will fail.
3952 |
3953 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3954 |
3955 | [SECTION 20 - EXPRESSION RETURN VALUES]
3956 | ExprTk expressions can return immediately from any point by utilising
3957 | the return call. Furthermore the return call can be used to transfer
3958 | out multiple return values from within the expression.
3959 |
3960 | If an expression evaluation exits using a return point, the result of
3961 | the call to the 'value' method will be NaN, and it is expected that
3962 | the return values will be available from the results_context.
3963 |
3964 | In the following example there are three return points in the
3965 | expression. If neither of the return points are hit, then the
3966 | expression will return normally.
3967 |
3968 | const std::string expression_string =
3969 | " if (x < y) "
3970 | " return [x + 1,'return-call 1']; "
3971 | " else if (x > y) "
3972 | " return [y / 2, y + 1, 'return-call 2']; "
3973 | " else if (equal(x,y)) "
3974 | " x + y; "
3975 | " return [x, y, x + y, x - y, 'return-call 3'] "
3976 |
3977 | typedef exprtk::symbol_table symbol_table_t;
3978 | typedef exprtk::expression expression_t;
3979 | typedef exprtk::parser parser_t;
3980 |
3981 | symbol_table_t symbol_table;
3982 | expression_t expression;
3983 | parser_t parser;
3984 |
3985 | double x = 0;
3986 | double y = 0;
3987 |
3988 | symbol_table.add_variable("x",x);
3989 | symbol_table.add_variable("y",y);
3990 |
3991 | expression.register_symbol_table(symbol_table);
3992 |
3993 | parser.compile(expression_string,expression);
3994 |
3995 | T result = expression.value();
3996 |
3997 | if (expression.return_invoked())
3998 | {
3999 | typedef exprtk::results_context results_context_t;
4000 | typedef typename results_context_t::type_store_t type_t;
4001 | typedef typename type_t::scalar_view scalar_t;
4002 | typedef typename type_t::vector_view vector_t;
4003 | typedef typename type_t::string_view string_t;
4004 |
4005 | const results_context_t& results = expression.results();
4006 |
4007 | for (std::size_t i = 0; i < results.count(); ++i)
4008 | {
4009 | type_t t = results[i];
4010 |
4011 | switch (t.type)
4012 | {
4013 | case type_t::e_scalar : ...
4014 | break;
4015 |
4016 | case type_t::e_vector : ...
4017 | break;
4018 |
4019 | case type_t::e_string : ...
4020 | break;
4021 |
4022 | default : continue;
4023 | }
4024 | }
4025 |
4026 |
4027 | In the above example, there are three possible "return" points and one
4028 | regular result. Only one of the four paths can ever be realised. Hence
4029 | it is necessary to capture the result of the expression value method
4030 | call. In the event, the call to return_invoked is not true then the
4031 | non-return code path was executed and the result of the evaluation
4032 | will be the result of the expression's value method.
4033 |
4034 | Note31: Processing of the return results is similar to that of the
4035 | generic function call parameters.
4036 |
4037 | The results_context provides getter methods for each of the possible
4038 | return types (scalar, vector and string) and can be used as follows:
4039 |
4040 | typedef exprtk::symbol_table symbol_table_t;
4041 | typedef exprtk::expression expression_t;
4042 | typedef exprtk::parser parser_t;
4043 |
4044 | const std::string expression_str =
4045 | " if (x > y) "
4046 | " return [1]; "
4047 | " else "
4048 | " return [ x, x + y, 2 * v, s + 'world' ]; "
4049 |
4050 | symbol_table_t symbol_table;
4051 | expression_t expression;
4052 | parser_t parser;
4053 |
4054 | symbol_table.add_variable ("x", x);
4055 | symbol_table.add_variable ("y", y);
4056 | symbol_table.add_variable ("z", z);
4057 | symbol_table.add_vector ("v", v);
4058 | symbol_table.add_stringvar("s", s);
4059 |
4060 | parser.compile(expression_str, expression);
4061 |
4062 | expression.value();
4063 |
4064 | typedef exprtk::results_context results_context_t;
4065 | const results_context_t& results = expression.results();
4066 |
4067 | if (results.count() == 4)
4068 | {
4069 | T result_x0;
4070 | T result_x1;
4071 | std::string result_s;
4072 | std::vector result_v;
4073 |
4074 | results.get_scalar(0, result_x0);
4075 | results.get_scalar(1, result_x1);
4076 | results.get_string(3, result_s );
4077 | results.get_vector(2, result_v );
4078 | }
4079 |
4080 |
4081 | It is however recommended that if there is to be only a single flow of
4082 | execution through the expression, that the simpler approach of
4083 | registering external variables of appropriate type be used.
4084 |
4085 | This method simply requires the variables that are to hold the various
4086 | results that are to be computed within the expression to be registered
4087 | with an associated symbol_table instance. Then within the expression
4088 | itself to have the result variables be assigned the appropriate
4089 | values.
4090 |
4091 | typedef exprtk::symbol_table symbol_table_t;
4092 | typedef exprtk::expression expression_t;
4093 | typedef exprtk::parser parser_t;
4094 |
4095 | const std::string expression_string =
4096 | " var x := 123.456; "
4097 | " var s := 'ijk'; "
4098 | " result0 := x + 78.90; "
4099 | " result1 := s + '123' "
4100 |
4101 | double result0;
4102 | std::string result1;
4103 |
4104 | symbol_table_t symbol_table;
4105 | symbol_table.add_variable ("result0",result0);
4106 | symbol_table.add_stringvar("result1",result1);
4107 |
4108 | expression_t expression;
4109 | expression.register_symbol_table(symbol_table);
4110 |
4111 | parser_t parser;
4112 | parser.compile(expression_string,expression);
4113 |
4114 | expression.value();
4115 |
4116 | printf("Result0: %15.5f\n", result0 );
4117 | printf("Result1: %s\n" , result1.c_str());
4118 |
4119 |
4120 | In the example above, the expression will compute two results. As such
4121 | two result variables are defined to hold the values named result0 and
4122 | result1 respectively. The first is of scalar type (double), the second
4123 | is of string type. Once the expression has been evaluated, the two
4124 | variables will have been updated with the new result values, and can
4125 | then be further utilised from within the calling host program.
4126 |
4127 | There will be times when an expression may have multiple exit paths,
4128 | where not all the paths will be return-statement based. The following
4129 | example builds upon the previous examples, but this time at least one
4130 | path is not return based.
4131 |
4132 | typedef exprtk::symbol_table symbol_table_t;
4133 | typedef exprtk::expression expression_t;
4134 | typedef exprtk::parser parser_t;
4135 |
4136 | double x = 100.0;
4137 | double y = 200.0;
4138 |
4139 | symbol_table_t symbol_table;
4140 | expression_t expression;
4141 | parser_t parser;
4142 |
4143 | symbol_table.add_variable ("x", x);
4144 | symbol_table.add_variable ("y", y);
4145 |
4146 | expression.register_symbol_table(symbol_table);
4147 |
4148 | const std::string expression_string =
4149 | " for (var i := 0; i < 10; i += 1) "
4150 | " { "
4151 | " if (i > x) "
4152 | " { "
4153 | " return [x + y, 'return-call 1']; "
4154 | " } "
4155 | " else if (i > y) "
4156 | " { "
4157 | " return [x - y, 'return-call 2']; "
4158 | " } "
4159 | " }; "
4160 | " "
4161 | " x / y "
4162 |
4163 | parser.compile(expression_str, expression);
4164 |
4165 | const auto result = expression.value();
4166 |
4167 | if (expression.return_invoked())
4168 | {
4169 | const auto results = expression.results();
4170 |
4171 | for (std::size_t i = 0; i < results.count(); ++i)
4172 | {
4173 | const auto& rtrn_result = results[i];
4174 | .
4175 | .
4176 | .
4177 | }
4178 | }
4179 | else
4180 | {
4181 | printf("result: %f\n",result);
4182 | }
4183 |
4184 |
4185 | After having called the value method on the expression, calling the
4186 | return_invoked method will determine if the expression completed due
4187 | to a return statement being invoked or if it finished normally.
4188 |
4189 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4190 |
4191 | [SECTION 21 - COMPILATION ERRORS]
4192 | When attempting to compile a malformed or otherwise erroneous ExprTk
4193 | expression, the compilation process will result in an error, as is
4194 | indicated by the 'compile' method returning a false value. A
4195 | diagnostic indicating the first error encountered and its cause can be
4196 | obtained by invoking the 'error' method, as is demonstrated in the
4197 | following example:
4198 |
4199 | if (!parser.compile(expression_string,expression))
4200 | {
4201 | printf("Error: %s\n", parser.error().c_str());
4202 | return false;
4203 | }
4204 |
4205 |
4206 | Any error(s) resulting from a failed compilation will be stored in the
4207 | parser instance until the next time a compilation is performed. Before
4208 | then errors can be enumerated in the order they occurred by invoking
4209 | the 'get_error' method which itself will return a 'parser_error' type.
4210 | A parser_error object will contain an error diagnostic, an error mode
4211 | (or class), and the character position of the error in the expression
4212 | string. The following example demonstrates the enumeration of error(s)
4213 | in the event of a failed compilation.
4214 |
4215 | typedef exprtk::parser parser_t;
4216 | typedef exprtk::parser_error::type error_t;
4217 |
4218 | if (!parser.compile(expression_string,expression))
4219 | {
4220 | for (std::size_t i = 0; i < parser.error_count(); ++i)
4221 | {
4222 | typedef exprtk::parser_error::type error_t;
4223 |
4224 | error_t error = parser.get_error(i);
4225 |
4226 | printf("Error[%02d] Position: %02d Type: [%14s] Msg: %s\n",
4227 | i,
4228 | error.token.position,
4229 | exprtk::parser_error::to_str(error.mode).c_str(),
4230 | error.diagnostic.c_str());
4231 | }
4232 |
4233 | return false;
4234 | }
4235 |
4236 |
4237 | Assuming the following expression '2 + (3 / log(1 + x))' which uses a
4238 | variable named 'x' that has not been registered with the appropriate
4239 | symbol_table instance and is not a locally defined variable, once
4240 | compiled the above denoted post compilation error handling code shall
4241 | produce the following output:
4242 |
4243 | Error[00] Pos:17 Type:[Syntax] Msg: ERR184 - Undefined symbol: 'x'
4244 |
4245 |
4246 | For expressions comprised of multiple lines, the error position
4247 | provided in the parser_error object can be converted into a pair of
4248 | line and column numbers by invoking the 'update_error' function as is
4249 | demonstrated by the following example:
4250 |
4251 | if (!parser.compile(program_str,expression))
4252 | {
4253 | for (std::size_t i = 0; i < parser.error_count(); ++i)
4254 | {
4255 | typedef exprtk::parser_error::type error_t;
4256 |
4257 | error_t error = parser.get_error(i);
4258 |
4259 | exprtk::parser_error::update_error(error,program_str);
4260 |
4261 | printf("Error[%0lu] at line: %lu column: %lu\n",
4262 | i,
4263 | error.line_no,
4264 | error.column_no);
4265 | }
4266 |
4267 | return false;
4268 | }
4269 |
4270 |
4271 | Note32: There are five distinct error modes in ExprTk which denote the
4272 | class of an error. These classes are as follows:
4273 |
4274 | (a) Syntax
4275 | (b) Token
4276 | (c) Numeric
4277 | (d) Symbol Table
4278 | (e) Lexer
4279 |
4280 |
4281 | (a) Syntax Errors
4282 | These are errors related to invalid syntax found within the denoted
4283 | expression. Examples are invalid sequences of operators and variables,
4284 | incorrect number of parameters to functions, invalid conditional or
4285 | loop structures and invalid use of keywords.
4286 |
4287 | eg: 'for := sin(x,y,z) + 2 * equal > until[2 - x,3]'
4288 |
4289 |
4290 | (b) Token Errors
4291 | Errors in this class relate to token level errors detected by one or
4292 | more of the following checkers:
4293 |
4294 | (1) Bracket Checker
4295 | (2) Numeric Checker
4296 | (3) Sequence Checker
4297 |
4298 |
4299 | (c) Numeric Errors
4300 | This class of error is related to conversion of numeric values from
4301 | their string form to the underlying numerical type (float, double
4302 | etc).
4303 |
4304 | (d) Symbol Table Errors
4305 | This is the class of errors related to failures when interacting with
4306 | the registered symbol_table instance. Errors such as not being able to
4307 | find, within the symbol_table, symbols representing variables or
4308 | functions, to being unable to create new variables in the symbol_table
4309 | via the 'unknown symbol resolver' mechanism.
4310 |
4311 | Note33: The function compositor also supports error message handling
4312 | similar to how it is done via the parser. The following demonstrates
4313 | how after a failed function composition the associated errors can be
4314 | enumerated.
4315 |
4316 | typedef exprtk::function_compositor compositor_t;
4317 | typedef typename compositor_t::function function_t;
4318 |
4319 | compositor_t compositor;
4320 |
4321 | const bool compositor_result =
4322 | compositor.add(
4323 | function_t("foobar")
4324 | .vars("x","y")
4325 | .expression
4326 | ( " x + y / z " ));
4327 |
4328 | if (!compositor_result)
4329 | {
4330 | printf("Error: %s\n", compositor.error().c_str());
4331 |
4332 | for (std::size_t i = 1; i < compositor.error_count(); ++i)
4333 | {
4334 | typedef exprtk::parser_error::type error_t;
4335 |
4336 | error_t error = compositor.get_error(i);
4337 |
4338 | printf("Err No.: %02d Pos: %02d Type: [%14s] Msg: %s\n",
4339 | static_cast(i),
4340 | static_cast(error.token.position),
4341 | exprtk::parser_error::to_str(error.mode).c_str(),
4342 | error.diagnostic.c_str());
4343 | }
4344 | }
4345 |
4346 |
4347 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4348 |
4349 | [SECTION 22 - RUNTIME LIBRARY PACKAGES]
4350 | ExprTk includes a range of extensions, that provide functionalities
4351 | beyond simple numerical calculations. Currently the available packages
4352 | are:
4353 |
4354 | +---+--------------------+-----------------------------------+
4355 | | # | Package Name | Namespace/Type |
4356 | +---+--------------------+-----------------------------------+
4357 | | 1 | Basic I/O | exprtk::rtl::io::package |
4358 | | 2 | File I/O | exprtk::rtl::io::file::package |
4359 | | 3 | Vector Operations | exprtk::rtl::vecops::package |
4360 | +---+--------------------+-----------------------------------+
4361 |
4362 |
4363 | In order to make the features of a specific package available within
4364 | an expression, an instance of the package must be added to the
4365 | expression's associated symbol table. In the following example, the
4366 | file I/O package is made available for the given expression:
4367 |
4368 | typedef exprtk::symbol_table symbol_table_t;
4369 | typedef exprtk::expression expression_t;
4370 | typedef exprtk::parser parser_t;
4371 |
4372 | exprtk::rtl::io::file::package fileio_package;
4373 |
4374 | const std::string expression_string =
4375 | " var file_name := 'file.txt'; "
4376 | " var stream := null; "
4377 | " "
4378 | " stream := open(file_name,'w'); "
4379 | " "
4380 | " write(stream,'Hello world....\n'); "
4381 | " "
4382 | " close(stream); "
4383 | " "
4384 |
4385 | symbol_table_t symbol_table;
4386 | symbol_table.add_package(fileio_package);
4387 |
4388 | expression_t expression;
4389 | expression.register_symbol_table(symbol_table);
4390 |
4391 | parser_t parser;
4392 | parser.compile(expression_string,expression);
4393 |
4394 | expression.value();
4395 |
4396 |
4397 | (1) Basic I/O functions:
4398 |
4399 | (a) print
4400 | (b) println
4401 |
4402 | (2) File I/O functions:
4403 |
4404 | (a) open (b) close
4405 | (c) write (d) read
4406 | (e) getline (f) eof
4407 |
4408 | (3) Vector Operations functions:
4409 |
4410 | (a) all_true (b) all_false
4411 | (c) any_true (d) any_false
4412 | (e) assign (f) count
4413 | (g) copy (h) reverse
4414 | (i) rotate-left (j) rotate-right
4415 | (k) shift-left (l) shift-right
4416 | (m) sort (n) nth_element
4417 | (o) iota (p) sumk
4418 | (q) axpy (r) axpby
4419 | (s) axpyz (t) axpbyz
4420 | (u) axpbz (v) dot
4421 | (w) dotk (x) diff
4422 |
4423 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4424 |
4425 | [SECTION 23 - HELPERS & UTILS]
4426 | The ExprTk library provides a series of usage simplifications via
4427 | helper routines that combine various processes into a single 'function
4428 | call' making certain actions easier to carry out though not
4429 | necessarily in the most efficient way possible. A list of the routines
4430 | are as follows:
4431 |
4432 | (a) collect_variables
4433 | (b) collect_functions
4434 | (c) compute
4435 | (d) integrate
4436 | (e) derivative
4437 | (f) second_derivative
4438 | (g) third_derivative
4439 |
4440 |
4441 | (a) collect_variables
4442 | This function will collect all the variable symbols in a given string
4443 | representation of an expression and return them in an STL compatible
4444 | sequence data structure (eg: std::vector, dequeue etc) specialised
4445 | upon a std::string type. If an error occurs during the parsing of the
4446 | expression then the return value of the function will be false,
4447 | otherwise it will be true. An example use of the given routine is as
4448 | follows:
4449 |
4450 | const std::string expression = "x + abs(y / z)"
4451 |
4452 | std::vectorstd::string variable_list;
4453 |
4454 | if (exprtk::collect_variables(expression, variable_list))
4455 | {
4456 | for (const auto& var : variable_list)
4457 | {
4458 | ...
4459 | }
4460 | }
4461 | else
4462 | printf("An error occurred.");
4463 |
4464 |
4465 | (b) collect_functions
4466 | This function will collect all the function symbols in a given string
4467 | representation of an expression and return them in an STL compatible
4468 | sequence data structure (eg: std::vector, dequeue etc) specialised
4469 | upon a std::string type. If an error occurs during the parsing of the
4470 | expression then the return value of the function will be false,
4471 | otherwise it will be true. An example use of the given routine is as
4472 | follows:
4473 |
4474 | const std::string expression = "x + abs(y / cos(1 + z))"
4475 |
4476 | std::dequestd::string function_list;
4477 |
4478 | if (exprtk::collect_functions(expression, function_list))
4479 | {
4480 | for (const auto& func : function_list)
4481 | {
4482 | ...
4483 | }
4484 | }
4485 | else
4486 | printf("An error occurred.");
4487 |
4488 |
4489 | Note34: When either the 'collect_variables' or 'collect_functions'
4490 | free functions return true - that does not necessarily indicate the
4491 | expression itself is valid. It is still possible that when compiled
4492 | the expression may have certain 'type' related errors - though it is
4493 | highly likely that no semantic errors will occur if either return
4494 | true.
4495 |
4496 | Note35: The default interface provided for both the collect_variables
4497 | and collect_functions free_functions, assumes that expressions will
4498 | only be utilising the ExprTk reserved functions (eg: abs, cos, min
4499 | etc). When user defined functions are to be used in an expression, a
4500 | symbol_table instance containing said functions can be passed to
4501 | either routine, and will be incorporated during the compilation and
4502 | Dependent Entity Collection processes. In the following example, a
4503 | user defined free function named 'foo' is registered with a
4504 | symbol_table. Finally the symbol_table instance and associated
4505 | expression string are passed to the exprtk::collect_functions routine.
4506 |
4507 | template
4508 | T foo(T v)
4509 | {
4510 | return std::abs(v + T(2)) / T(3);
4511 | }
4512 |
4513 | ......
4514 |
4515 | exprtk::symbol_table sym_tab;
4516 |
4517 | symbol_table.add_function("foo",foo);
4518 |
4519 | const std::string expression = "x + foo(y / cos(1 + z))"
4520 |
4521 | std::dequestd::string function_list;
4522 |
4523 | if (exprtk::collect_functions(expression, sym_tab, function_list))
4524 | {
4525 | for (const auto& func : function_list)
4526 | {
4527 | ...
4528 | }
4529 | }
4530 | else
4531 | printf("An error occurred.");
4532 |
4533 |
4534 | (c) compute
4535 | This free function will compute the value of an expression from its
4536 | string form. If an invalid expression is passed, the result of the
4537 | function will be false indicating an error, otherwise the return value
4538 | will be true indicating success. The compute function has three
4539 | overloads, the definitions of which are:
4540 |
4541 | (1) No variables
4542 | (2) One variable called x
4543 | (3) Two variables called x and y
4544 | (3) Three variables called x, y and z
4545 |
4546 |
4547 | Example uses of each of the three overloads for the compute routine
4548 | are as follows:
4549 |
4550 | T result = T(0);
4551 |
4552 | // No variables overload
4553 | const std::string no_vars = "abs(1 - (3 / pi)) * 5"
4554 |
4555 | if (!exprtk::compute(no_vars,result))
4556 | printf("Failed to compute: %s",no_vars.c_str());
4557 | else
4558 | printf("Result: %15.5f\n",result);
4559 |
4560 | // One variable 'x' overload
4561 | T x = T(123.456);
4562 |
4563 | const std::string one_var = "abs(x - (3 / pi)) * 5"
4564 |
4565 | if (!exprtk::compute(one_var, x, result))
4566 | printf("Failed to compute: %s",one_var.c_str());
4567 | else
4568 | printf("Result: %15.5f\n",result);
4569 |
4570 | // Two variables 'x' and 'y' overload
4571 | T y = T(789.012);
4572 |
4573 | const std::string two_var = "abs(x - (y / pi)) * 5"
4574 |
4575 | if (!exprtk::compute(two_var, x, y, result))
4576 | printf("Failed to compute: %s",two_var.c_str());
4577 | else
4578 | printf("Result: %15.5f\n",result);
4579 |
4580 | // Three variables 'x', 'y' and 'z' overload
4581 | T z = T(345.678);
4582 |
4583 | const std::string three_var = "abs(x - (y / pi)) * z"
4584 |
4585 | if (!exprtk::compute(three_var, x, y, z, result))
4586 | printf("Failed to compute: %s",three_var.c_str());
4587 | else
4588 | printf("Result: %15.5f\n",result);
4589 |
4590 |
4591 | (d) integrate
4592 | This free function will attempt to perform a numerical integration of
4593 | a single variable compiled expression over a specified range and step
4594 | size. The numerical integration is based on the three point form of
4595 | Simpson's rule. The integrate function has two overloads, where the
4596 | variable of integration can either be passed as a reference or as a
4597 | name in string form. Example usage of the function is as follows:
4598 |
4599 | typedef exprtk::symbol_table symbol_table_t;
4600 | typedef exprtk::expression expression_t;
4601 | typedef exprtk::parser parser_t;
4602 |
4603 | const std::string expression_string = "sqrt(1 - (x^2))"
4604 |
4605 | T x = T(0);
4606 |
4607 | symbol_table_t symbol_table;
4608 | symbol_table.add_variable("x",x);
4609 |
4610 | expression_t expression;
4611 | expression.register_symbol_table(symbol_table);
4612 |
4613 | parser_t parser;
4614 | parser.compile(expression_string,expression);
4615 |
4616 | ....
4617 |
4618 | // Integrate in domain [-1,1] using a reference to x variable
4619 | T area1 = exprtk::integrate(expression, x, T(-1), T(1));
4620 |
4621 | // Integrate in domain [-1,1] using name of x variable
4622 | T area2 = exprtk::integrate(expression, "x", T(-1), T(1));
4623 |
4624 |
4625 | (e) derivative
4626 | This free function will attempt to perform a numerical differentiation
4627 | of a single variable compiled expression at a given point for a given
4628 | epsilon, using a variant of Newton's difference quotient called the
4629 | five-point stencil method. The derivative function has two overloads,
4630 | where the variable of differentiation can either be passed as a
4631 | reference or as a name in string form. Example usage of the derivative
4632 | function is as follows:
4633 |
4634 | typedef exprtk::symbol_table symbol_table_t;
4635 | typedef exprtk::expression expression_t;
4636 | typedef exprtk::parser parser_t;
4637 |
4638 | const std::string expression_string = "sqrt(1 - (x^2))"
4639 |
4640 | T x = T(0);
4641 |
4642 | symbol_table_t symbol_table;
4643 | symbol_table.add_variable("x",x);
4644 |
4645 | expression_t expression;
4646 | expression.register_symbol_table(symbol_table);
4647 |
4648 | parser_t parser;
4649 | parser.compile(expression_string,expression);
4650 |
4651 | ....
4652 |
4653 | // Differentiate expression at value of x = 12.3 using a reference
4654 | // to the x variable
4655 | x = T(12.3);
4656 | T derivative1 = exprtk::derivative(expression, x);
4657 |
4658 | // Differentiate expression where value x = 45.6 using name
4659 | // of the x variable
4660 | x = T(45.6);
4661 | T derivative2 = exprtk::derivative(expression, "x");
4662 |
4663 |
4664 | (f) second_derivative
4665 | This free function will attempt to perform a numerical second
4666 | derivative of a single variable compiled expression at a given point
4667 | for a given epsilon, using a variant of Newton's difference quotient
4668 | method. The second_derivative function has two overloads, where the
4669 | variable of differentiation can either be passed as a reference or as
4670 | a name in string form. Example usage of the second_derivative function
4671 | is as follows:
4672 |
4673 | typedef exprtk::symbol_table symbol_table_t;
4674 | typedef exprtk::expression expression_t;
4675 | typedef exprtk::parser parser_t;
4676 |
4677 | const std::string expression_string = "sqrt(1 - (x^2))"
4678 |
4679 | T x = T(0);
4680 |
4681 | symbol_table_t symbol_table;
4682 | symbol_table.add_variable("x",x);
4683 |
4684 | expression_t expression;
4685 | expression.register_symbol_table(symbol_table);
4686 |
4687 | parser_t parser;
4688 | parser.compile(expression_string,expression);
4689 |
4690 | ....
4691 |
4692 | // Second derivative of expression where value of x = 12.3 using a
4693 | // reference to x variable
4694 | x = T(12.3);
4695 | T derivative1 = exprtk::second_derivative(expression,x);
4696 |
4697 | // Second derivative of expression where value of x = 45.6 using
4698 | // name of x variable
4699 | x = T(45.6);
4700 | T derivative2 = exprtk::second_derivative(expression, "x");
4701 |
4702 |
4703 | (g) third_derivative
4704 | This free function will attempt to perform a numerical third
4705 | derivative of a single variable compiled expression at a given point
4706 | for a given epsilon, using a variant of Newton's difference quotient
4707 | method. The third_derivative function has two overloads, where the
4708 | variable of differentiation can either be passed as a reference or as
4709 | a name in string form. Example usage of the third_derivative function
4710 | is as follows:
4711 |
4712 | typedef exprtk::symbol_table symbol_table_t;
4713 | typedef exprtk::expression expression_t;
4714 | typedef exprtk::parser parser_t;
4715 |
4716 | const std::string expression_string = "sqrt(1 - (x^2))"
4717 |
4718 | T x = T(0);
4719 |
4720 | symbol_table_t symbol_table;
4721 | symbol_table.add_variable("x",x);
4722 |
4723 | expression_t expression;
4724 | expression.register_symbol_table(symbol_table);
4725 |
4726 | parser_t parser;
4727 | parser.compile(expression_string,expression);
4728 |
4729 | ....
4730 |
4731 | // Third derivative of expression where value of x = 12.3 using a
4732 | // reference to the x variable
4733 | x = T(12.3);
4734 | T derivative1 = exprtk::third_derivative(expression, x);
4735 |
4736 | // Third derivative of expression where value of x = 45.6 using
4737 | // name of the x variable
4738 | x = T(45.6);
4739 | T derivative2 = exprtk::third_derivative(expression, "x");
4740 |
4741 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4742 |
4743 | [SECTION 24 - RUNTIME CHECKS]
4744 | The ExprTk library provides the ability to perform runtime checks
4745 | during expression evaluation so as to ensure memory access violations
4746 | errors are caught and handled without causing further issues. The
4747 | checks typically cover:
4748 |
4749 | 1. Vector access and handling
4750 | 2. String access and handling
4751 | 3. Loop iteration checks
4752 | 4. Compilation checkpointing
4753 | 5. Assert statements
4754 |
4755 |
4756 | (1) Vector Access Runtime Checks
4757 | Expressions that contain vectors where elements of the vectors may be
4758 | accessed using indexes that can only be determined at runtime may
4759 | result in memory access violations when the index is out of the
4760 | vector's bound. Some examples of problematic expressions are as
4761 | follows:
4762 |
4763 | 1. vec[i]
4764 | 2. vec[i + j]
4765 | 3. vec[i + 10]
4766 | 4. vec[i + vec[]] := x + y
4767 | 5. vec[i + j] <=> vec[i]
4768 | 6. vec[i + j] := (vec1 + vec2)[i + j]
4769 |
4770 |
4771 | In the above expressions, it is assumed that the values used in the
4772 | index operator may either exceed the vector bounds or precede the
4773 | vector's start, In short, the indexes may not necessarily be within
4774 | the range [0,vec[]).
4775 |
4776 | ExprTk provides the ability to inject a runtime check at the point of
4777 | index evaluation and handle situations where the index violates the
4778 | vector's bounds. This capability is done by registering a user-
4779 | implemented Vector Access Runtime Check (VARTC) to the parser before
4780 | expression compilation. Initially a VARTC can be defined as follows:
4781 |
4782 | struct my_vector_access_rtc final :
4783 | public exprtk::vector_access_runtime_check
4784 | {
4785 | bool handle_runtime_violation(violation_context& context)
4786 | override
4787 | {
4788 | // Handling of the violation
4789 | return ...;
4790 | }
4791 | };
4792 |
4793 |
4794 | Then an instance of the VARTC can be registered with a parser instance
4795 | as follows:
4796 |
4797 | my_vector_access_rtc vartc;
4798 |
4799 | exprtk::symbol_table symbol_table;
4800 |
4801 | T i;
4802 | T x;
4803 | T y;
4804 | std::vector vec = { 0, 1, 2, 3, 4 };
4805 |
4806 | symbol_table.add_variable("i" , i );
4807 | symbol_table.add_variable("x" , x );
4808 | symbol_table.add_variable("y" , y );
4809 | symbol_table.add_vector ("vec", vec);
4810 |
4811 | exprtk::expression expression;
4812 | exprtk::parser parser;
4813 |
4814 | parser.register_vector_access_runtime_check(vartc);
4815 |
4816 | std::string expression = "vec[i + vec[]] := x + y"
4817 |
4818 | parser.compile(expression_str, expression);
4819 |
4820 | try
4821 | {
4822 | expression.value();
4823 | }
4824 | catch (std::runtime_error& rte)
4825 | {
4826 | printf("Exception: %s\n", rte.what());
4827 | }
4828 |
4829 |
4830 | Note36: The lifetime of any parser or expression instance must not
4831 | exceed that of any VARTC instance that has been registered with it.
4832 |
4833 | When a vector access violation occurs, the registered VARTC instance's
4834 | handle_runtime_violation method will be invoked, coupled with it a
4835 | violation_context shall be provided that will contain the following
4836 | members:
4837 |
4838 | 1. base_ptr: Of type void, which points to the first element
4839 | of the vector. The base_ptr can also be used as a key to
4840 | determine the vector upon which the access violation has
4841 | occurred.
4842 |
4843 | 2. end_ptr : Of type void*, which points to one position after
4844 | the last element of the vector
4845 |
4846 | 3. access_ptr: Of type void*, points to the memory location
4847 | which is the base_ptr offset by the derived index value.
4848 |
4849 | 4. type_size: Size of the vector's element type in bytes. This
4850 | value can be used to determine the number of elements in
4851 | the vector based on the base_ptr and end_ptr.
4852 |
4853 |
4854 | The implementation of the handle_runtime_violation method can at this
4855 | point perform various actions such as:
4856 |
4857 | 1. Log the violation
4858 | 2. Throw an exception (eg: std::runtime_error)
4859 | 3. Remedy the access_ptr to allow for the evaluation to continue
4860 |
4861 |
4862 | Note37: When employing option [3], handle_runtime_violation needs to
4863 | return true, otherwise the caller will assume an unhandled access
4864 | violation and default to using the base_ptr.
4865 |
4866 | It is recommended, at the very least, to throw an exception when
4867 | handling vector access violations and to only consider option [3] when
4868 | the the ramifications of changing the access_ptr are well understood.
4869 |
4870 | The following are simple examples of how the handle_runtime_violation
4871 | can be implemented.
4872 |
4873 | Example 1: Log the access violation to stdout and then throw a runtime
4874 | error exception:
4875 |
4876 | bool handle_runtime_violation(violation_context& context) override
4877 | {
4878 | printf("ERROR - Runtime vector access violation. "
4879 | "base: %p end: %p access: %p typesize: %lu\n",
4880 | context.base_ptr ,
4881 | context.end_ptr ,
4882 | context.access_ptr,
4883 | context.type_size);
4884 |
4885 | throw std::runtime_error("Runtime vector access violation.");
4886 | return false;
4887 | }
4888 |
4889 |
4890 | Example 2: Handle the access violation by resetting the access pointer
4891 | to the last value in the vector.
4892 |
4893 | bool handle_runtime_violation(violation_context& context) override
4894 | {
4895 | context.access_ptr =
4896 | static_cast<char*>(context.end_ptr) - context.type_size;
4897 | return true;
4898 | }
4899 |
4900 |
4901 | Note38: The return value of true in the above handler method signals
4902 | the caller to continue the vector access using the updated access_ptr.
4903 |
4904 |
4905 | (2) String Access Runtime Checks
4906 | Expressions that contain strings where elements or substrings of the
4907 | strings may be accessed using indexes that can only be determined at
4908 | runtime may result in memory access violations when the index or range
4909 | is out of the string's bound. Examples of problematic expressions are
4910 | as follows:
4911 |
4912 | 1. s[i : j + k]
4913 | 2. s[i : j + k][-x : y]
4914 | 3. (s1 + s2)[i : j + k]
4915 | 4. '01234'[5 + i]
4916 | 5. s += s[i : j + k]
4917 | 6. s[i : j + k] := 'chappy days'[1 : ]
4918 |
4919 |
4920 | To enable string access runtime checks all one needs to do is simply
4921 | use the following define before the ExprTk header is included or as
4922 | part of the compilation define parameters:
4923 |
4924 | exprtk_enable_range_runtime_checks
4925 |
4926 |
4927 | When the above define is used, and a string related runtime access
4928 | violation occurs a std::runtime_error exception will be thrown. The
4929 | following demonstrates the general flow of handling the access
4930 | violation:
4931 |
4932 | parser.compile(expression_string, expression)
4933 | .
4934 | .
4935 | try
4936 | {
4937 | expression.value();
4938 | }
4939 | catch (std::runtime_error& rte)
4940 | {
4941 | printf("Exception: %s\n", rte.what());
4942 | }
4943 |
4944 |
4945 | (3) Loop Iteration Checks
4946 | Expressions that contain loop structures (eg: for/while/repeat et al)
4947 | can be problematic from a usage point of view due to the difficulty in
4948 | determining the following:
4949 |
4950 | 1. Will the loop ever complete (aka is this an infinite loop?)
4951 | 2. Maximum loop execution time
4952 |
4953 |
4954 | ExprTk provides the ability to inject a runtime check within loop
4955 | conditionals, and to have the result of the check either signal the
4956 | loop to continue or for the check to raise a loop violation error.
4957 |
4958 | The process involves instantiating a user defined loop_runtime_check
4959 | (LRTC), registering the instance with a exprtk::parser instance and
4960 | specifying which loop types the check is to performed upon. The
4961 | following code demonstrates a how custom LRTC can be instantiated and
4962 | registered with the associated parser:
4963 |
4964 | typedef exprtk::parser parser_t;
4965 | typedef exprtk::loop_runtime_check loop_runtime_check_t;
4966 |
4967 | my_loop_rtc loop_rtc;
4968 | loop_runtime_check.loop_set = loop_runtime_check_t::e_all_loops;
4969 | loop_runtime_check.max_loop_iterations = 100000;
4970 |
4971 | parser_t parser;
4972 |
4973 | parser.register_loop_runtime_check(loop_rtc);
4974 |
4975 |
4976 | The following is an example of how one could derive from and implement
4977 | a custom loop_runtime_check:
4978 |
4979 | struct my_loop_rtc final : exprtk::loop_runtime_check
4980 | {
4981 |
4982 | bool check() override
4983 | {
4984 | //
4985 | return ...
4986 | }
4987 |
4988 | void handle_runtime_violation
4989 | (const exprtk::violation_context&) override
4990 | {
4991 | throw std::runtime_error("Loop runtime violation.");
4992 | }
4993 | };
4994 |
4995 |
4996 | In the above code, if either the check method returns false or the
4997 | loop iteration count exceeds the max_loop_iterations value, the
4998 | handle_runtime_violation method will be invoked, coupled with it a
4999 | violation_context shall be provided that will contain the following
5000 | members:
5001 |
5002 | 1. loop: Of type loop_types. This value denotes the type of
5003 | loop that triggered the violation (e_for_loop, e_while_loop,
5004 | e_repeat_until_loop).
5005 |
5006 | 2. violation: Of type type. This value denotes the type of
5007 | violation (e_iteration_count, e_timeout)
5008 |
5009 | 3. iteration_count: Of type uint64_t. The number of iterations
5010 | that the triggering loop has executed since the start of the
5011 | expression.
5012 |
5013 |
5014 | Note39: The lifetime of any parser or expression instance must not
5015 | exceed that of any LRTC instance that has been registered with it.
5016 |
5017 | The following is an example implementation of an LRTC that
5018 | supports loop timeout violations:
5019 |
5020 | struct timeout_loop_rtc final : exprtk::loop_runtime_check
5021 | {
5022 | using time_point_t =
5023 | std::chrono::time_pointstd::chrono::steady_clock;
5024 |
5025 | std::size_t iterations_ = 0;
5026 | time_point_t timeout_tp_;
5027 |
5028 | bool check() override
5029 | {
5030 | if (std::chrono::steady_clock::now() >= timeout_tp_)
5031 | {
5032 | // handle_runtime_violation shall be invoked
5033 | return false;
5034 | }
5035 |
5036 | return true;
5037 | }
5038 |
5039 | void handle_runtime_violation
5040 | (const exprtk::violation_context&) override
5041 | {
5042 | throw std::runtime_error("Loop timed out");
5043 | }
5044 |
5045 | void set_timeout_time(const time_point_t& timeout_tp)
5046 | {
5047 | timeout_tp_ = timeout_tp;
5048 | }
5049 | };
5050 |
5051 |
5052 | In the above code, the check method shall be invoked on each iteration
5053 | of the associated loop. Within the method the current time is compared
5054 | to the setup timeout time-point, in the event the current time exceeds
5055 | the timeout, the method returns false, triggering the violation, which
5056 | in turn will result in the handle_runtime_violation being invoked.
5057 |
5058 | The following code demonstrates how the above defined LRTC can be used
5059 | to ensure that at the very least the loop portion(s) of an expression
5060 | will never exceed a given amount of execution time.
5061 |
5062 | typedef exprtk::parser parser_t;
5063 | typedef exprtk::loop_runtime_check loop_runtime_check_t;
5064 |
5065 | my_loop_rtc loop_rtc;
5066 | loop_rtc.loop_set = loop_runtime_check_t::e_all_loops;
5067 | loop_rtc.max_loop_iterations = 100000;
5068 |
5069 | parser_t parser;
5070 |
5071 | parser.register_loop_runtime_check(loop_rtc);
5072 | .
5073 | .
5074 | .
5075 | .
5076 | using std::chrono;
5077 | const auto max_duration = seconds(25);
5078 |
5079 | try
5080 | {
5081 | loop_rtc.set_timeout_time(steady_clock::now() + max_duration);
5082 | expression.value();
5083 |
5084 | loop_rtc.set_timeout_time(steady_clock::now() + max_duration);
5085 | expression.value();
5086 |
5087 | loop_rtc.set_timeout_time(steady_clock::now() + max_duration);
5088 | expression.value();
5089 |
5090 | }
5091 | catch(std::runtime_error& exception)
5092 | {
5093 | printf("Exception: %s\n",exception.what());
5094 | }
5095 |
5096 |
5097 | (4) Compilation Process Checkpointing
5098 | When compiling an expression, one may require the compilation process
5099 | to periodically checkpoint its internal state, subsequently at the
5100 | checkpoint one can then make the decision to continue the compilation
5101 | process or to immediately terminate and return.
5102 |
5103 | The following are reasons one may want to checkpoint the compilation
5104 | process:
5105 |
5106 | 1. Determine if the compilation process has run for far too long
5107 | 2. Determine if the current stack frame size exceeds a limit
5108 | 3. Enforce an external termination request
5109 |
5110 |
5111 | ExprTk provides the ability to inject a checkpoint into the
5112 | compilation process that will be evaluated periodically. This
5113 | capability is achieved by registering a user-implemented compilation
5114 | check (CCK) to the parser before expression compilation. Initially a
5115 | CCK can be defined as follows:
5116 |
5117 | struct compilation_timeout_check final :
5118 | public exprtk::compilation_check
5119 | {
5120 | bool continue_compilation(compilation_context& context)
5121 | override
5122 | {
5123 | // Determine if compilation should continue
5124 | return ...;
5125 | }
5126 | };
5127 |
5128 |
5129 | An example checkpoint use-case could be that we do not want the
5130 | compilation process to take longer than a maximum defined period, eg:
5131 | five seconds. The associated compilation check implementation could be
5132 | as follows:
5133 |
5134 | struct my_compilation_timeout_check final :
5135 | public exprtk::compilation_check
5136 | {
5137 |
5138 | bool continue_compilation(compilation_context& context)
5139 | override
5140 | {
5141 | static constexpr std::size_t max_iters_per_check = 1000;
5142 |
5143 | if (++iterations_ >= max_iters_per_check)
5144 | {
5145 | if (std::chrono::steady_clock::now() >= timeout_tp_)
5146 | {
5147 | context.error_message = "Compilation has timed-out"
5148 | return false;
5149 | }
5150 |
5151 | iterations_ = 0;
5152 | }
5153 |
5154 | return true;
5155 | }
5156 |
5157 | using time_point_t = std::chrono::time_pointstd::chrono::steady_clock;
5158 |
5159 | void set_timeout_time(const time_point_t& timeout_tp)
5160 | {
5161 | timeout_tp_ = timeout_tp;
5162 | }
5163 |
5164 | std::size_t iterations_ = 0;
5165 | time_point_t timeout_tp_;
5166 | };
5167 |
5168 |
5169 | Usage of the above defined compilation check will require registering
5170 | the check with the parser, setting up the expiry time and then
5171 | proceeding to compile the expression. The following is a general
5172 | outline of what will be needed:
5173 |
5174 | typedef exprtk::expression expression_t;
5175 | typedef exprtk::parser parser_t;
5176 |
5177 | expression_t expression;
5178 |
5179 | my_compilation_timeout_check compilation_timeout_check;
5180 |
5181 | parser_t parser;
5182 | parser.
5183 | register_compilation_timeout_check(compilation_timeout_check);
5184 |
5185 | const auto max_duration = std::chrono::seconds(5);
5186 | const auto timeout_tp =
5187 | std::chrono::steady_clock::now() + max_duration;
5188 |
5189 | compilation_timeout_check.set_timeout_time(timeout_tp);
5190 |
5191 | if (!parser.compile(large_expression_string, expression))
5192 | {
5193 | printf("Error: %s\t\n", parser.error().c_str());
5194 | return;
5195 | }
5196 |
5197 |
5198 | (5) Assert statements
5199 | ExprTk supports the use of assert statements to verify pre and post
5200 | conditions during the evaluation of expressions. The assert statements
5201 | are only active when a user defined assert handler is registered with
5202 | the parser before expression compilation, otherwise they are compiled
5203 | out, this is similar to how asserts are included/excluded in C++
5204 | coupled with the definition of NDEBUG. The assert syntax has three
5205 | variations as described below:
5206 |
5207 | assert(x + y > i);
5208 | assert(x + y > i, 'assert statement 1');
5209 | assert(x + y > i, 'assert statement 1', 'ASSERT01');
5210 |
5211 |
5212 | The three assert statement input parameters are as follows:
5213 |
5214 | 1. assert condition (mandatory)
5215 | 2. assert message (optional)
5216 | 3. assert id (optional)
5217 |
5218 |
5219 | The assert condition is essentially a boolean statement that is
5220 | expected to be true during evaluation. The other two parameters of
5221 | assert message and ID are string values that are intended to provide
5222 | feedback to the handler and to ensure the uniqueness of assert
5223 | statement respectively. The three parameters denoted above and the
5224 | offset of the assert statement from the beginning of the expression
5225 | are placed inside assert_context that is provided as part of the
5226 | assert_check handler. A user defined assert_check handler can be
5227 | defined as follows:
5228 |
5229 | struct my_assert_handler final : public exprtk::assert_check
5230 | {
5231 | void handle_assert(const assert_context& ctxt) override
5232 | {
5233 | printf("condition: [%s] \n", ctxt.condition.c_str());
5234 | printf("message: [%s] \n", ctxt.message .c_str());
5235 | printf("id: [%s] \n", ctxt.id .c_str());
5236 | printf("offset: [%lu]\n", ctxt.offet );
5237 | // throw std::runtime_error(.....);
5238 | }
5239 | };
5240 |
5241 |
5242 | Once the assert_check handler has been registered with the parser,
5243 | expressions that contain assert statements will have their asserts
5244 | compiled in as part final evaluable expression instance:
5245 |
5246 | typedef exprtk::symbol_table symbol_table_t;
5247 | typedef exprtk::expression expression_t;
5248 | typedef exprtk::parser parser_t;
5249 |
5250 | const std::string program =
5251 | " var x := 4; "
5252 | " "
5253 | " for (var i := 0; i < 10; i += 1) "
5254 | " { "
5255 | " assert(i < x, 'assert statement 1'); "
5256 | " } "
5257 |
5258 | my_assert_handler handler;
5259 |
5260 | expression_t expression;
5261 | parser_t parser;
5262 |
5263 | parser.register_assert_check(handler);
5264 | parser.compile(program, expression);
5265 |
5266 |
5267 | (6) Runtime Check Overheads
5268 | All of the above mentioned runtime checks will incur an execution time
5269 | overhead during the evaluation of expressions. This is an unfortunate
5270 | but necessary side-effect of the process when runtime safety is of
5271 | concern.
5272 |
5273 | A recommendation to consider, that is not demonstrated above, is that
5274 | in the check method of the LRTC, one should not evaluate the timeout
5275 | condition on every call to check (aka on every loop iteration).
5276 | Instead a counter should be maintained and incremented on each call
5277 | and when the counter exceeds some predefined amount (eg: 10000
5278 | iterations), then the timeout based check can be preformed. The
5279 | reasoning here is that incrementing an integer should be far less
5280 | expensive than computing the current "now" time-point.
5281 |
5282 |
5283 | (7) Runtime Check Limitations
5284 | The available RTC mechanisms in ExprTk are limited to implementing
5285 | said checks only within ExprTk based syntax sections of an expression.
5286 | The RTCs will not be active within user defined functions, or
5287 | composited functions that have been compiled with parser instances
5288 | that don't have the same set of RTC configurations enabled.
5289 |
5290 |
5291 | (8) Runtime Handlers
5292 | When implementing stateful run-time check handlers one must be careful
5293 | to ensure the handler is setup correctly or reset between calls to the
5294 | expression::value or parser::compile methods.
5295 |
5296 | The following example code utilises the compilation timeout RTC and
5297 | expression loop duration RTC examples from above to demonstrate the
5298 | need to reset the internal state of the various handlers before
5299 | compilation and valuation processes are invoked, as not doing so will
5300 | affect the ability for the next expression in the list to either be
5301 | correctly compiled or evaluated due to the potential of erroneous
5302 | timeouts occurring.
5303 |
5304 | typedef exprtk::expression expression_t;
5305 | typedef exprtk::parser parser_t;
5306 |
5307 | my_compilation_timeout_check compilation_timeout_check;
5308 |
5309 | my_loop_rtc loop_rtc;
5310 | loop_rtc.loop_set = loop_runtime_check_t::e_all_loops;
5311 | loop_rtc.max_loop_iterations = 100000;
5312 |
5313 | parser_t parser;
5314 | parser.register_loop_runtime_check(loop_rtc);
5315 | parser.
5316 | register_compilation_timeout_check(compilation_timeout_check);
5317 |
5318 | const auto compile_timeout_tp =
5319 | {
5320 | const auto max_duration = std::chrono::seconds(5);
5321 | return std::chrono::steady_clock::now() + max_duration;
5322 | };
5323 |
5324 | const auto loop_timeout_tp =
5325 | {
5326 | const auto max_duration = std::chrono::seconds(10);
5327 | return std::chrono::steady_clock::now() + max_duration;
5328 | };
5329 |
5330 | const std::vectorstd::string expressions =
5331 | {
5332 | "x + y / 2",
5333 | "sin(x) / cos(y) + 1",
5334 | "clamp(-1, sin(2 * pi * x) + cos(y / 2 * pi), +1)"
5335 | };
5336 |
5337 | for (const auto& expr_str : expressions)
5338 | {
5339 | // Reset the timeout for the compilation RTC
5340 | compilation_timeout_check
5341 | .set_timeout_time(compile_timeout_tp());
5342 |
5343 | expression_t expression;
5344 |
5345 | if (!parser.compile(large_expression_string, expression))
5346 | {
5347 | printf("Error: %s\t\n", parser.error().c_str());
5348 | continue;
5349 | }
5350 |
5351 | try
5352 | {
5353 | // Reset the timeout for the loop duration RTC
5354 | loop_rtc.set_timeout_time(loop_timeout_tp());
5355 |
5356 | expression.value();
5357 | }
5358 | catch(std::runtime_error& exception)
5359 | {
5360 | printf("Exception: %s\n Expression: %s\n",
5361 | exception.what(),
5362 | expr_str.c_str());
5363 | }
5364 | }
5365 |
5366 |
5367 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5368 |
5369 | [SECTION 25 - BENCHMARKING]
5370 | As part of the ExprTk package there is an expression benchmark utility
5371 | named 'exprtk_benchmark'. The utility attempts to determine expression
5372 | evaluation speed (or rate of evaluations - evals per second), by
5373 | evaluating each expression numerous times and mutating the underlying
5374 | variables of the expression between each evaluation. The utility
5375 | assumes any valid ExprTk expression (containing conditionals, loops
5376 | etc), however it will only make use of a predefined set of scalar
5377 | variables, namely: a, b, c, x, y, z and w. That being said expressions
5378 | themselves can contain any number of local variables, vectors or
5379 | strings. There are two modes of operation:
5380 |
5381 | (1) Default
5382 | (2) User Specified Expressions
5383 |
5384 |
5385 | (1) Default
5386 | The default mode is enabled simply by executing the exprtk_benchmark
5387 | binary with no command line parameters. In this mode a predefined set
5388 | of expressions will be evaluated in three phases:
5389 |
5390 | (a) ExprTk evaluation
5391 | (b) Native evaluation
5392 | (c) ExprTk parse
5393 |
5394 |
5395 | In the first two phases (a and b) a list of predefined (hard-coded)
5396 | expressions will be evaluated using both ExprTk and native mode
5397 | implementations. This is done so as to compare evaluation times
5398 | between ExprTk and native implementations. The set of expressions used
5399 | are as follows:
5400 |
5401 | (01) (y + x)
5402 | (02) 2 * (y + x)
5403 | (03) (2 * y + 2 * x)
5404 | (04) ((1.23 * x^2) / y) - 123.123
5405 | (05) (y + x / y) * (x - y / x)
5406 | (06) x / ((x + y) + (x - y)) / y
5407 | (07) 1 - ((x * y) + (y / x)) - 3
5408 | (08) (5.5 + x) + (2 * x - 2 / 3 * y) * (x / 3 + y / 4) + (y + 7.7)
5409 | (09) 1.1x^1 + 2.2y^2 - 3.3x^3 + 4.4y^15 - 5.5x^23 + 6.6y^55
5410 | (10) sin(2 * x) + cos(pi / y)
5411 | (11) 1 - sin(2 * x) + cos(pi / y)
5412 | (12) sqrt(111.111 - sin(2 * x) + cos(pi / y) / 333.333)
5413 | (13) (x^2 / sin(2 * pi / y)) - x / 2
5414 | (14) x + (cos(y - sin(2 / x * pi)) - sin(x - cos(2 * y / pi))) - y
5415 | (15) clamp(-1.0, sin(2 * pi * x) + cos(y / 2 * pi), +1.0)
5416 | (16) max(3.33, min(sqrt(1 - sin(2 * x) + cos(pi / y) / 3), 1.11))
5417 | (17) if((y + (x * 2.2)) <= (x + y + 1.1), x - y, xy) + 2 * pi / x
5418 |
5419 |
5420 | The third and final phase (c), is used to determine average
5421 | compilation rates (compiles per second) for expressions of varying
5422 | complexity. Each expression is compiled 100K times and the average for
5423 | each expression is output.
5424 |
5425 |
5426 | (2) User Specified Expressions
5427 | In this mode two parameters are passed to the utility via the command
5428 | line:
5429 |
5430 | (a) A name of a text file containing one expression per line
5431 | (b) An integer representing the number of evaluations per expression
5432 |
5433 |
5434 | An example execution of the benchmark utility in this mode is as
5435 | follows:
5436 |
5437 | ./exprtk_benchmark my_expressions.txt 1000000
5438 |
5439 |
5440 | The above invocation will load the expressions from the file
5441 | 'my_expressions.txt' and will then proceed to evaluate each expression
5442 | one million times, varying the above mentioned variables (x, y, z
5443 | etc.) between each evaluation, and at the end of each expression round
5444 | a print out of running times, result of a single evaluation and total
5445 | sum of results is provided as demonstrated below:
5446 |
5447 | Expression 1 of 7 4.770 ns 47700 ns ( 9370368.0) '((((x+y)+z)))'
5448 | Expression 2 of 7 4.750 ns 47500 ns ( 1123455.9) '((((x+y)-z)))'
5449 | Expression 3 of 7 4.766 ns 47659 ns (21635410.7) '((((x+y)z)))'
5450 | Expression 4 of 7 5.662 ns 56619 ns ( 1272454.9) '((((x+y)/z)))'
5451 | Expression 5 of 7 4.950 ns 49500 ns ( 4123455.9) '((((x-y)+z)))'
5452 | Expression 6 of 7 7.581 ns 75810 ns (-4123455.9) '((((x-y)-z)))'
5453 | Expression 7 of 7 4.801 ns 48010 ns ( 0.0) '((((x-y)z)))'
5454 |
5455 |
5456 | The benchmark utility can be very useful when investigating evaluation
5457 | efficiency issues with ExprTk or simply during the prototyping of
5458 | expressions. As an example, lets take the following expression:
5459 |
5460 | 1 / sqrt(2x) * e^(3y)
5461 |
5462 |
5463 | Lets say we would like to determine which sub-part of the expression
5464 | takes the most time to evaluate and perhaps attempt to rework the
5465 | expression based on the results. In order to do this we will create a
5466 | text file called 'test.txt' and then proceed to make some educated
5467 | guesses about how to break the expression up into its more
5468 | 'interesting' sub-parts which we will then add as one expression per
5469 | line to the file. An example breakdown may be as follows:
5470 |
5471 | 1 / sqrt(2x) * e^(3y)
5472 | 1 / sqrt(2x)
5473 | e^(3y)
5474 |
5475 |
5476 | The benchmark with the given file, where each expression will be
5477 | evaluated 100K times can be executed as follows:
5478 |
5479 | ./exprtk_benchmark test.txt 100000
5480 | Expr 1 of 3 90.340 ns 9034000 ns (296417859.3) '1/sqrt(2x)e^(3y)'
5481 | Expr 2 of 3 11.100 ns 1109999 ns ( 44267.3) '1/sqrt(2x)'
5482 | Expr 3 of 3 77.830 ns 7783000 ns (615985286.6) 'e^(3y)'
5483 | [] Number Of Evals: 300000
5484 | [] Total Time: 0.018sec
5485 | [] Total Single Eval Time: 0.000ms
5486 |
5487 |
5488 | From the results above we conclude that the third expression (e^(3y))
5489 | consumes the largest amount of time. The variable 'e', as used in both
5490 | the benchmark and in the expression, is an approximation of the
5491 | transcendental mathematical constant e (2.71828182845904...) hence the
5492 | sub-expression should perhaps be modified to use the generally more
5493 | efficient built-in 'exp' function.
5494 |
5495 | ./exprtk_benchmark test.txt 1000000
5496 | Expr 1 of 5 86.563 ns 8656300ns (296417859.6) '1/sqrt(2x)e^(3y)'
5497 | Expr 2 of 5 40.506 ns 4050600ns (296417859.6) '1/sqrt(2x)exp(3y)'
5498 | Expr 3 of 5 14.248 ns 1424799ns ( 44267.2) '1/sqrt(2x)'
5499 | Expr 4 of 5 88.840 ns 8884000ns (615985286.9) 'e^(3y)'
5500 | Expr 5 of 5 29.267 ns 2926699ns (615985286.9) 'exp(3y)'
5501 | [] Number Of Evals: 5000000
5502 | [] Total Time: 0.260sec
5503 | [] Total Single Eval Time: 0.000ms
5504 |
5505 |
5506 | The above output demonstrates the results from making the previously
5507 | mentioned modification to the expression. As can be seen the new form
5508 | of the expression using the 'exp' function reduces the evaluation time
5509 | by over 50%, in other words increases the evaluation rate by two fold.
5510 |
5511 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5512 |
5513 | [SECTION 26 - EXPRTK NOTES]
5514 | The following is a list of facts and suggestions one may want to take
5515 | into account when using ExprTk:
5516 |
5517 | (00) Precision and performance of expression evaluations are the
5518 | dominant principles of the ExprTk library.
5519 |
5520 | (01) ExprTk uses a rudimentary imperative programming model with
5521 | syntax based on languages such as Pascal and C. Furthermore
5522 | ExprTk is an LL(2) type grammar and is processed using a
5523 | recursive descent parsing algorithm.
5524 |
5525 | (02) Supported types are float, double, long double and MPFR/GMP.
5526 | Generally any user defined numerical type that supports all the
5527 | basic floating point arithmetic operations: -,+,,/,^,% etc;
5528 | unary and binary operations: sin,cos,min,max,equal etc and any
5529 | other ExprTk dependent operations can be used to specialise the
5530 | various components: expression, parser and symbol_table.
5531 |
5532 | (03) Standard arithmetic operator precedence is applied (BEDMAS). In
5533 | general C, Pascal or Rust equivalent unary, binary, logical and
5534 | equality/inequality operator precedence rules apply.
5535 | eg: a == b and c > d + 1 ---> (a == b) and (c > (d + 1))
5536 | x - y <= z / 2 ---> (x - y) <= (z / 2)
5537 | a - b / c * d^2^3 ---> a - ((b / c) * d^(2^3))
5538 |
5539 | (04) Results of expressions that are deemed as being 'valid' are to
5540 | exist within the set of Real numbers. All other results will be
5541 | of the value: Not-A-Number (NaN). However this may not
5542 | necessarily be a requirement for user defined numerical types,
5543 | eg: complex number type.
5544 |
5545 | (05) Supported user defined types are numeric and string
5546 | variables, numeric vectors and functions.
5547 |
5548 | (06) All reserved words, keywords, variable, vector, string and
5549 | function names are case-insensitive.
5550 |
5551 | (07) Variable, vector, string variable and function names must begin
5552 | with a letter (A-Z or a-z), then can be comprised of any
5553 | combination of letters, digits, underscores and non-consecutive
5554 | dots, ending in either a letter (A-Z or a-z), digit or
5555 | underscore. (eg: x, y2, var1, power_func99, person.age,
5556 | item.size.0). The associated regex pattern is:
5557 | [a-zA-Z][a-zA-Z0-9_](.[a-zA-Z0-9_]+)*
5558 |
5559 | (08) Expression lengths and sub-expression lists are limited only by
5560 | storage capacity.
5561 |
5562 | (09) The life-time of objects registered with or created from a
5563 | specific symbol-table must span at least the lifetime of the
5564 | symbol table instance and all compiled expressions which
5565 | utilise objects, such as variables, strings, vectors, function
5566 | compositor functions and functions of that symbol-table,
5567 | otherwise the result will be undefined behaviour.
5568 |
5569 | (10) Equal and not_equal are normalised-epsilon equality routines,
5570 | which use epsilons of 0.0000000001 and 0.000001 for double and
5571 | float types respectively.
5572 |
5573 | (11) All trigonometric functions assume radian input unless stated
5574 | otherwise.
5575 |
5576 | (12) Expressions may contain white-space characters such as space,
5577 | tabs, new-lines, control-feed et al.
5578 | ('\n', '\r', '\t', '\b', '\v', '\f')
5579 |
5580 | (13) Strings may be comprised of any combination of letters, digits
5581 | special characters including (!@#$%^&*()[]|=+ ,./?<>;:"`_) or
5582 | hexadecimal escaped sequences (eg: \0x30) and must be enclosed
5583 | with single-quotes.
5584 | eg: 'Frankly my dear, \0x49 do n0t give a damn!'
5585 |
5586 | (14) User defined normal functions can have up to 20 parameters,
5587 | where as user defined generic-functions and vararg-functions
5588 | can have an unlimited number of parameters.
5589 |
5590 | (15) The inbuilt polynomial functions can be at most of degree 12.
5591 |
5592 | (16) Where appropriate constant folding optimisations may be applied.
5593 | (eg: The expression '2 + (3 - (x / y))' becomes '5 - (x / y)')
5594 |
5595 | (17) If the strength reduction compilation option has been enabled,
5596 | then where applicable strength reduction optimisations may be
5597 | applied.
5598 |
5599 | (18) String processing capabilities are available by default. To
5600 | turn them off, the following needs to be defined at compile
5601 | time: exprtk_disable_string_capabilities
5602 |
5603 | (19) Composited functions can call themselves or any other functions
5604 | that have been defined prior to their own definition.
5605 |
5606 | (20) Recursive calls made from within composited functions will have
5607 | a stack size bound by the stack of the executing architecture.
5608 |
5609 | (21) User defined functions by default are assumed to have side
5610 | effects. As such an "all constant parameter" invocation of such
5611 | functions wont result in constant folding. If the function has
5612 | no side-effects then that can be noted during the constructor
5613 | of the ifunction allowing it to be constant folded where
5614 | appropriate.
5615 |
5616 | (22) The entity relationship between symbol_table and an expression
5617 | is many-to-many. However the intended 'typical' use-case where
5618 | possible, is to have a single symbol table manage the variable
5619 | and function requirements of multiple expressions.
5620 |
5621 | (23) The common use-case for an expression is to have it compiled
5622 | only ONCE and then subsequently have it evaluated multiple
5623 | times. An extremely inefficient and suboptimal approach would
5624 | be to recompile an expression from its string form every time
5625 | it requires evaluating.
5626 |
5627 | (24) It is strongly recommended that the return value of method
5628 | invocations from the parser and symbol_table types be taken
5629 | into account. Specifically the 'compile' method of the parser
5630 | and the 'add_xxx' set of methods of the symbol_table as they
5631 | denote either the success or failure state of the invoked call.
5632 | Continued processing from a failed state without having first
5633 | rectified the underlying issue will in turn result in further
5634 | failures and undefined behaviours.
5635 |
5636 | (25) The following are examples of compliant floating point value
5637 | representations:
5638 |
5639 | (01) 12345 (06) -123.456
5640 | (02) +123.456e+12 (07) 123.456E-12
5641 | (03) +012.045e+07 (08) .1234
5642 | (04) 1234. (09) -56789.
5643 | (05) 123.456f (10) -321.654E+3L
5644 |
5645 | (26) Expressions may contain any of the following comment styles:
5646 |
5647 | (1) // .... \n
5648 | (2) # .... \n
5649 | (3) /* .... /
5650 |
5651 | (27) The 'null' value type is a special non-zero type that
5652 | incorporates specific semantics when undergoing operations with
5653 | the standard numeric type. The following is a list of type and
5654 | boolean results associated with the use of 'null':
5655 |
5656 | (1) null +,-,,/,% x --> x
5657 | (2) x +,-,,/,% null --> x
5658 | (3) null +,-,,/,% null --> null
5659 | (4) null == null --> true
5660 | (5) null == x --> true
5661 | (6) x == null --> true
5662 | (7) x != null --> false
5663 | (8) null != null --> false
5664 | (9) null != x --> false
5665 |
5666 | (28) The following is a list of reserved words and symbols used by
5667 | ExprTk. Attempting to add a variable or custom function to a
5668 | symbol table using any of the reserved words will result in a
5669 | failure.
5670 |
5671 | abs, acos, acosh, and, asin, asinh, assert, atan, atan2,
5672 | atanh, avg, break, case, ceil, clamp, continue, cosh, cos,
5673 | cot, csc, default, deg2grad, deg2rad, else, equal, erfc,
5674 | erf, exp, expm1, false, floor, for, frac, grad2deg, hypot,
5675 | iclamp, if, ilike, in, inrange, in, like, log, log10, log1p,
5676 | log2, logn, mand, max, min, mod, mor, mul, nand, ncdf, nor,
5677 | not, not_equal, not, null, or, pow, rad2deg, repeat, return,
5678 | root, roundn, round, sec, sgn, shl, shr, sinc, sinh, sin,
5679 | sqrt, sum, swap, switch, tanh, tan, true, trunc, until, var,
5680 | while, xnor, xor
5681 |
5682 | (29) Every valid ExprTk statement is a "value returning" expression.
5683 | Unlike some languages that limit the types of expressions that
5684 | can be performed in certain situations, in ExprTk any valid
5685 | expression can be used in any "value consuming" context. eg:
5686 |
5687 | var y := 3;
5688 | for (var x := switch
5689 | {
5690 | case 1 : 7;
5691 | case 2 : -1 + {var x{};};
5692 | default : y > 2 ? 3 : 4;
5693 | };
5694 | x != while (y > 0) { y -= 1; };
5695 | x -= {
5696 | if (min(x,y) < 2 * max(x,y))
5697 | x + 2;
5698 | else
5699 | x + y - 3;
5700 | }
5701 | )
5702 | {
5703 | (x + y) / (x - y);
5704 | };
5705 |] ---> [{(] -+-> [expression] -+-> [;,] ---+ |
6207 | | | |
6208 | | +----------------<----------------+ |
6209 | | | |
6210 | | +--> [})] |
6211 | | |
6212 | +-------------------------------------------------------------+
6213 |
5706 | (30) It is recommended when prototyping expressions that the ExprTk
5707 | REPL be utilised, as it supports all the features available in
5708 | the library, including complete error analysis, benchmarking
5709 | and dependency dumps etc which allows for rapid
5710 | coding/prototyping and debug cycles without the hassle of
5711 | having to recompile test programs with expressions that have
5712 | been hard-coded. It is also a good source of truth for how the
5713 | library's various features can be applied.
5714 |
5715 | (31) For performance considerations, one should assume the actions
5716 | of expression, symbol table and parser instance instantiation
5717 | and destruction, and the expression compilation process itself
5718 | to be of high latency. Hence none of them should be part of any
5719 | performance critical code paths, and should instead occur
5720 | entirely either before or after such code paths.
5721 |
5722 | (32) Deep copying an expression instance for the purposes of
5723 | persisting to disk or otherwise transmitting elsewhere with the
5724 | intent to 'resurrect' the expression instance later on is not
5725 | possible due to the reasons described in the final note of
5726 | Section 10. The recommendation is to instead simply persist the
5727 | string form of the expression and compile the expression at
5728 | run-time on the target.
5729 |
5730 | (33) The correctness and robustness of the ExprTk library is
5731 | maintained by having a comprehensive suite of unit tests and
5732 | functional tests all of which are run using sanitizers (ASAN,
5733 | UBSAN, LSAN, MSAN, TSAN). Additionally, continuous fuzz-testing
5734 | provided by Google OSS Fuzz, and static analysis via Synopsis
5735 | Coverity.
5736 |
5737 | (34) The library name ExprTk is pronounced "Ex-Pee-Ar-Tee-Kay" or
5738 | simply "Mathematical Expression Toolkit"
5739 |
5740 |
5741 | (35) For general support, inquires or bug/issue reporting:
5742 | https://www.partow.net/programming/exprtk/index.html#support
5743 |
5744 | (36) Before jumping in and using ExprTk, do take the time to peruse
5745 | the documentation and all of the examples, both in the main and
5746 | the extras distributions. Having an informed general view of
5747 | what can and can't be done, and how something should be done
5748 | with ExprTk, will likely result in a far more productive and
5749 | enjoyable programming experience.
5750 |
5751 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5752 |
5753 | [SECTION 27 - SIMPLE EXPRTK EXAMPLE]
5754 | The following is a simple yet complete example demonstrating typical
5755 | usage of the ExprTk Library. The example instantiates a symbol table
5756 | object, adding to it three variables named x, y and z, and a custom
5757 | user defined function, that accepts only two parameters, named myfunc.
5758 | The example then proceeds to instantiate an expression object and
5759 | register to it the symbol table instance.
5760 |
5761 | A parser is then instantiated, and the string representation of the
5762 | expression and the expression object are passed to the parser's
5763 | compile method for compilation. If an error occurred during
5764 | compilation, the compile method will return false, leading to a series
5765 | of error diagnostics being printed to stdout. Otherwise the newly
5766 | compiled expression is evaluated by invoking the expression object's
5767 | value method, and subsequently printing the result of the computation
5768 | to stdout.
5769 |
5770 |
5771 | --- snip ---
5772 | #include
5773 | #include
5774 |
5775 | #include "exprtk.hpp"
5776 |
5777 | template
5778 | struct myfunc final : public exprtk::ifunction
5779 | {
5780 | myfunc() : exprtk::ifunction(2) {}
5781 |
5782 | T operator()(const T& v1, const T& v2) override
5783 | {
5784 | return T(1) + (v1 * v2) / T(3);
5785 | }
5786 | };
5787 |
5788 | int main()
5789 | {
5790 | typedef exprtk::symbol_table symbol_table_t;
5791 | typedef exprtk::expression expression_t;
5792 | typedef exprtk::parser parser_t;
5793 | typedef exprtk::parser_error::type error_t;
5794 |
5795 | const std::string expression_string =
5796 | "z := 2 myfunc([4 + sin(x / pi)^3],y ^ 2)"
5797 |
5798 | double x = 1.1;
5799 | double y = 2.2;
5800 | double z = 3.3;
5801 |
5802 | myfunc mf;
5803 |
5804 | symbol_table_t symbol_table;
5805 | symbol_table.add_constants();
5806 | symbol_table.add_variable("x",x);
5807 | symbol_table.add_variable("y",y);
5808 | symbol_table.add_variable("z",z);
5809 | symbol_table.add_function("myfunc",mf);
5810 |
5811 | expression_t expression;
5812 | expression.register_symbol_table(symbol_table);
5813 |
5814 | parser_t parser;
5815 |
5816 | if (!parser.compile(expression_string,expression))
5817 | {
5818 | // A compilation error has occurred. Attempt to
5819 | // print all errors to stdout.
5820 |
5821 | printf("Error: %s\tExpression: %s\n",
5822 | parser.error().c_str(),
5823 | expression_string.c_str());
5824 |
5825 | for (std::size_t i = 0; i < parser.error_count(); ++i)
5826 | {
5827 | // Include the specific nature of each error
5828 | // and its position in the expression string.
5829 |
5830 | error_t error = parser.get_error(i);
5831 |
5832 | printf("Error: %02d Position: %02d "
5833 | "Type: [%s] "
5834 | "Message: %s "
5835 | "Expression: %s\n",
5836 | static_cast(i),
5837 | static_cast(error.token.position),
5838 | exprtk::parser_error::to_str(error.mode).c_str(),
5839 | error.diagnostic.c_str(),
5840 | expression_string.c_str());
5841 | }
5842 |
5843 | return 1;
5844 | }
5845 |
5846 | // Evaluate the expression and obtain its result.
5847 |
5848 | double result = expression.value();
5849 |
5850 | printf("Result: %10.5f\n",result);
5851 |
5852 | return 0;
5853 | }
5854 | --- snip ---
5855 |
5856 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5857 |
5858 | [SECTION 28 - BUILD OPTIONS]
5859 | When building ExprTk there are a number of defines that will enable or
5860 | disable certain features and capabilities. The defines can either be
5861 | part of a compiler command line switch or scoped around the include to
5862 | the ExprTk header. The defines are as follows:
5863 |
5864 | (01) exprtk_enable_debugging
5865 | (02) exprtk_disable_cardinal_pow_optimisation
5866 | (03) exprtk_disable_comments
5867 | (04) exprtk_disable_break_continue
5868 | (05) exprtk_disable_sc_andor
5869 | (06) exprtk_disable_return_statement
5870 | (07) exprtk_disable_enhanced_features
5871 | (08) exprtk_disable_string_capabilities
5872 | (09) exprtk_disable_superscalar_unroll
5873 | (10) exprtk_disable_rtl_io
5874 | (11) exprtk_disable_rtl_io_file
5875 | (12) exprtk_disable_rtl_vecops
5876 | (13) exprtk_disable_caseinsensitivity
5877 | (14) exprtk_enable_range_runtime_checks
5878 |
5879 | (01) exprtk_enable_debugging
5880 | This define will enable printing of debug information to stdout during
5881 | the compilation process.
5882 |
5883 | (02) exprtk_disable_cardinal_pow_optimisation
5884 | This define will disable the optimisation invoked when constant
5885 | integers are used as powers in exponentiation expressions (eg: x^7).
5886 |
5887 | (03) exprtk_disable_comments
5888 | This define will disable the ability for expressions to have comments.
5889 | Expressions that have comments when parsed with a build that has this
5890 | option, will result in a compilation failure.
5891 |
5892 | (04) exprtk_disable_break_continue
5893 | This define will disable the loop-wise 'break' and 'continue'
5894 | capabilities. Any expression that contains those keywords will result
5895 | in a compilation failure.
5896 |
5897 | (05) exprtk_disable_sc_andor
5898 | This define will disable the short-circuit '&' (and) and '|' (or)
5899 | operators
5900 |
5901 | (06) exprtk_disable_return_statement
5902 | This define will disable use of return statements within expressions.
5903 |
5904 | (07) exprtk_disable_enhanced_features
5905 | This define will disable all enhanced features such as strength
5906 | reduction and special function optimisations and expression specific
5907 | type instantiations. This feature will reduce compilation times and
5908 | binary sizes but will also result in massive performance degradation
5909 | of expression evaluations.
5910 |
5911 | (08) exprtk_disable_string_capabilities
5912 | This define will disable all string processing capabilities. Any
5913 | expression that contains a string or string related syntax will result
5914 | in a compilation failure.
5915 |
5916 | (09) exprtk_disable_superscalar_unroll
5917 | This define will set the loop unroll batch size to 4 operations per
5918 | loop instead of the default 8 operations. This define is used in
5919 | operations that involve vectors and aggregations over vectors. When
5920 | targeting non-superscalar architectures, it may be recommended to
5921 | build using this particular option if efficiency of evaluations is of
5922 | concern.
5923 |
5924 | (10) exprtk_disable_rtl_io
5925 | This define will disable all of basic IO RTL package features. When
5926 | present, any attempt to register the basic IO RTL package with a given
5927 | symbol table will fail causing a compilation error.
5928 |
5929 | (11) exprtk_disable_rtl_io_file
5930 | This define will disable the file I/O RTL package features. When
5931 | present, any attempts to register the file I/O package with a given
5932 | symbol table will fail causing a compilation error.
5933 |
5934 | (12) exprtk_disable_rtl_vecops
5935 | This define will disable the extended vector operations RTL package
5936 | features. When present, any attempts to register the vector operations
5937 | package with a given symbol table will fail causing a compilation
5938 | error.
5939 |
5940 | (13) exprtk_disable_caseinsensitivity
5941 | This define will disable case-insensitivity when matching variables
5942 | and functions. Furthermore all reserved and keywords will only be
5943 | acknowledged when in all lower-case.
5944 |
5945 | (14) exprtk_enable_range_runtime_checks
5946 | This define will enable run-time checks pertaining to vector indexing
5947 | operations used in any of the vector-to-vector and vector-to-scalar
5948 | operations.
5949 |
5950 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5951 |
5952 | [SECTION 29 - FILES]
5953 | The source distribution of ExprTk is comprised of the following set of
5954 | files:
5955 |
5956 | (00) Makefile
5957 | (01) readme.txt
5958 | (02) exprtk.hpp
5959 | (03) exprtk_test.cpp
5960 | (04) exprtk_benchmark.cpp
5961 | (05) exprtk_simple_example_01.cpp
5962 | (06) exprtk_simple_example_02.cpp
5963 | (07) exprtk_simple_example_03.cpp
5964 | (08) exprtk_simple_example_04.cpp
5965 | (09) exprtk_simple_example_05.cpp
5966 | (10) exprtk_simple_example_06.cpp
5967 | (11) exprtk_simple_example_07.cpp
5968 | (12) exprtk_simple_example_08.cpp
5969 | (13) exprtk_simple_example_09.cpp
5970 | (14) exprtk_simple_example_10.cpp
5971 | (15) exprtk_simple_example_11.cpp
5972 | (16) exprtk_simple_example_12.cpp
5973 | (17) exprtk_simple_example_13.cpp
5974 | (18) exprtk_simple_example_14.cpp
5975 | (19) exprtk_simple_example_15.cpp
5976 | (20) exprtk_simple_example_16.cpp
5977 | (21) exprtk_simple_example_17.cpp
5978 | (22) exprtk_simple_example_18.cpp
5979 | (23) exprtk_simple_example_19.cpp
5980 | (24) exprtk_simple_example_20.cpp
5981 | (25) exprtk_simple_example_21.cpp
5982 | (26) exprtk_simple_example_22.cpp
5983 | (27) exprtk_simple_example_23.cpp
5984 | (28) exprtk_simple_example_24.cpp
5985 |
5986 |
5987 | Details for each of the above examples can be found here:
5988 |
5989 | https://www.partow.net/programming/exprtk/index.html#examples
5990 |
5991 |
5992 | Various extended and advanced examples using ExprTk are available
5993 | via the following:
5994 |
5995 | (00) exprtk_american_option_binomial_model.cpp
5996 | (01) exprtk_archimedes_pi.cpp
5997 | (02) exprtk_arithmetic_evaluator.cpp
5998 | (03) exprtk_binomial_coefficient.cpp
5999 | (04) exprtk_bsm_benchmark.cpp
6000 | (05) exprtk_calc.cpp
6001 | (06) exprtk_collatz.cpp
6002 | (07) exprtk_compilation_timeout.cpp
6003 | (08) exprtk_degree_trigonometry_example.cpp
6004 | (09) exprtk_exprgen.cpp
6005 | (00) exprtk_extract_dependents.cpp
6006 | (11) exprtk_e_10kdigits.cpp
6007 | (12) exprtk_factorize_fermat.cpp
6008 | (13) exprtk_factorize_pollard.cpp
6009 | (14) exprtk_fizzbuzz.cpp
6010 | (15) exprtk_funcall_benchmark.cpp
6011 | (16) exprtk_game_of_life.cpp
6012 | (17) exprtk_gcd.cpp
6013 | (18) exprtk_gnuplot.cpp
6014 | (19) exprtk_gnuplot_multi.cpp
6015 | (10) exprtk_groups_examples.cpp
6016 | (21) exprtk_immutable_symbol_table_example.cpp
6017 | (22) exprtk_import_packages.cpp
6018 | (23) exprtk_instruction_primer.cpp
6019 | (24) exprtk_jump_diffusion_process.cpp
6020 | (25) exprtk_loop_timeout_rtc.cpp
6021 | (26) exprtk_magic_square.cpp
6022 | (27) exprtk_mandelbrot.cpp
6023 | (28) exprtk_max_subarray_sum.cpp
6024 | (29) exprtk_maze_generator.cpp
6025 | (20) exprtk_miller_rabin_primality_test.cpp
6026 | (31) exprtk_montecarlo_e.cpp
6027 | (32) exprtk_montecarlo_option_pricing_model.cpp
6028 | (33) exprtk_montecarlo_pi.cpp
6029 | (34) exprtk_naive_primes.cpp
6030 | (35) exprtk_normal_random_marsaglia_method.cpp
6031 | (36) exprtk_nqueens_problem.cpp
6032 | (37) exprtk_nthroot_bisection.cpp
6033 | (38) exprtk_ornstein_uhlenbeck_process.cpp
6034 | (39) exprtk_pascals_triangle.cpp
6035 | (30) exprtk_pi_10kdigits.cpp
6036 | (41) exprtk_prime_sieve.cpp
6037 | (42) exprtk_prime_sieve_vectorized.cpp
6038 | (43) exprtk_pyramid.cpp
6039 | (44) exprtk_pythagorean_triples.cpp
6040 | (45) exprtk_recursive_fibonacci.cpp
6041 | (46) exprtk_repl.cpp
6042 | (47) exprtk_riddle.cpp
6043 | (48) exprtk_rtc_overhead.cpp
6044 | (49) exprtk_sudoku_solver.cpp
6045 | (50) exprtk_sumofprimes.cpp
6046 | (51) exprtk_symtab_functions.cpp
6047 | (52) exprtk_testgen.cpp
6048 | (53) exprtk_tower_of_hanoi.cpp
6049 | (54) exprtk_truthtable_gen.cpp
6050 | (55) exprtk_vectorized_binomial_model.cpp
6051 | (56) exprtk_vectornorm.cpp
6052 | (57) exprtk_vector_benchmark.cpp
6053 | (58) exprtk_vector_benchmark_multithreaded.cpp
6054 | (59) exprtk_vector_resize_example.cpp
6055 | (60) exprtk_vector_resize_inline_example.cpp
6056 | (61) exprtk_wiener_process_pi.cpp
6057 |
6058 |
6059 | Details for each of the above examples can be found here:
6060 |
6061 | https://partow.net/programming/exprtk/index.html#variousexamples
6062 |
6063 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6064 |
6065 | [SECTION 30 - LANGUAGE STRUCTURE]
6066 | The following are the various language structures available within
6067 | ExprTk and their structural representations.
6068 |
6069 | (00) If Statement
6070 | (01) Else Statement
6071 | (02) Ternary Statement
6072 | (03) While Loop
6073 | (04) Repeat Until Loop
6074 | (05) For Loop
6075 | (06) Switch Statement
6076 | (07) Multi Subexpression Statement
6077 | (08) Multi Case-Consequent Statement
6078 | (09) Variable Definition Statement
6079 | (10) Vector Definition Statement
6080 | (11) String Definition Statement
6081 | (12) Range Statement
6082 | (13) Return Statement
6083 |
6084 |
6085 | (00) - If Statement
6086 | +-------------------------------------------------------------+
6087 | | |
6088 | | [if] ---> [(] ---> [condition] -+-> [,] -+ |
6089 | | | | |
6090 | | +---------------<---------------+ | |
6091 | | | | |
6092 | | | +------------------<------------------+ |
6093 | | | | |
6094 | | | +--> [consequent] ---> [,] ---> [alternative] ---> [)] |
6095 | | | |
6096 | | +--> [)] --+-> [{] ---> [expression*] ---> [}] --+ |
6097 | | | | |
6098 | | | +---------<----------+ |
6099 | | +----<-----+ | |
6100 | | | v |
6101 | | +--> [consequent] --> [;] -{}-> [else-statement] |
6102 | | |
6103 | +-------------------------------------------------------------+
6104 |
6105 |
6106 | (01) - Else Statement
6107 | +-------------------------------------------------------------+
6108 | | |
6109 | | [else] -+-> [alternative] ---> [;] |
6110 | | | |
6111 | | +--> [{] ---> [expression] ---> [}] |
6112 | | | |
6113 | | +--> [if-statement] |
6114 | | |
6115 | +-------------------------------------------------------------+
6116 |
6117 |
6118 | (02) - Ternary Statement
6119 | +-------------------------------------------------------------+
6120 | | |
6121 | | [condition] ---> [?] ---> [consequent] ---> [:] --+ |
6122 | | | |
6123 | | +------------------------<------------------------+ |
6124 | | | |
6125 | | +--> [alternative] --> [;] |
6126 | | |
6127 | +-------------------------------------------------------------+
6128 |
6129 |
6130 | (03) - While Loop
6131 | +-------------------------------------------------------------+
6132 | | |
6133 | | [while] ---> [(] ---> [condition] ---> [)] ---+ |
6134 | | | |
6135 | | +----------------------<----------------------+ |
6136 | | | |
6137 | | +--> [{] ---> [expression*] ---> [}] |
6138 | | |
6139 | +-------------------------------------------------------------+
6140 |
6141 |
6142 | (04) - Repeat Until Loop
6143 | +-------------------------------------------------------------+
6144 | | |
6145 | | [repeat] ---> [expression*] ---+ |
6146 | | | |
6147 | | +--------------<---------------+ |
6148 | | | |
6149 | | +--> [until] ---> [(] ---> [condition] --->[)] |
6150 | | |
6151 | +-------------------------------------------------------------+
6152 |
6153 |
6154 | (05) - For Loop
6155 | +-------------------------------------------------------------+
6156 | | |
6157 | | [for] ---> [(] -+-> [initialise expression] --+--+ |
6158 | | | | | |
6159 | | +------------->---------------+ v |
6160 | | | |
6161 | | +-----------------------<------------------------+ |
6162 | | | |
6163 | | +--> [;] -+-> [condition] -+-> [;] ---+ |
6164 | | | | | |
6165 | | +------->--------+ v |
6166 | | | |
6167 | | +------------------<---------+--------+ |
6168 | | | | |
6169 | | +--> [increment expression] -+-> [)] --+ |
6170 | | | |
6171 | | +------------------<-------------------+ |
6172 | | | |
6173 | | +--> [{] ---> [expression*] ---> [}] |
6174 | | |
6175 | +-------------------------------------------------------------+
6176 |
6177 |
6178 | (06) - Switch Statement
6179 | +-------------------------------------------------------------+
6180 | | |
6181 | | [switch] ---> [{] ---+ |
6182 | | | |
6183 | | +---------<----------+-----------<-----------+ |
6184 | | | | |
6185 | | +--> [case] ---> [condition] ---> [:] ---+ | |
6186 | | | | |
6187 | | +-------------------<--------------------+ | |
6188 | | | | |
6189 | | +--> [consequent] ---> [;] --------->--------+ |
6190 | | | | |
6191 | | | | |
6192 | | +--> [default] ---> [consequent] ---> [;] ---+ |
6193 | | | | |
6194 | | +---------------------<----------------------+ |
6195 | | | |
6196 | | +--> [}] |
6197 | | |
6198 | +-------------------------------------------------------------+
6199 |
6200 |
6201 | (07) - Multi Subexpression Statement
6202 | +-------------------------------------------------------------+
6203 | | |
6204 | | +--------------<---------------+ |
6205 | | | | |
6206 | | [
6214 |
6215 | (08) - Multi Case-Consequent Statement
6216 | +-------------------------------------------------------------+
6217 | | |
6218 | | [[*]] ---> [{] ---+ |
6219 | | | |
6220 | | +--------<--------+--------------<----------+ |
6221 | | | | |
6222 | | +--> [case] ---> [condition] ---> [:] ---+ | |
6223 | | | | |
6224 | | +-------------------<--------------------+ | |
6225 | | | | |
6226 | | +--> [consequent] ---> [;] ---+------>------+ |
6227 | | | |
6228 | | +--> [}] |
6229 | | |
6230 | +-------------------------------------------------------------+
6231 |
6232 |
6233 | (09) - Variable Definition Statement
6234 | +-------------------------------------------------------------+
6235 | | |
6236 | | [var] ---> [symbol] -+-> [:=] -+-> [expression] -+-> [;] |
6237 | | | | | |
6238 | | | +-----> [{}] -->--+ |
6239 | | | | |
6240 | | +------------->-------------+ |
6241 | | |
6242 | +-------------------------------------------------------------+
6243 |
6244 |
6245 | (10) - Vector Definition Statement
6246 | +-------------------------------------------------------------+
6247 | | |
6248 | | [var] ---> [symbol] ---> [[] ---> [constant] ---> []] --+ |
6249 | | | |
6250 | | +---------------------------<---------------------------+ |
6251 | | | |
6252 | | | +--------->---------+ |
6253 | | | | | |
6254 | | +--> [:=] ---> [{] -+-+-> [expression] -+-> [}] ---> [;] |
6255 | | | | |
6256 | | +--<--- [,] <-----+ |
6257 | | |
6258 | +-------------------------------------------------------------+
6259 |
6260 |
6261 | (11) - String Definition Statement
6262 | +-------------------------------------------------------------+
6263 | | |
6264 | | [var] --> [symbol] --> [:=] --> [str-expression] ---> [;] |
6265 | | |
6266 | +-------------------------------------------------------------+
6267 |
6268 |
6269 | (12) - Range Statement
6270 | +-------------------------------------------------------------+
6271 | | |
6272 | | +-------->--------+ |
6273 | | | | |
6274 | | [[] -+-> [expression] -+-> [:] -+-> [expression] -+--> []] |
6275 | | | | |
6276 | | +-------->--------+ |
6277 | | |
6278 | +-------------------------------------------------------------+
6279 |
6280 |
6281 | (13) - Return Statement
6282 | +-------------------------------------------------------------+
6283 | | |
6284 | | [return] ---> [[] -+-> [expression] -+-> []] ---> [;] |
6285 | | | | |
6286 | | +--<--- [,] <-----+ |
6287 | | |
6288 | +-------------------------------------------------------------+