commit 7434dc0a910c128ff75256f31142146c8f2f7784
Author: Charlie Stanton <charlie@shtanton.xyz>
Date: Wed, 17 Aug 2022 14:40:40 +0100
Initial commit
Implements:
JSON reading
Following pattern segments:
- all
- index
- filter (partially)
A bunch of operations for the actions
println
Diffstat:
A | go.mod | | | 3 | +++ |
A | main/eval.go | | | 907 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main/json.go | | | 97 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main/lex.go | | | 381 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main/main.go | | | 29 | +++++++++++++++++++++++++++++ |
A | main/parser.go | | | 398 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 1815 insertions(+), 0 deletions(-)
diff --git a/go.mod b/go.mod
@@ -0,0 +1,3 @@
+module main
+
+go 1.18
diff --git a/main/eval.go b/main/eval.go
@@ -0,0 +1,907 @@
+package main
+
+import (
+ "strconv"
+ "fmt"
+ "math"
+ "strings"
+)
+
+type ValueType int
+const (
+ TypeNull ValueType = iota
+ TypeBool
+ TypeNumber
+ TypeString
+ TypeArray
+ TypeMap
+)
+
+type ValueNull struct {}
+type ValueBool bool
+type ValueNumber float64
+type ValueString string
+type ValueArray []Value
+type ValueMap map[string]Value
+
+type Value interface{
+ StackValue
+ getPath([]TreePathSegment) Value
+ typ() ValueType
+ clone() Value
+ withAssignment([]Value, Value) Value
+ castToBool() ValueBool
+ castToNumber() ValueNumber
+ castToString() ValueString
+ castToArray() ValueArray
+ castToMap() ValueMap
+ add(Value) Value
+ sub(Value) Value
+ mul(Value) Value
+ div(Value) Value
+ index(Value) Value
+}
+
+func castToType(v Value, t ValueType) Value {
+ switch t {
+ case TypeNull:
+ return ValueNull{}
+ case TypeBool:
+ return v.castToBool()
+ case TypeNumber:
+ return v.castToNumber()
+ case TypeString:
+ return v.castToString()
+ case TypeArray:
+ return v.castToArray()
+ case TypeMap:
+ return v.castToMap()
+ default:
+ panic("Unknown value type")
+ }
+}
+
+func (v ValueNull) getPath(path []TreePathSegment) Value {
+ if len(path) != 0 {
+ panic("Tried to index null")
+ }
+ return v
+}
+func (v ValueNull) typ() ValueType {
+ return TypeNull
+}
+func (v ValueNull) clone() Value {
+ return v
+}
+func (v ValueNull) withAssignment(path []Value, value Value) Value {
+ if len(path) == 0 {
+ return value
+ }
+ res := make(ValueMap)
+ res[string(path[0].castToString())] = ValueNull{}.withAssignment(path[1:], value)
+ return res
+}
+func (v ValueNull) castToBool() ValueBool {
+ return false
+}
+func (v ValueNull) castToNumber() ValueNumber {
+ return 0
+}
+func (v ValueNull) castToString() ValueString {
+ return ""
+}
+func (v ValueNull) castToArray() ValueArray {
+ var res []Value
+ return res
+}
+func (v ValueNull) castToMap() ValueMap {
+ return make(map[string]Value)
+}
+func (v ValueNull) add(w Value) Value {
+ return w
+}
+func (v ValueNull) sub(w Value) Value {
+ typ := w.typ()
+ if typ == TypeNull {
+ return ValueNull{}
+ }
+ return castToType(v, typ).sub(w)
+}
+func (v ValueNull) mul(w Value) Value {
+ typ := w.typ()
+ if typ == TypeNull {
+ return ValueNull{}
+ }
+ return castToType(v, typ).mul(w)
+}
+func (v ValueNull) div(w Value) Value {
+ typ := w.typ()
+ if typ == TypeNull {
+ return ValueNull{}
+ }
+ return castToType(v, typ).div(w)
+}
+func (v ValueNull) index(w Value) Value {
+ return ValueNull {}
+}
+
+func (v ValueBool) withAssignment(path []Value, value Value) Value {
+ if len(path) == 0 {
+ return value
+ }
+ res := make(ValueMap)
+ res[string(path[0].castToString())] = ValueNull{}.withAssignment(path[1:], value)
+ return res
+}
+func (v ValueBool) getPath(path []TreePathSegment) Value {
+ if len(path) != 0 {
+ panic("Tried to index bool")
+ }
+ return v
+}
+func (v ValueBool) typ() ValueType {
+ return TypeBool
+}
+func (v ValueBool) clone() Value {
+ return v
+}
+func (v ValueBool) castToBool() ValueBool {
+ return v
+}
+func (v ValueBool) castToNumber() ValueNumber {
+ if v {
+ return 1
+ } else {
+ return 0
+ }
+}
+func (v ValueBool) castToString() ValueString {
+ if v {
+ return "true"
+ } else {
+ return "false"
+ }
+}
+func (v ValueBool) castToArray() ValueArray {
+ var res []Value
+ if v {
+ res = append(res, ValueNull{})
+ }
+ return res
+}
+func (v ValueBool) castToMap() ValueMap {
+ res := make(map[string]Value)
+ if v {
+ res[""] = ValueNull{}
+ }
+ return res
+}
+func (v ValueBool) add(w Value) Value {
+ return v || w.castToBool()
+}
+func (v ValueBool) sub(w Value) Value {
+ rhs := w.castToBool()
+ return (v || rhs) && !(v && rhs)
+}
+func (v ValueBool) mul(w Value) Value {
+ return v && w.castToBool()
+}
+func (v ValueBool) div(w Value) Value {
+ rhs := w.castToBool()
+ return (v && rhs) || !(v || rhs)
+}
+func (v ValueBool) index(w Value) Value {
+ return v
+}
+
+func (v ValueNumber) withAssignment(path []Value, value Value) Value {
+ if len(path) == 0 {
+ return value
+ }
+ res := make(ValueMap)
+ res[string(path[0].castToString())] = ValueNull{}.withAssignment(path[1:], value)
+ return res
+}
+func (v ValueNumber) getPath(path []TreePathSegment) Value {
+ if len(path) != 0 {
+ panic("Tried to index number")
+ }
+ return v
+}
+func (v ValueNumber) typ() ValueType {
+ return TypeNumber
+}
+func (v ValueNumber) clone() Value {
+ return v
+}
+func (v ValueNumber) castToBool() ValueBool {
+ return v != 0
+}
+func (v ValueNumber) castToNumber() ValueNumber {
+ return v
+}
+func (v ValueNumber) castToString() ValueString {
+ return ValueString(strconv.FormatFloat(float64(v), 'g', 10, 64))
+}
+func (v ValueNumber) castToArray() ValueArray {
+ res := make([]Value, int(math.Round(float64(v))))
+ for i := range res {
+ res[i] = ValueNull {}
+ }
+ return res
+}
+func (v ValueNumber) castToMap() ValueMap {
+ res := make(map[string]Value)
+ res[string(v.castToString())] = ValueNull {}
+ return res
+}
+func (v ValueNumber) add(w Value) Value {
+ return v + w.castToNumber()
+}
+func (v ValueNumber) sub(w Value) Value {
+ return v - w.castToNumber()
+}
+func (v ValueNumber) mul(w Value) Value {
+ return v * w.castToNumber()
+}
+func (v ValueNumber) div(w Value) Value {
+ return v / w.castToNumber()
+}
+func (v ValueNumber) index(w Value) Value {
+ return v
+}
+
+func (v ValueString) withAssignment(path []Value, value Value) Value {
+ if len(path) == 0 {
+ return value
+ }
+ if len(path) > 1 {
+ panic("Cannot index string twice")
+ }
+ index := int(math.Round(float64(path[0].castToNumber())))
+ var builder strings.Builder
+ reader := strings.NewReader(string(v))
+ for i := 0; i < index; i += 1 {
+ r, _, err := reader.ReadRune()
+ if err != nil {
+ panic("Error assigning to string index")
+ }
+ builder.WriteRune(r)
+ }
+ _, _, err := reader.ReadRune()
+ if err != nil {
+ panic("Error assigning to string index (2)")
+ }
+ builder.WriteString(string(value.castToString()))
+ for {
+ r, _, err := reader.ReadRune()
+ if err != nil {
+ break
+ }
+ builder.WriteRune(r)
+ }
+ return ValueString(builder.String())
+}
+func (v ValueString) getPath(path []TreePathSegment) Value {
+ if len(path) != 0 {
+ panic("Tried to index string")
+ }
+ return v
+}
+func (v ValueString) typ() ValueType {
+ return TypeString
+}
+func (v ValueString) clone() Value {
+ return v
+}
+func (v ValueString) castToBool() ValueBool {
+ if v == "" || v == "false" {
+ return false
+ } else {
+ return true
+ }
+}
+func (v ValueString) castToNumber() ValueNumber {
+ num, err := strconv.ParseFloat(string(v), 64)
+ if err != nil {
+ return 0
+ }
+ return ValueNumber(num)
+}
+func (v ValueString) castToString() ValueString {
+ return v
+}
+func (v ValueString) castToArray() ValueArray {
+ fields := strings.Fields(string(v))
+ res := make([]Value, len(fields))
+ for i, field := range fields {
+ res[i] = ValueString(field)
+ }
+ return res
+}
+func (v ValueString) castToMap() ValueMap {
+ res := make(map[string]Value)
+ res[string(v)] = ValueNull {}
+ return res
+}
+func (v ValueString) add(w Value) Value {
+ return v + w.castToString()
+}
+func (v ValueString) sub(w Value) Value {
+ // TODO
+ panic("Cannot subtract strings yet")
+}
+func (v ValueString) mul(w Value) Value {
+ num := int(math.Round(float64(w.castToNumber())))
+ var builder strings.Builder
+ for i := 0; i < num; i += 1 {
+ builder.WriteString(string(v))
+ }
+ return ValueString(builder.String())
+}
+func (v ValueString) div(w Value) Value {
+ // TODO
+ panic("Cannot divide strings yet")
+}
+func (v ValueString) index(w Value) Value {
+ index := int(math.Round(float64(w.castToNumber())))
+ // TODO: use proper strings functions here
+ return ValueString(v[index])
+}
+
+func (v ValueArray) withAssignment(path []Value, value Value) Value {
+ if len(path) == 0 {
+ return value
+ }
+ index := int(math.Round(float64(path[0].castToNumber())))
+ res := v.clone().(ValueArray)
+ res[index] = res[index].withAssignment(path[1:], value)
+ return res
+}
+func (v ValueArray) getPath(path []TreePathSegment) Value {
+ if len(path) == 0 {
+ return v
+ }
+ switch path[0].(type) {
+ case int:
+ return v[path[0].(int)].getPath(path[1:])
+ default:
+ panic("Tried to index array with string")
+ }
+}
+func (v ValueArray) typ() ValueType {
+ return TypeArray
+}
+func (v ValueArray) clone() Value {
+ var res []Value
+ for _, el := range v {
+ res = append(res, el.clone())
+ }
+ return ValueArray(res)
+}
+func (v ValueArray) castToBool() ValueBool {
+ return len(v) > 0
+}
+func (v ValueArray) castToNumber() ValueNumber {
+ return ValueNumber(len(v))
+}
+func (v ValueArray) castToString() ValueString {
+ var builder strings.Builder
+ for i, el := range v {
+ if i != 0 {
+ builder.WriteString(" ")
+ }
+ builder.WriteString(string(el.castToString()))
+ }
+ return ValueString(builder.String())
+}
+func (v ValueArray) castToArray() ValueArray {
+ return v
+}
+func (v ValueArray) castToMap() ValueMap {
+ res := make(map[string]Value)
+ for _, el := range v {
+ res[string(el.castToString())] = ValueNull {}
+ }
+ return res
+}
+func (v ValueArray) add(w Value) Value {
+ return append(v, w.castToArray()...)
+}
+func (v ValueArray) sub(w Value) Value {
+ width := int(math.Round(float64(w.castToNumber())))
+ if len(v) < width {
+ return v
+ } else {
+ res := make([]Value, 2)
+ res[0] = v[0:width]
+ res[1] = v[width:]
+ return ValueArray(res)
+ }
+}
+func (v ValueArray) mul(w Value) Value {
+ var res []Value
+ target := int(math.Round(float64(w.castToNumber())))
+ for i := 0; i < target; i += 1 {
+ res = append(res, v...)
+ }
+ return ValueArray(res)
+}
+func(v ValueArray) div(w Value) Value {
+ l := len(v)
+ parts := int(math.Round(float64(w.castToNumber())))
+ var res []Value
+ part_width := l / parts
+ remaining_els := l % parts
+ progress := 0
+ for i := 0; i < parts; i += 1 {
+ if i < remaining_els {
+ res = append(res, v[progress:progress+part_width+1])
+ progress += part_width + 1
+ } else {
+ res = append(res, v[progress:progress+part_width])
+ progress += part_width
+ }
+ }
+ return ValueArray(res)
+}
+func (v ValueArray) index(w Value) Value {
+ index := int(math.Round(float64(w.castToNumber())))
+ return v[index]
+}
+
+func (v ValueMap) withAssignment(path []Value, value Value) Value {
+ if len(path) == 0 {
+ return value
+ }
+ index := string(path[0].castToString())
+ res := v.clone().(ValueMap)
+ part, hasPart := res[index]
+ if !hasPart {
+ part = ValueNull {}
+ }
+ res[index] = part.withAssignment(path[1:], value)
+ return res
+}
+func (v ValueMap) getPath(path []TreePathSegment) Value {
+ if len(path) == 0 {
+ return v
+ }
+ switch path[0].(type) {
+ case string:
+ return v[path[0].(string)].getPath(path[1:])
+ default:
+ panic("Tried to index map with int")
+ }
+}
+func (v ValueMap) typ() ValueType {
+ return TypeMap
+}
+func (v ValueMap) clone() Value {
+ res := make(map[string]Value)
+ for key, value := range v {
+ res[key] = value.clone()
+ }
+ return ValueMap(res)
+}
+func (v ValueMap) castToBool() ValueBool {
+ return len(v) > 0
+}
+func (v ValueMap) castToNumber() ValueNumber {
+ return ValueNumber(len(v))
+}
+func (v ValueMap) castToString() ValueString {
+ var builder strings.Builder
+ first := true
+ for key, val := range v {
+ if !first {
+ builder.WriteString(" ")
+ }
+ builder.WriteString(key)
+ builder.WriteString(": ")
+ builder.WriteString(string(val.castToString()))
+ }
+ return ValueString(builder.String())
+}
+func (v ValueMap) castToArray() ValueArray {
+ var res []Value
+ for key := range v {
+ res = append(res, ValueString(key))
+ }
+ return res
+}
+func (v ValueMap) castToMap() ValueMap {
+ return v
+}
+func (v ValueMap) add(w Value) Value {
+ other := w.castToMap()
+ res := v.clone().(ValueMap)
+ for key, val := range other {
+ res[key] = val
+ }
+ return res
+}
+func (v ValueMap) sub(w Value) Value {
+ res := v.clone().(ValueMap)
+ to_remove := w.castToArray()
+ for _, key := range to_remove {
+ delete(res, string(key.castToString()))
+ }
+ return res
+}
+func (v ValueMap) mul(w Value) Value {
+ rhs := w.castToMap()
+ res := make(map[string]Value)
+ for key, val := range v {
+ val2, hasRhsVal := rhs[key]
+ if !hasRhsVal {
+ val2 = ValueNull {}
+ }
+ res[key] = ValueArray {val, val2}
+ }
+ for key, val2 := range rhs {
+ _, hasLhsVal := v[key]
+ if !hasLhsVal {
+ res[key] = ValueArray {ValueNull {}, val2}
+ }
+ }
+ return ValueMap(res)
+}
+func (v ValueMap) div(w Value) Value {
+ // TODO
+ panic("Dividing a map not yet implemented")
+}
+func (v ValueMap) index(w Value) Value {
+ index := string(w.castToString())
+ res, hasValue := v[index]
+ if !hasValue {
+ return ValueNull {}
+ }
+ return res
+}
+
+type VariableReference string
+type IndexReference struct {
+ parent StackValue
+ index Value
+}
+
+type StackValue interface{
+ toValue(*EvalState) Value
+ toAddress() Address
+}
+
+func (v ValueNull) toValue(state *EvalState) Value {
+ return v
+}
+func (v ValueNull) toAddress() Address {
+ panic("Invalid assign to non variable")
+}
+
+func (v ValueBool) toValue(state *EvalState) Value {
+ return v
+}
+func (v ValueBool) toAddress() Address {
+ panic("Invalid assign to non variable")
+}
+
+func (v ValueNumber) toValue(state *EvalState) Value {
+ return v
+}
+func (v ValueNumber) toAddress() Address {
+ panic("Invalid assign to non variable")
+}
+
+func (v ValueString) toValue(state *EvalState) Value {
+ return v
+}
+func (v ValueString) toAddress() Address {
+ panic("Invalid assign to non variable")
+}
+
+func (v ValueArray) toValue(state *EvalState) Value {
+ return v
+}
+func (v ValueArray) toAddress() Address {
+ panic("Invalid assign to non variable")
+}
+
+func (v ValueMap) toValue(state *EvalState) Value {
+ return v
+}
+func (v ValueMap) toAddress() Address {
+ panic("Invalid assign to non variable")
+}
+
+func (v VariableReference) toValue(state *EvalState) Value {
+ value, hasValue := state.variables[string(v)]
+ if !hasValue {
+ state.variables[string(v)] = ValueNull {}
+ return ValueNull {}
+ }
+ return value.clone()
+}
+func (v VariableReference) toAddress() Address {
+ return v
+}
+
+func (v IndexReference) toValue(state *EvalState) Value {
+ return v.parent.toValue(state).index(v.index)
+}
+func (v IndexReference) toAddress() Address {
+ return v
+}
+
+type Address interface {
+ assign(*EvalState, Value)
+ assignPath(*EvalState, []Value, Value)
+}
+
+func (v VariableReference) assign(state *EvalState, value Value) {
+ state.variables[string(v)] = value
+}
+func (v VariableReference) assignPath(state *EvalState, path []Value, value Value) {
+ state.variables[string(v)] = state.variables[string(v)].withAssignment(path, value)
+}
+
+func (v IndexReference) assign(state *EvalState, value Value) {
+ v.assignPath(state, nil, value)
+}
+func (v IndexReference) assignPath(state *EvalState, path []Value, value Value) {
+ v.parent.toAddress().assignPath(state, append([]Value{v.index}, path...), value)
+}
+
+type EvalState struct {
+ stack []StackValue
+ variables map[string]Value
+ data Value
+}
+
+func (state *EvalState) push(value StackValue) {
+ state.stack = append(state.stack, value)
+}
+
+func (state *EvalState) pop() StackValue {
+ if len(state.stack) < 1 {
+ panic("Error tried to pop empty stack")
+ }
+ index := len(state.stack) - 1
+ value := state.stack[index]
+ state.stack = state.stack[:index]
+ return value
+}
+
+func (state *EvalState) popValue() Value {
+ return state.pop().toValue(state)
+}
+
+func (state *EvalState) popAddress() Address {
+ return state.pop().toAddress()
+}
+
+func (index PatternSegmentIndex) matches(_ *EvalState, pathSegment TreePathSegment) bool {
+ switch pathSegment.(type) {
+ case string:
+ return string(index) == pathSegment.(string)
+ case int:
+ return string(index) == strconv.Itoa(pathSegment.(int))
+ default:
+ panic("Bug in treek, invalid TreePathSegment")
+ }
+}
+
+func (filter PatternSegmentFilter) matches(state *EvalState, pathSegment TreePathSegment) bool {
+ result := evalExpr(state, Expression(filter))
+ return bool(result.castToBool())
+}
+
+func (segment PatternSegmentBasic) matches(state *EvalState, pathSegment TreePathSegment) bool {
+ switch segment {
+ case PatternSegmentAll:
+ return true
+ default:
+ panic("Invalid basic pattern segment")
+ }
+}
+
+func matchPattern(state *EvalState, pattern Pattern, walkItem TreeWalkItem) bool {
+ if len(pattern.segments) != len(walkItem.path) || pattern.isFirst != walkItem.first{
+ return false
+ }
+ for i, patternSegment := range pattern.segments {
+ pathSegment := walkItem.path[i]
+ if !patternSegment.matches(state, pathSegment) {
+ return false
+ }
+ }
+ return true
+}
+
+func evalAction(state *EvalState, action Expression, node TreeWalkItem) {
+ if len(action) == 0 {
+ subroutinePrintln([]Value{state.data.getPath(node.path)})
+ return
+ }
+ state.variables["path"] = pathToValueArray(node.path).clone()
+ state.variables["$0"] = state.data.getPath(node.path).clone()
+ evalExpr(state, action)
+}
+
+func (instruction InstructionBasic) eval(state *EvalState) {
+ switch instruction {
+ case InstructionAdd:
+ rhs := state.popValue()
+ lhs := state.popValue()
+ state.push(lhs.add(rhs))
+ case InstructionSub:
+ rhs := state.popValue()
+ lhs := state.popValue()
+ state.push(lhs.sub(rhs))
+ case InstructionDiv:
+ rhs := state.popValue()
+ lhs := state.popValue()
+ state.push(lhs.div(rhs))
+ case InstructionMul:
+ rhs := state.popValue()
+ lhs := state.popValue()
+ state.push(lhs.mul(rhs))
+ case InstructionIgnore:
+ state.popValue()
+ case InstructionPushNull:
+ state.push(ValueNull {})
+ case InstructionAssign:
+ rhs := state.popValue()
+ lhs := state.popAddress()
+ lhs.assign(state, rhs)
+ state.push(ValueNull {})
+ case InstructionIndex:
+ index := state.popValue()
+ parent := state.pop()
+ state.push(IndexReference {parent, index})
+ case InstructionDup:
+ val := state.pop()
+ state.push(val)
+ state.push(val.toValue(state).clone())
+ default:
+ panic("Error: Tried to execute invalid basic instruction")
+ }
+}
+
+func (n InstructionPushNumber) eval(state *EvalState) {
+ state.push(ValueNumber(n))
+}
+
+func (variable InstructionPushVariable) eval(state *EvalState) {
+ state.push(VariableReference(variable))
+}
+
+func (s InstructionPushString) eval(state *EvalState) {
+ state.push(ValueString(s))
+}
+
+type SubroutineFn func ([]Value) Value
+
+func printSingle(arg Value) {
+ switch arg.(type) {
+ case ValueNull:
+ fmt.Print("null")
+ case ValueBool:
+ fmt.Printf("%v", bool(arg.(ValueBool)))
+ case ValueNumber:
+ fmt.Printf("%v", float64(arg.(ValueNumber)))
+ case ValueString:
+ fmt.Printf("%q", string(arg.(ValueString)))
+ case ValueArray:
+ fmt.Print("[")
+ for i, el := range arg.(ValueArray) {
+ if i != 0 {
+ fmt.Print(", ")
+ }
+ printSingle(el)
+ }
+ fmt.Print("]")
+ case ValueMap:
+ fmt.Print("{")
+ isStart := true
+ for key, value := range arg.(ValueMap) {
+ if !isStart {
+ fmt.Print(", ")
+ }
+ fmt.Printf("%q: ", key)
+ printSingle(value)
+ isStart = false
+ }
+ fmt.Print("}")
+ }
+}
+func subroutinePrintln(args []Value) Value {
+ for i, arg := range args {
+ if i != 0 {
+ fmt.Print(" ")
+ }
+ printSingle(arg)
+ }
+ fmt.Print("\n")
+ return ValueNull{}
+}
+
+func (call InstructionCall) eval(state *EvalState) {
+ args := make([]Value, call.nargs)
+ for i := call.nargs - 1; i >= 0; i -= 1 {
+ args[i] = state.popValue()
+ }
+ subroutines := map[Subroutine]SubroutineFn {
+ SubroutinePrintln: subroutinePrintln,
+ }
+ subroutine, isSubroutine := subroutines[call.subroutine]
+ if !isSubroutine {
+ panic("Error: Invalid subroutine")
+ }
+ state.push(subroutine(args))
+}
+
+func evalExpr(state *EvalState, expr Expression) Value {
+ for _, instruction := range expr {
+ instruction.eval(state)
+ }
+ return state.popValue()
+}
+
+func pathToValueArray(path []TreePathSegment) ValueArray {
+ value := make(ValueArray, len(path))
+ for i, segment := range path {
+ switch segment.(type) {
+ case string:
+ value[i] = ValueString(segment.(string))
+ case int:
+ value[i] = ValueNumber(float64(segment.(int)))
+ }
+ }
+ return value
+}
+
+type TreeWalkItem struct {
+ path []TreePathSegment
+ first bool
+}
+
+func walkPaths(data Value, path []TreePathSegment, out chan TreeWalkItem) {
+ out <- TreeWalkItem {path, true}
+ switch data.(type) {
+ case ValueNull, ValueBool, ValueNumber, ValueString:
+ case ValueArray:
+ for i, el := range data.(ValueArray) {
+ walkPaths(el, append(path, i), out)
+ }
+ case ValueMap:
+ for key, el := range data.(ValueMap) {
+ walkPaths(el, append(path, key), out)
+ }
+ }
+ out <- TreeWalkItem {path, false}
+}
+func pathsRoutine(data Value, path []TreePathSegment, out chan TreeWalkItem) {
+ walkPaths(data, path, out)
+ close(out)
+}
+func getPaths(data Value) chan TreeWalkItem {
+ out := make(chan TreeWalkItem)
+ go pathsRoutine(data, nil, out)
+ return out
+}
+
+func Eval(program Program, data Value) {
+ state := &EvalState {
+ stack: nil,
+ variables: make(map[string]Value),
+ data: data,
+ }
+ paths := getPaths(data)
+ for node := range paths {
+ for _, block := range program.blocks {
+ if matchPattern(state, block.pattern, node) {
+ evalAction(state, block.action, node)
+ }
+ }
+ }
+}
diff --git a/main/json.go b/main/json.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "io"
+ "encoding/json"
+)
+
+func tokenToValue(token json.Token) Value {
+ switch token.(type) {
+ case nil:
+ return ValueNull {}
+ case bool:
+ return ValueBool(token.(bool))
+ case float64:
+ return ValueNumber(token.(float64))
+ case string:
+ return ValueString(token.(string))
+ default:
+ panic("Can't convert JSON token to value")
+ }
+}
+
+func readValue(dec *json.Decoder) (value Value, empty bool) {
+ if !dec.More() {
+ return nil, true
+ }
+ t, err := dec.Token()
+ if err == io.EOF {
+ return nil, true
+ } else if err != nil {
+ panic("Invalid JSON")
+ }
+ switch t.(type) {
+ case nil, string, float64, bool:
+ v := tokenToValue(t)
+ return v, false
+ case json.Delim:
+ switch rune(t.(json.Delim)) {
+ case '[':
+ var value []Value
+ for dec.More() {
+ v, empty := readValue(dec)
+ if empty {
+ break
+ }
+ value = append(value, v)
+ }
+ t, err := dec.Token()
+ if err != nil {
+ panic("Invalid JSON")
+ }
+ delim, isDelim := t.(json.Delim)
+ if !isDelim || delim != ']' {
+ panic("Expected ] in JSON")
+ }
+ v := ValueArray(value)
+ return v, false
+ case '{':
+ value := make(map[string]Value)
+ for dec.More() {
+ t, _ := dec.Token()
+ key, keyIsString := t.(string)
+ if !keyIsString {
+ panic("Invalid JSON")
+ }
+ v, empty := readValue(dec)
+ if empty {
+ panic("Invalid JSON")
+ }
+ value[key] = v
+ }
+ t, err := dec.Token()
+ if err != nil {
+ panic("Invalid JSON")
+ }
+ delim, isDelim := t.(json.Delim)
+ if !isDelim || delim != '}' {
+ panic("Expected } in JSON")
+ }
+ v := ValueMap(value)
+ return v, false
+ default:
+ panic("Error parsing JSON")
+ }
+ default:
+ panic("Invalid JSON token")
+ }
+}
+
+func Json(r io.Reader) Value {
+ dec := json.NewDecoder(r)
+ value, isEmpty := readValue(dec)
+ if isEmpty {
+ panic("Missing JSON input")
+ }
+ return value
+}
diff --git a/main/lex.go b/main/lex.go
@@ -0,0 +1,381 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+ "unicode/utf8"
+)
+
+type stateFunc func(*lexer) stateFunc
+
+type lexer struct {
+ input string
+ start int
+ pos int
+ width int
+ nestingLevel int
+ tokenStream chan Token
+}
+
+func (l *lexer) run() {
+ for state := lexBlockStart; state != nil; {
+ state = state(l)
+ }
+ close(l.tokenStream)
+}
+
+func (l *lexer) emit(t TokenType) {
+ l.tokenStream <- Token{
+ typ: t,
+ val: l.input[l.start:l.pos],
+ }
+ l.start = l.pos
+}
+
+func (l *lexer) errorf(format string, args ...interface{}) stateFunc {
+ l.tokenStream <- Token{
+ typ: TokenErr,
+ val: fmt.Sprintf(format, args...),
+ }
+ return nil
+}
+
+const eof rune = -1
+
+func (l *lexer) next() rune {
+ if l.pos >= len(l.input) {
+ l.width = 0
+ return eof
+ }
+ var r rune
+ r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
+ l.pos += l.width
+ return r
+}
+
+func (l *lexer) backup() {
+ l.pos -= l.width
+}
+
+func (l *lexer) ignore() {
+ l.start = l.pos
+}
+
+func (l *lexer) reset() {
+ l.pos = l.start
+}
+
+func (l *lexer) peek() rune {
+ w := l.width
+ r := l.next()
+ l.backup()
+ l.width = w
+ return r
+}
+
+func (l *lexer) accept(valid string) bool {
+ if strings.IndexRune(valid, l.next()) >= 0 {
+ return true
+ }
+ l.backup()
+ return false
+}
+
+func (l *lexer) acceptAll(valid string) {
+ for strings.IndexRune(valid, l.next()) >= 0 {}
+ l.backup()
+}
+
+func (l *lexer) acceptPassing(valid func(rune) bool) bool {
+ if valid(l.next()) {
+ return true
+ }
+ l.backup()
+ return false
+}
+
+func (l *lexer) acceptAllPassing(valid func(rune) bool) {
+ for valid(l.next()) {}
+ l.backup()
+}
+
+type TokenType int
+
+const (
+ TokenErr TokenType = iota // Lexing error
+ TokenEOF // end of file
+ TokenNumber // number literal
+ TokenIdentifier // identifier
+ TokenAdd // +
+ TokenSub // -
+ TokenAst // *
+ TokenDiv // /
+ TokenDot // .
+ TokenComma // ,
+ TokenSemicolon // ;
+ TokenLParen // (
+ TokenRParen // )
+ TokenLBrace // {
+ TokenRBrace // }
+ TokenLBrack // [
+ TokenRBrack // ]
+ TokenIndexPattern // An index pattern segment
+ TokenAssign // =
+ TokenCircum // ^
+ TokenDoubleQuote // "
+ TokenStringLiteral // String literal
+ TokenAddAssign // +=
+ TokenSubAssign // -=
+ TokenAstAssign // *=
+ TokenDivAssign // /=
+)
+
+type Token struct {
+ typ TokenType
+ val string
+}
+
+func (t Token) String() string {
+ switch t.typ {
+ case TokenEOF:
+ return "EOF"
+ case TokenErr:
+ return t.val
+ }
+ if len(t.val) > 10 {
+ return fmt.Sprintf("%.10q...", t.val)
+ }
+ return fmt.Sprintf("%q", t.val)
+}
+
+func Lex(input string) chan Token {
+ l := &lexer{
+ input: input,
+ tokenStream: make(chan Token),
+ }
+ go l.run()
+ return l.tokenStream
+}
+
+const (
+ whitespace string = " \t"
+ whitespaceNewlines string = " \t\r\n"
+)
+
+func isAlpha(r rune) bool {
+ return ('a' <= r && r < 'z') || ('A' <= r && r <= 'Z')
+}
+func isDigit(r rune) bool {
+ return '0' <= r && r <= '9'
+}
+func isAlphaNumeric(r rune) bool {
+ return isAlpha(r) || isDigit(r)
+}
+func isIdentifierStartRune(r rune) bool {
+ return isAlpha(r) || r == '_' || r == '$'
+}
+func isIdentifierRune(r rune) bool {
+ return isIdentifierStartRune(r) || isDigit(r)
+}
+
+func lexBlockStart(l *lexer) stateFunc {
+ l.acceptAll(whitespace)
+ l.ignore()
+ if l.peek() == eof {
+ l.emit(TokenEOF)
+ return nil
+ }
+ if l.accept("^") {
+ l.emit(TokenCircum)
+ }
+ if l.accept("{") {
+ l.emit(TokenLBrace)
+ l.nestingLevel += 1
+ return lexStartAction
+ }
+ return lexPattern
+}
+
+func lexPattern(l *lexer) stateFunc {
+ r := l.next()
+ switch {
+ case isIdentifierRune(r):
+ l.backup()
+ return lexIndexPattern
+ case r == '(':
+ l.nestingLevel += 1
+ l.emit(TokenLParen)
+ return lexFilterPattern
+ case r == '*':
+ l.emit(TokenAst)
+ return lexPatternEnd
+ }
+ return l.errorf("Invalid Pattern")
+}
+
+// Next rune is identifier rune
+func lexIndexPattern(l *lexer) stateFunc {
+ l.acceptAllPassing(isIdentifierRune)
+ l.emit(TokenIndexPattern)
+ return lexPatternEnd
+}
+
+// Previous rune is (
+func lexFilterPattern(l *lexer) stateFunc {
+ for state := lexAction; state != nil; {
+ state = state(l)
+ }
+ return lexPatternEnd
+}
+
+func lexPatternEnd(l *lexer) stateFunc {
+ if l.accept(".") {
+ l.emit(TokenDot)
+ return lexPattern
+ }
+ l.acceptAll(whitespaceNewlines)
+ l.ignore()
+ if !l.accept("{") {
+ if l.peek() == eof {
+ l.emit(TokenEOF)
+ return nil
+ }
+ return l.errorf("Missing Action")
+ }
+ l.emit(TokenLBrace)
+ l.nestingLevel += 1
+ return lexStartAction
+}
+
+// Previous rune is {
+func lexStartAction(l *lexer) stateFunc {
+ for state := lexAction; state != nil; {
+ state = state(l)
+ }
+ return lexBlockStart
+}
+
+func lexAction(l *lexer) stateFunc {
+ l.acceptAll(whitespaceNewlines)
+ l.ignore()
+ doubleCharTokens := map[rune]map[rune]TokenType{
+ '+': {
+ '=': TokenAddAssign,
+ },
+ '-': {
+ '=': TokenSubAssign,
+ },
+ '*': {
+ '=': TokenAstAssign,
+ },
+ '/': {
+ '=': TokenDivAssign,
+ },
+ }
+ charTokens := map[rune]TokenType{
+ '+': TokenAdd,
+ '-': TokenSub,
+ '/': TokenDiv,
+ '*': TokenAst,
+ '.': TokenDot,
+ ',': TokenComma,
+ ';': TokenSemicolon,
+ '=': TokenAssign,
+ }
+ r := l.next()
+ charToken, isCharToken := charTokens[r]
+ doubleCharMap, hasDoubleCharMap := doubleCharTokens[r]
+ if hasDoubleCharMap {
+ doubleCharToken, hasDoubleCharToken := doubleCharMap[l.next()]
+ if hasDoubleCharToken {
+ l.emit(doubleCharToken)
+ return lexAction
+ } else {
+ l.backup()
+ }
+ }
+ switch {
+ case r == eof:
+ return l.errorf("Unclosed Action")
+ case r == '(':
+ l.nestingLevel += 1
+ l.emit(TokenLParen)
+ return lexAction
+ case r == ')':
+ l.nestingLevel -= 1
+ l.emit(TokenRParen)
+ if l.nestingLevel > 0 {
+ return lexAction
+ }
+ return nil
+ case r == '{':
+ l.nestingLevel += 1
+ l.emit(TokenLBrace)
+ return lexAction
+ case r == '}':
+ l.nestingLevel -= 1
+ l.emit(TokenRBrace)
+ if l.nestingLevel > 0 {
+ return lexAction
+ }
+ return nil
+ case r == '[':
+ l.nestingLevel += 1
+ l.emit(TokenLBrack)
+ return lexAction
+ case r == ']':
+ l.nestingLevel -= 1
+ l.emit(TokenRBrack)
+ if l.nestingLevel > 0 {
+ return lexAction
+ }
+ return nil
+ case isCharToken:
+ l.emit(charToken)
+ return lexAction
+ case isDigit(r):
+ return lexNumberLiteral
+ case isIdentifierStartRune(r):
+ return lexIdentifier
+ case r == '"':
+ l.emit(TokenDoubleQuote)
+ return lexStringLiteral
+ }
+ return l.errorf("Invalid Token: " + string(r))
+}
+
+// Just accepted the first digit
+func lexNumberLiteral(l *lexer) stateFunc {
+ l.acceptAllPassing(isDigit)
+ if l.accept(".") {
+ l.acceptAllPassing(isDigit)
+ }
+ if isIdentifierRune(l.peek()) {
+ l.next()
+ return l.errorf("Bad number: %q", l.input[l.start:l.pos])
+ }
+ l.emit(TokenNumber)
+ return lexAction
+}
+
+func lexStringLiteral(l *lexer) stateFunc {
+ for {
+ switch l.next() {
+ case eof:
+ return l.errorf("Missing closing quote in string literal")
+ case '"':
+ l.backup()
+ l.emit(TokenStringLiteral)
+ l.next()
+ l.emit(TokenDoubleQuote)
+ return lexAction
+ }
+ }
+}
+
+// Just accepted the first character
+func lexIdentifier(l *lexer) stateFunc {
+ l.acceptAllPassing(isIdentifierRune)
+ l.emit(TokenIdentifier)
+ return lexAction
+}
diff --git a/main/main.go b/main/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "bufio"
+)
+
+type TreePathSegment interface{}
+type TreeData struct {
+ path []TreePathSegment
+ value Value
+}
+type TreeStream chan TreeData
+
+func main() {
+ if len(os.Args) < 2 {
+ fmt.Println("Missing program arg")
+ return
+ }
+ input := os.Args[1]
+ tokens := Lex(input)
+ program := Parse(tokens)
+
+ stdin := bufio.NewReader(os.Stdin)
+ data := Json(stdin)
+
+ Eval(program, data)
+}
diff --git a/main/parser.go b/main/parser.go
@@ -0,0 +1,398 @@
+package main
+
+import (
+ "strconv"
+ "fmt"
+)
+
+type InstructionBasic int
+const (
+ InstructionAdd InstructionBasic = iota
+ InstructionSub
+ InstructionDiv
+ InstructionMul
+ InstructionIgnore
+ InstructionPushNull
+ InstructionAssign
+ InstructionIndex
+ InstructionDup
+)
+
+type InstructionPushNumber float64
+type InstructionPushVariable string
+type InstructionPushString string
+
+type Subroutine int
+const (
+ SubroutinePrintln Subroutine = iota
+)
+type InstructionCall struct {
+ subroutine Subroutine
+ nargs int
+}
+
+type Instruction interface {
+ debug()
+ eval(*EvalState)
+}
+
+func (i InstructionBasic) debug() {
+ switch i {
+ case InstructionAdd:
+ fmt.Println("Add")
+ case InstructionSub:
+ fmt.Println("Sub")
+ case InstructionDiv:
+ fmt.Println("Div")
+ case InstructionMul:
+ fmt.Println("Mul")
+ case InstructionIgnore:
+ fmt.Println("Ignore")
+ case InstructionPushNull:
+ fmt.Println("Push Null")
+ case InstructionAssign:
+ fmt.Println("Assign")
+ case InstructionIndex:
+ fmt.Println("Index")
+ case InstructionDup:
+ fmt.Println("Dup")
+ default:
+ fmt.Println("Unknown Basic Instruction")
+ }
+}
+
+func (n InstructionPushNumber) debug() {
+ fmt.Printf("Push Number: %v\n", n)
+}
+
+func (i InstructionCall) debug() {
+ subroutines := map[Subroutine]string {
+ SubroutinePrintln: "println",
+ }
+ fmt.Printf("Calling %v with %v arguments\n", subroutines[i.subroutine], i.nargs)
+}
+
+func (s InstructionPushVariable) debug() {
+ fmt.Printf("Push variable: %v\n", s)
+}
+
+func (s InstructionPushString) debug() {
+ fmt.Printf("Push string: %q\n", s)
+}
+
+type Expression []Instruction
+
+type PatternSegmentIndex string
+type PatternSegmentFilter Expression
+type PatternSegmentBasic int
+const (
+ PatternSegmentAll PatternSegmentBasic = iota
+)
+
+type PatternSegment interface {
+ debug()
+ matches(*EvalState, TreePathSegment) bool
+}
+
+type Pattern struct {
+ segments []PatternSegment
+ isFirst bool
+}
+
+func (s PatternSegmentIndex) debug() {
+ fmt.Printf("Index: %q\n", s)
+}
+
+func (s PatternSegmentFilter) debug() {
+ fmt.Println("Filter: (")
+ for _, instruction := range s {
+ instruction.debug()
+ }
+ fmt.Println(")")
+}
+
+func (s PatternSegmentBasic) debug() {
+ switch s {
+ case PatternSegmentAll:
+ fmt.Println("All")
+ default:
+ panic("Invalid basic pattern segment")
+ }
+}
+
+type Block struct {
+ pattern Pattern
+ action Expression
+}
+
+type Program struct {
+ blocks []Block
+}
+
+func (p Program) debug() {
+ for _, block := range p.blocks {
+ fmt.Println("\nPattern:")
+ if block.pattern.isFirst {
+ fmt.Println("First")
+ } else {
+ fmt.Println("Last")
+ }
+ for _, s := range block.pattern.segments {
+ s.debug()
+ }
+ fmt.Println("\nAction:")
+ for _, a := range block.action {
+ a.debug()
+ }
+ }
+}
+
+type parser struct {
+ tokenStream chan Token
+ prevToken Token
+ wasRewound bool
+}
+
+func (p *parser) next() Token {
+ if p.wasRewound {
+ p.wasRewound = false
+ return p.prevToken
+ }
+ p.prevToken = <-p.tokenStream
+ if p.prevToken.typ == TokenErr {
+ fmt.Printf("Error: %q\n", p.prevToken.val)
+ panic("Lexing error")
+ }
+ return p.prevToken
+}
+
+func (p *parser) rewind() {
+ p.wasRewound = true
+}
+
+func (p *parser) accept(typ TokenType) (string, bool) {
+ token := p.next()
+ if typ == token.typ {
+ return token.val, true
+ }
+ p.rewind()
+ return "", false
+}
+
+func (p *parser) peek() Token {
+ token := p.next()
+ p.rewind()
+ return token
+}
+
+func (p *parser) parsePatternSegment() (segment PatternSegment, action bool, eof bool) {
+ token := p.next()
+ switch token.typ {
+ case TokenEOF:
+ p.rewind()
+ return nil, false, true
+ case TokenLBrace:
+ p.rewind()
+ return nil, true, false
+ case TokenIndexPattern:
+ return PatternSegmentIndex(token.val), false, false
+ case TokenLParen:
+ filter, noExpression := p.parseExpression(0)
+ if noExpression {
+ panic("Missing expression in filter")
+ }
+ _, hasCloseParen := p.accept(TokenRParen)
+ if !hasCloseParen {
+ panic("Missing close paren")
+ }
+ return PatternSegmentFilter(filter), false, false
+ case TokenAst:
+ return PatternSegmentAll, false, false
+ default:
+ panic("Expected pattern segment")
+ }
+}
+
+func (p *parser) parsePattern() (pattern Pattern, eof bool) {
+ _, pattern.isFirst = p.accept(TokenCircum)
+ segment, action, eof := p.parsePatternSegment()
+ if eof {
+ return pattern, true
+ } else if action {
+ return pattern, false
+ }
+ pattern.segments = append(pattern.segments, segment)
+ for {
+ _, hasAnotherSegment := p.accept(TokenDot)
+ if !hasAnotherSegment {
+ break
+ }
+ segment, action, eof := p.parsePatternSegment()
+ if eof || action {
+ panic("Expected pattern segment")
+ }
+ pattern.segments = append(pattern.segments, segment)
+ }
+ return pattern, false
+}
+
+func (p *parser) parseExpression(minPower int) (expr Expression, noExpression bool) {
+ token := p.next()
+ switch token.typ {
+ case TokenEOF:
+ p.rewind()
+ return nil, true
+ case TokenNumber:
+ num, err := strconv.ParseFloat(token.val, 64)
+ if err != nil {
+ panic("Invalid number")
+ }
+ expr = append(expr, InstructionPushNumber(num))
+ case TokenDoubleQuote:
+ s, isStringLiteral := p.accept(TokenStringLiteral)
+ if !isStringLiteral {
+ panic("Missing string literal")
+ }
+ _, stringLiteralClosed := p.accept(TokenDoubleQuote)
+ if !stringLiteralClosed {
+ panic("Missing closing quote for string literal")
+ }
+ expr = append(expr, InstructionPushString(s))
+ case TokenIdentifier:
+ _, hasLParen := p.accept(TokenLParen)
+ if hasLParen {
+ subroutines := map[string]Subroutine {
+ "println": SubroutinePrintln,
+ }
+ subroutine, isSubroutine := subroutines[token.val]
+ if !isSubroutine {
+ panic("Invalid subroutine")
+ }
+ nargs := 0
+ for {
+ e, noExpression := p.parseExpression(0)
+ if noExpression {
+ break
+ }
+ expr = append(expr, e...)
+ nargs += 1
+ _, hasComma := p.accept(TokenComma)
+ if !hasComma {
+ break
+ }
+ }
+ _, hasRParen := p.accept(TokenRParen)
+ if !hasRParen {
+ panic("Missing ) for subroutine call")
+ }
+ expr = append(expr, InstructionCall {subroutine, nargs})
+ } else {
+ expr = append(expr, InstructionPushVariable(token.val))
+ }
+ case TokenLParen:
+ e, noExpression := p.parseExpression(0)
+ if noExpression {
+ panic("Missing expression in ()")
+ }
+ _, hasCloseParen := p.accept(TokenRParen)
+ if !hasCloseParen {
+ panic("Missing ) in expression")
+ }
+ expr = append(expr, e...)
+ default:
+ p.rewind()
+ return nil, true
+ }
+
+ oploop: for {
+ token := p.next()
+ binops := map[TokenType] struct{
+ op InstructionBasic
+ left, right int
+ } {
+ TokenAdd: {InstructionAdd, 10, 11},
+ TokenSub: {InstructionSub, 10, 11},
+ TokenAst: {InstructionMul, 12, 13},
+ TokenDiv: {InstructionDiv, 12, 13},
+ TokenAssign: {InstructionAssign, 3, 2},
+ }
+ binop, isBinop := binops[token.typ]
+ assigns := map[TokenType]InstructionBasic {
+ TokenAddAssign: InstructionAdd,
+ TokenSubAssign: InstructionSub,
+ TokenAstAssign: InstructionMul,
+ TokenDivAssign: InstructionDiv,
+ }
+ assignInstruction, isAssign := assigns[token.typ]
+ switch {
+ case isBinop && binop.left >= minPower:
+ e, noExpression := p.parseExpression(binop.right)
+ if noExpression {
+ panic("Missing expression after operator")
+ }
+ expr = append(expr, e...)
+ expr = append(expr, binop.op)
+ case isAssign && 3 >= minPower:
+ expr = append(expr, InstructionDup)
+ e, noExpression := p.parseExpression(2)
+ if noExpression {
+ panic("Missing expression after operator")
+ }
+ expr = append(expr, e...)
+ expr = append(expr, assignInstruction, InstructionAssign)
+ case token.typ == TokenSemicolon && 0 >= minPower:
+ e, noExpression := p.parseExpression(1)
+ expr = append(expr, InstructionIgnore)
+ if noExpression {
+ expr = append(expr, InstructionPushNull)
+ } else {
+ expr = append(expr, e...)
+ }
+ case token.typ == TokenDot && 20 >= minPower:
+ index, hasIndex := p.accept(TokenIdentifier)
+ if !hasIndex {
+ panic("Expected identifier after .")
+ }
+ expr = append(expr, InstructionPushString(index), InstructionIndex)
+ default:
+ p.rewind()
+ break oploop
+ }
+ }
+
+ return expr, false
+}
+
+func Parse(tokenStream chan Token) Program {
+ p := parser {
+ tokenStream: tokenStream,
+ wasRewound: false,
+ }
+ var blocks []Block
+ for {
+ pattern, eof := p.parsePattern()
+ if eof {
+ break
+ }
+ _, hasAction := p.accept(TokenLBrace)
+ var action Expression
+ if hasAction {
+ var noAction bool
+ action, noAction = p.parseExpression(0)
+ if noAction {
+ action = nil
+ }
+ _, hasActionClose := p.accept(TokenRBrace)
+ if !hasActionClose {
+ panic("Error: Missing } at end of action")
+ }
+ }
+ blocks = append(blocks, Block {
+ pattern: pattern,
+ action: action,
+ })
+ }
+ return Program {
+ blocks: blocks,
+ }
+}