simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

expressions.js (6357B)


      1 /**
      2  * Expressions can be evaluated in various ways:
      3  *
      4  * 1. using the function math.evaluate
      5  * 2. using the function math.parse
      6  * 3. using a parser. A parser contains functions evaluate and parse,
      7  *    and keeps a scope with assigned variables in memory
      8  */
      9 
     10 // load math.js (using node.js)
     11 const math = require('..')
     12 
     13 // 1. using the function math.evaluate
     14 //
     15 // Function `evaluate` accepts a single expression or an array with
     16 // expressions as first argument, and has an optional second argument
     17 // containing a scope with variables and functions. The scope is a regular
     18 // JavaScript Object. The scope will be used to resolve symbols, and to write
     19 // assigned variables or function.
     20 console.log('1. USING FUNCTION MATH.EVAL')
     21 
     22 // evaluate expressions
     23 console.log('\nevaluate expressions')
     24 print(math.evaluate('sqrt(3^2 + 4^2)')) // 5
     25 print(math.evaluate('sqrt(-4)')) // 2i
     26 print(math.evaluate('2 inch to cm')) // 5.08 cm
     27 print(math.evaluate('cos(45 deg)')) // 0.70711
     28 
     29 // evaluate multiple expressions at once
     30 console.log('\nevaluate multiple expressions at once')
     31 print(math.evaluate([
     32   'f = 3',
     33   'g = 4',
     34   'f * g'
     35 ])) // [3, 4, 12]
     36 
     37 // provide a scope (just a regular JavaScript Object)
     38 console.log('\nevaluate expressions providing a scope with variables and functions')
     39 const scope = {
     40   a: 3,
     41   b: 4
     42 }
     43 
     44 // variables can be read from the scope
     45 print(math.evaluate('a * b', scope)) // 12
     46 
     47 // variable assignments are written to the scope
     48 print(math.evaluate('c = 2.3 + 4.5', scope)) // 6.8
     49 print(scope.c) // 6.8
     50 
     51 // scope can contain both variables and functions
     52 scope.hello = function (name) {
     53   return 'hello, ' + name + '!'
     54 }
     55 print(math.evaluate('hello("hero")', scope)) // "hello, hero!"
     56 
     57 // define a function as an expression
     58 const f = math.evaluate('f(x) = x ^ a', scope)
     59 print(f(2)) // 8
     60 print(scope.f(2)) // 8
     61 
     62 // 2. using function math.parse
     63 //
     64 // Function `math.parse` parses expressions into a node tree. The syntax is
     65 // similar to function `math.evaluate`.
     66 // Function `parse` accepts a single expression or an array with
     67 // expressions as first argument. The function returns a node tree, which
     68 // then can be compiled against math, and then evaluated against an (optional
     69 // scope. This scope is a regular JavaScript Object. The scope will be used
     70 // to resolve symbols, and to write assigned variables or function.
     71 console.log('\n2. USING FUNCTION MATH.PARSE')
     72 
     73 // parse an expression
     74 console.log('\nparse an expression into a node tree')
     75 const node1 = math.parse('sqrt(3^2 + 4^2)')
     76 print(node1.toString()) // "sqrt((3 ^ 2) + (4 ^ 2))"
     77 
     78 // compile and evaluate the compiled code
     79 // you could also do this in two steps: node1.compile().evaluate()
     80 print(node1.evaluate()) // 5
     81 
     82 // provide a scope
     83 console.log('\nprovide a scope')
     84 const node2 = math.parse('x^a')
     85 const code2 = node2.compile()
     86 print(node2.toString()) // "x ^ a"
     87 const scope2 = {
     88   x: 3,
     89   a: 2
     90 }
     91 print(code2.evaluate(scope2)) // 9
     92 
     93 // change a value in the scope and re-evaluate the node
     94 scope2.a = 3
     95 print(code2.evaluate(scope2)) // 27
     96 
     97 // 3. using function math.compile
     98 //
     99 // Function `math.compile` compiles expressions into a node tree. The syntax is
    100 // similar to function `math.evaluate`.
    101 // Function `compile` accepts a single expression or an array with
    102 // expressions as first argument, and returns an object with a function evaluate
    103 // to evaluate the compiled expression. On evaluation, an optional scope can
    104 // be provided. This scope will be used to resolve symbols, and to write
    105 // assigned variables or function.
    106 console.log('\n3. USING FUNCTION MATH.COMPILE')
    107 
    108 // parse an expression
    109 console.log('\ncompile an expression')
    110 const code3 = math.compile('sqrt(3^2 + 4^2)')
    111 
    112 // evaluate the compiled code
    113 print(code3.evaluate()) // 5
    114 
    115 // provide a scope for the variable assignment
    116 console.log('\nprovide a scope')
    117 const code4 = math.compile('a = a + 3')
    118 const scope3 = {
    119   a: 7
    120 }
    121 code4.evaluate(scope3)
    122 print(scope3.a) // 10
    123 
    124 // 4. using a parser
    125 //
    126 // In addition to the static functions `math.evaluate` and `math.parse`, math.js
    127 // contains a parser with functions `evaluate` and `parse`, which automatically
    128 // keeps a scope with assigned variables in memory. The parser also contains
    129 // some convenience methods to get, set, and remove variables from memory.
    130 console.log('\n4. USING A PARSER')
    131 const parser = math.parser()
    132 
    133 // evaluate with parser
    134 console.log('\nevaluate expressions')
    135 print(parser.evaluate('sqrt(3^2 + 4^2)')) // 5
    136 print(parser.evaluate('sqrt(-4)')) // 2i
    137 print(parser.evaluate('2 inch to cm')) // 5.08 cm
    138 print(parser.evaluate('cos(45 deg)')) // 0.70710678118655
    139 
    140 // define variables and functions
    141 console.log('\ndefine variables and functions')
    142 print(parser.evaluate('x = 7 / 2')) // 3.5
    143 print(parser.evaluate('x + 3')) // 6.5
    144 print(parser.evaluate('f2(x, y) = x^y')) // f2(x, y)
    145 print(parser.evaluate('f2(2, 3)')) // 8
    146 
    147 // manipulate matrices
    148 // Note that matrix indexes in the expression parser are one-based with the
    149 // upper-bound included. On a JavaScript level however, math.js uses zero-based
    150 // indexes with an excluded upper-bound.
    151 console.log('\nmanipulate matrices')
    152 print(parser.evaluate('k = [1, 2; 3, 4]')) // [[1, 2], [3, 4]]
    153 print(parser.evaluate('l = zeros(2, 2)')) // [[0, 0], [0, 0]]
    154 print(parser.evaluate('l[1, 1:2] = [5, 6]')) // [5, 6]
    155 print(parser.evaluate('l')) // [[5, 6], [0, 0]]
    156 print(parser.evaluate('l[2, :] = [7, 8]')) // [7, 8]
    157 print(parser.evaluate('l')) // [[5, 6], [7, 8]]
    158 print(parser.evaluate('m = k * l')) // [[19, 22], [43, 50]]
    159 print(parser.evaluate('n = m[2, 1]')) // 43
    160 print(parser.evaluate('n = m[:, 1]')) // [[19], [43]]
    161 
    162 // get and set variables and functions
    163 console.log('\nget and set variables and function in the scope of the parser')
    164 const x = parser.get('x')
    165 console.log('x =', x) // x = 3.5
    166 const f2 = parser.get('f2')
    167 console.log('f2 =', math.format(f2)) // f2 = f2(x, y)
    168 const h = f2(3, 3)
    169 console.log('h =', h) // h = 27
    170 
    171 parser.set('i', 500)
    172 print(parser.evaluate('i / 2')) // 250
    173 parser.set('hello', function (name) {
    174   return 'hello, ' + name + '!'
    175 })
    176 print(parser.evaluate('hello("hero")')) // "hello, hero!"
    177 
    178 // clear defined functions and variables
    179 parser.clear()
    180 
    181 /**
    182  * Helper function to output a value in the console. Value will be formatted.
    183  * @param {*} value
    184  */
    185 function print (value) {
    186   const precision = 14
    187   console.log(math.format(value, precision))
    188 }