commit f010bf4fc1cbf1129e19c4bff052a6d50277ad17
parent 3e39549231249ace1bb886aa8b70345c5febca1d
Author: Charlie Stanton <charlie@shtanton.xyz>
Date: Sat, 20 Aug 2022 11:55:43 +0100
Add context variables to filter patterns and some boolean operators
Diffstat:
4 files changed, 98 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
@@ -22,3 +22,8 @@ treek 'people.* {println($0.first_name + " " + $0.last_name)}'
```
treek 'people.*.age {total += $0; count += 1} {println(total / count)}'
```
+
+#### Print the first names of all the Johnsons
+```
+treek 'people.($0.last_name=="Johnson").first_name'
+```
diff --git a/main/eval.go b/main/eval.go
@@ -40,6 +40,7 @@ type Value interface{
mul(Value) Value
div(Value) Value
index(Value) Value
+ equals(Value) ValueBool
}
func castToType(v Value, t ValueType) Value {
@@ -124,6 +125,13 @@ func (v ValueNull) div(w Value) Value {
func (v ValueNull) index(w Value) Value {
return ValueNull {}
}
+func (v ValueNull) equals(w Value) ValueBool {
+ typ := w.typ()
+ if typ == TypeNull {
+ return true
+ }
+ return castToType(v, typ).equals(w)
+}
func (v ValueBool) withAssignment(path []Value, value Value) Value {
if len(path) == 0 {
@@ -193,6 +201,10 @@ func (v ValueBool) div(w Value) Value {
func (v ValueBool) index(w Value) Value {
return v
}
+func (v ValueBool) equals(w Value) ValueBool {
+ rhs := w.castToBool()
+ return v == rhs
+}
func (v ValueNumber) withAssignment(path []Value, value Value) Value {
if len(path) == 0 {
@@ -250,6 +262,10 @@ func (v ValueNumber) div(w Value) Value {
func (v ValueNumber) index(w Value) Value {
return v
}
+func (v ValueNumber) equals(w Value) ValueBool {
+ rhs := w.castToNumber()
+ return v == rhs
+}
func (v ValueString) withAssignment(path []Value, value Value) Value {
if len(path) == 0 {
@@ -348,6 +364,10 @@ func (v ValueString) index(w Value) Value {
// TODO: use proper strings functions here
return ValueString(v[index])
}
+func (v ValueString) equals(w Value) ValueBool {
+ rhs := w.castToString()
+ return v == rhs
+}
func (v ValueArray) withAssignment(path []Value, value Value) Value {
if len(path) == 0 {
@@ -449,6 +469,18 @@ func (v ValueArray) index(w Value) Value {
index := int(math.Round(float64(w.castToNumber())))
return v[index]
}
+func (v ValueArray) equals(w Value) ValueBool {
+ rhs := w.castToArray()
+ if len(v) != len(rhs) {
+ return false
+ }
+ for i, el := range v {
+ if !el.equals(rhs[i]) {
+ return false
+ }
+ }
+ return true
+}
func (v ValueMap) withAssignment(path []Value, value Value) Value {
if len(path) == 0 {
@@ -559,6 +591,22 @@ func (v ValueMap) index(w Value) Value {
}
return res
}
+func (v ValueMap) equals(w Value) ValueBool {
+ rhs := w.castToMap()
+ for key, lvalue := range v {
+ rvalue, rhsHasValue := rhs[key]
+ if !rhsHasValue || !bool(lvalue.equals(rvalue)) {
+ return false
+ }
+ }
+ for key := range rhs {
+ _, lhsHasValue := v[key]
+ if !lhsHasValue {
+ return false
+ }
+ }
+ return true
+}
type VariableReference string
type IndexReference struct {
@@ -679,7 +727,7 @@ func (state *EvalState) popAddress() Address {
return state.pop().toAddress()
}
-func (index PatternSegmentIndex) matches(_ *EvalState, pathSegment TreePathSegment) bool {
+func (index PatternSegmentIndex) matches(_ *EvalState, path []TreePathSegment, pathSegment TreePathSegment) bool {
switch pathSegment.(type) {
case string:
return string(index) == pathSegment.(string)
@@ -690,12 +738,14 @@ func (index PatternSegmentIndex) matches(_ *EvalState, pathSegment TreePathSegme
}
}
-func (filter PatternSegmentFilter) matches(state *EvalState, pathSegment TreePathSegment) bool {
+func (filter PatternSegmentFilter) matches(state *EvalState, path []TreePathSegment, pathSegment TreePathSegment) bool {
+ state.variables["path"] = pathToValueArray(path).clone()
+ state.variables["$0"] = state.data.getPath(path).clone()
result := evalExpr(state, Expression(filter))
return bool(result.castToBool())
}
-func (segment PatternSegmentBasic) matches(state *EvalState, pathSegment TreePathSegment) bool {
+func (segment PatternSegmentBasic) matches(state *EvalState, path []TreePathSegment, pathSegment TreePathSegment) bool {
switch segment {
case PatternSegmentAll:
return true
@@ -710,7 +760,7 @@ func matchPattern(state *EvalState, pattern Pattern, walkItem TreeWalkItem) bool
}
for i, patternSegment := range pattern.segments {
pathSegment := walkItem.path[i]
- if !patternSegment.matches(state, pathSegment) {
+ if !patternSegment.matches(state, walkItem.path[0:i+1], pathSegment) {
return false
}
}
@@ -762,6 +812,13 @@ func (instruction InstructionBasic) eval(state *EvalState) {
val := state.pop()
state.push(val)
state.push(val.toValue(state).clone())
+ case InstructionEqual:
+ rhs := state.popValue()
+ lhs := state.popValue()
+ state.push(lhs.equals(rhs))
+ case InstructionNot:
+ val := state.popValue().castToBool()
+ state.push(!val)
default:
panic("Error: Tried to execute invalid basic instruction")
}
diff --git a/main/lex.go b/main/lex.go
@@ -128,6 +128,9 @@ const (
TokenSubAssign // -=
TokenAstAssign // *=
TokenDivAssign // /=
+ TokenEqual // ==
+ TokenNotEqual // !=
+ TokenNot // !
)
type Token struct {
@@ -271,6 +274,12 @@ func lexAction(l *lexer) stateFunc {
'/': {
'=': TokenDivAssign,
},
+ '=': {
+ '=': TokenEqual,
+ },
+ '!': {
+ '=': TokenNotEqual,
+ },
}
charTokens := map[rune]TokenType{
'+': TokenAdd,
@@ -281,6 +290,7 @@ func lexAction(l *lexer) stateFunc {
',': TokenComma,
';': TokenSemicolon,
'=': TokenAssign,
+ '!': TokenNot,
}
r := l.next()
charToken, isCharToken := charTokens[r]
diff --git a/main/parser.go b/main/parser.go
@@ -16,6 +16,8 @@ const (
InstructionAssign
InstructionIndex
InstructionDup
+ InstructionEqual
+ InstructionNot
)
type InstructionPushNumber float64
@@ -56,6 +58,10 @@ func (i InstructionBasic) debug() {
fmt.Println("Index")
case InstructionDup:
fmt.Println("Dup")
+ case InstructionEqual:
+ fmt.Println("Equal")
+ case InstructionNot:
+ fmt.Println("Not")
default:
fmt.Println("Unknown Basic Instruction")
}
@@ -91,7 +97,7 @@ const (
type PatternSegment interface {
debug()
- matches(*EvalState, TreePathSegment) bool
+ matches(*EvalState, []TreePathSegment, TreePathSegment) bool
}
type Pattern struct {
@@ -242,6 +248,13 @@ func (p *parser) parseExpression(minPower int) (expr Expression, noExpression bo
case TokenEOF:
p.rewind()
return nil, true
+ case TokenNot:
+ e, noExpression := p.parseExpression(14)
+ if noExpression {
+ panic("Missing expression after !")
+ }
+ expr = append(expr, e...)
+ expr = append(expr, InstructionNot)
case TokenNumber:
num, err := strconv.ParseFloat(token.val, 64)
if err != nil {
@@ -315,6 +328,7 @@ func (p *parser) parseExpression(minPower int) (expr Expression, noExpression bo
TokenAst: {InstructionMul, 12, 13},
TokenDiv: {InstructionDiv, 12, 13},
TokenAssign: {InstructionAssign, 3, 2},
+ TokenEqual: {InstructionEqual, 8, 9},
}
binop, isBinop := binops[token.typ]
assigns := map[TokenType]InstructionBasic {
@@ -354,6 +368,13 @@ func (p *parser) parseExpression(minPower int) (expr Expression, noExpression bo
panic("Expected identifier after .")
}
expr = append(expr, InstructionPushString(index), InstructionIndex)
+ case token.typ == TokenNotEqual && 8 >= minPower:
+ e, noExpression := p.parseExpression(9)
+ if noExpression {
+ panic("Missing expression after operator")
+ }
+ expr = append(expr, e...)
+ expr = append(expr, InstructionEqual, InstructionNot)
default:
p.rewind()
break oploop