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'constj=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"
)
funcmain() {
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'constmath=(rule: any,ctx: any,op: any, ...terms: any[])=>{switch(op.src){case'+': returnterms[0]+terms[1]case'-': returnop.prefix ? -terms[0] : terms[0]-terms[1]case'*': returnterms[0]*terms[1]case'/': returnterms[0]/terms[1]}}constj=Jsonic.make().use(Expr,{evaluate: math})console.log(j('1+2*3'))// 7console.log(j('-4+10'))// 6Go:
package main
import (
"fmt"
jsonic "github.com/jsonicjs/jsonic/go"
expr "github.com/jsonicjs/expr/go"
)
funcmath(r*jsonic.Rule, ctx*jsonic.Context, op*expr.Op, terms []interface{}) interface{} {
a, _:=terms[0].(float64)
switchop.Src {
case"+":
b, _:=terms[1].(float64)
returna+bcase"-":
ifop.Prefix {
return-a
}
b, _:=terms[1].(float64)
returna-bcase"*":
b, _:=terms[1].(float64)
returna*bcase"/":
b, _:=terms[1].(float64)
returna/b
}
returnnil
}
funcmain() {
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.