A walk-through of parsing and evaluating your first expression with
@jsonic/expr. By the end you will have:
- Parsed
1+2*3into an S-expression AST - Plugged in an evaluator to compute the numeric result
- Seen how expressions compose with the rest of Jsonic JSON
The TypeScript and Go variants are kept side-by-side. Pick one language and follow the column; the inputs and outputs match.
# TypeScript
npm install @jsonic/expr jsonic
# Go
go get github.com/jsonicjs/expr/goTypeScript:
import { Jsonic } from 'jsonic'
import { Expr } from '@jsonic/expr'
const j = Jsonic.make().use(Expr)
console.log(j('1+2*3'))
// [ { src: '+', ... }, 1, [ { src: '*', ... }, 2, 3 ] ]Go:
package main
import (
"fmt"
expr "github.com/jsonicjs/expr/go"
)
func main() {
result, _ := expr.Parse("1+2*3")
fmt.Println(expr.Simplify(result))
// ["+" 1 ["*" 2 3]]
}The AST is an array whose first element is the operator. Operator
precedence (* binds tighter than +) shapes the tree without
parentheses.
An evaluator is a function that receives the op and its evaluated terms
and returns a value. Supply it via the plugin's evaluate option.
TypeScript:
import { Jsonic } from 'jsonic'
import { Expr } from '@jsonic/expr'
const math = (rule: any, ctx: any, op: any, ...terms: any[]) => {
switch (op.src) {
case '+': return terms[0] + terms[1]
case '-': return op.prefix ? -terms[0] : terms[0] - terms[1]
case '*': return terms[0] * terms[1]
case '/': return terms[0] / terms[1]
}
}
const j = Jsonic.make().use(Expr, { evaluate: math })
console.log(j('1+2*3')) // 7
console.log(j('-4+10')) // 6Go:
package main
import (
"fmt"
jsonic "github.com/jsonicjs/jsonic/go"
expr "github.com/jsonicjs/expr/go"
)
func math(r *jsonic.Rule, ctx *jsonic.Context, op *expr.Op, terms []interface{}) interface{} {
a, _ := terms[0].(float64)
switch op.Src {
case "+":
b, _ := terms[1].(float64)
return a + b
case "-":
if op.Prefix {
return -a
}
b, _ := terms[1].(float64)
return a - b
case "*":
b, _ := terms[1].(float64)
return a * b
case "/":
b, _ := terms[1].(float64)
return a / b
}
return nil
}
func main() {
result, _ := expr.Parse("1+2*3", map[string]interface{}{"evaluate": math})
fmt.Println(result) // 7
}Expressions live inside any Jsonic value slot, so you can drop them into objects and arrays:
j('{ total: 1+2*3, flags: [!true, -4] }')
// { total: 7, flags: [false, -4] } (after an evaluator for ! and - is added)- How-to guides for recipes: adding a custom operator,
using parens for grouping, building function-call syntax via
paren-preval. - Reference for the full
OpDefschema and the default operator table. - Explanation for how the Pratt parser and AST work.