Simplify matcher
diff --git a/.gitignore b/.gitignore
index c5b8dd9..b03668f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+.*
+!.gitignore
+!.dockerignore
testdata
testdata/sandbox
examples/
diff --git a/mapper/mapper.go b/mapper/mapper.go
index 15e3984..9d32042 100644
--- a/mapper/mapper.go
+++ b/mapper/mapper.go
@@ -186,8 +186,8 @@
return nil, fmt.Errorf("failed to serialize AST to JSON: %w", err)
}
- // Parse the JSON string back into an interface{}
- var resultData interface{}
+ // Parse the JSON string back into
+ var resultData any
if err := json.Unmarshal(resultBytes, &resultData); err != nil {
return nil, fmt.Errorf("failed to parse result JSON: %w", err)
}
diff --git a/matcher/matcher.go b/matcher/matcher.go
index 3b496b2..1630b1a 100644
--- a/matcher/matcher.go
+++ b/matcher/matcher.go
@@ -93,109 +93,61 @@
return &ast.Token{Wrap: wrap}
}
- // If this node matches the pattern
- if m.Match(node) {
- // For TermGroups that contain a matching Term, preserve unmatched operands
- if tg, ok := node.(*ast.TermGroup); ok {
- // Check if any operand matches the pattern exactly
- hasExactMatch := false
- for _, op := range tg.Operands {
- if m.matchNode(op, m.pattern.Root) {
- hasExactMatch = true
- break
- }
- }
-
- // If we have an exact match, replace matching operands
- if hasExactMatch {
- hasMatch := false
- newOperands := make([]ast.Node, 0, len(tg.Operands))
- for _, op := range tg.Operands {
- if m.matchNode(op, m.pattern.Root) {
- if !hasMatch {
- newOperands = append(newOperands, m.cloneNode(m.replacement.Root))
- hasMatch = true
- } else {
- newOperands = append(newOperands, m.replaceNode(op))
- }
- } else {
- newOperands = append(newOperands, m.replaceNode(op))
- }
- }
- return &ast.TermGroup{
- Operands: newOperands,
- Relation: tg.Relation,
- }
- }
- // Otherwise, replace the entire TermGroup
- return m.cloneNode(m.replacement.Root)
- }
- // For other nodes, return the replacement
- return m.cloneNode(m.replacement.Root)
- }
-
- // Otherwise recursively process children
- switch n := node.(type) {
- case *ast.TermGroup:
- // Check if any operand matches the pattern exactly
- hasExactMatch := false
- for _, op := range n.Operands {
- if m.matchNode(op, m.pattern.Root) {
- hasExactMatch = true
- break
+ // Handle TermGroup nodes
+ if tg, ok := node.(*ast.TermGroup); ok {
+ // Check if any operand matches the pattern
+ hasMatch := false
+ newOperands := make([]ast.Node, 0, len(tg.Operands))
+ for _, op := range tg.Operands {
+ if !hasMatch && m.matchNode(op, m.pattern.Root) {
+ newOperands = append(newOperands, m.cloneNode(m.replacement.Root))
+ hasMatch = true
+ } else {
+ newOperands = append(newOperands, m.replaceNode(op))
}
}
-
- // If we have an exact match, replace matching operands
- if hasExactMatch {
- hasMatch := false
- newOperands := make([]ast.Node, 0, len(n.Operands))
- for _, op := range n.Operands {
- if m.matchNode(op, m.pattern.Root) {
- if !hasMatch {
- newOperands = append(newOperands, m.cloneNode(m.replacement.Root))
- hasMatch = true
- } else {
- newOperands = append(newOperands, m.replaceNode(op))
- }
- } else {
- newOperands = append(newOperands, m.replaceNode(op))
- }
- }
+ // If we found a match, return the modified TermGroup
+ if hasMatch {
return &ast.TermGroup{
Operands: newOperands,
- Relation: n.Relation,
+ Relation: tg.Relation,
}
}
- // Otherwise, recursively process operands
- newOperands := make([]ast.Node, len(n.Operands))
- for i, op := range n.Operands {
- newOperands[i] = m.replaceNode(op)
+ // If this TermGroup matches the pattern exactly, replace it
+ if m.matchNode(node, m.pattern.Root) {
+ return m.cloneNode(m.replacement.Root)
}
+ // Otherwise, return the modified TermGroup
return &ast.TermGroup{
Operands: newOperands,
- Relation: n.Relation,
+ Relation: tg.Relation,
}
+ }
- case *ast.CatchallNode:
+ // Handle CatchallNode nodes
+ if c, ok := node.(*ast.CatchallNode); ok {
newNode := &ast.CatchallNode{
- NodeType: n.NodeType,
- RawContent: n.RawContent,
+ NodeType: c.NodeType,
+ RawContent: c.RawContent,
}
- if n.Wrap != nil {
- newNode.Wrap = m.replaceNode(n.Wrap)
+ if c.Wrap != nil {
+ newNode.Wrap = m.replaceNode(c.Wrap)
}
- if len(n.Operands) > 0 {
- newNode.Operands = make([]ast.Node, len(n.Operands))
- for i, op := range n.Operands {
+ if len(c.Operands) > 0 {
+ newNode.Operands = make([]ast.Node, len(c.Operands))
+ for i, op := range c.Operands {
newNode.Operands[i] = m.replaceNode(op)
}
}
return newNode
-
- default:
- return node
}
+
+ // If this node matches the pattern exactly, replace it
+ if m.matchNode(node, m.pattern.Root) {
+ return m.cloneNode(m.replacement.Root)
+ }
+
+ return node
}
// simplifyNode removes unnecessary wrappers and empty nodes
@@ -276,120 +228,97 @@
return false
}
- // Handle pattern being a Token
- if pToken, ok := pattern.(*ast.Token); ok {
- if nToken, ok := node.(*ast.Token); ok {
- return m.matchNode(nToken.Wrap, pToken.Wrap)
- }
- return false
+ // Handle wrapped nodes (Token and CatchallNode)
+ if m.tryMatchWrapped(node, pattern) {
+ return true
}
- // Handle pattern being a Term
- if pTerm, ok := pattern.(*ast.Term); ok {
- // Direct term to term matching
- if t, ok := node.(*ast.Term); ok {
- return t.Foundry == pTerm.Foundry &&
- t.Key == pTerm.Key &&
- t.Layer == pTerm.Layer &&
- t.Match == pTerm.Match &&
- (pTerm.Value == "" || t.Value == pTerm.Value)
+ switch p := pattern.(type) {
+ case *ast.Token:
+ if n, ok := node.(*ast.Token); ok {
+ return m.matchNode(n.Wrap, p.Wrap)
}
- // If node is a Token, check its wrap
- if tkn, ok := node.(*ast.Token); ok {
- if tkn.Wrap == nil {
- return false
- }
- return m.matchNode(tkn.Wrap, pattern)
- }
- // If node is a TermGroup, check its operands
- if tg, ok := node.(*ast.TermGroup); ok {
- for _, op := range tg.Operands {
- if m.matchNode(op, pattern) {
- return true
- }
- }
- return false
- }
- // If node is a CatchallNode, check its wrap and operands
- if c, ok := node.(*ast.CatchallNode); ok {
- if c.Wrap != nil && m.matchNode(c.Wrap, pattern) {
- return true
- }
- for _, op := range c.Operands {
- if m.matchNode(op, pattern) {
- return true
- }
- }
- return false
- }
- return false
- }
- // Handle pattern being a TermGroup
- if pGroup, ok := pattern.(*ast.TermGroup); ok {
- // For OR relations, check if any operand matches the node
- if pGroup.Relation == ast.OrRelation {
- for _, pOp := range pGroup.Operands {
+ case *ast.Term:
+ return m.matchTerm(node, p)
+
+ case *ast.TermGroup:
+ if p.Relation == ast.OrRelation {
+ // For OR relations, check if any operand matches
+ for _, pOp := range p.Operands {
if m.matchNode(node, pOp) {
return true
}
}
- return false
+ } else if tg, ok := node.(*ast.TermGroup); ok && tg.Relation == p.Relation {
+ // For AND relations, all pattern operands must match in any order
+ return m.matchAndTermGroup(tg, p)
}
-
- // For AND relations, node must be a TermGroup with matching relation
- if tg, ok := node.(*ast.TermGroup); ok {
- if tg.Relation != pGroup.Relation {
- return false
- }
- // Check that all pattern operands match in any order
- if len(tg.Operands) < len(pGroup.Operands) {
- return false
- }
- matched := make([]bool, len(tg.Operands))
- for _, pOp := range pGroup.Operands {
- found := false
- for j, tOp := range tg.Operands {
- if !matched[j] && m.matchNode(tOp, pOp) {
- matched[j] = true
- found = true
- break
- }
- }
- if !found {
- return false
- }
- }
- return true
- }
-
- // If node is a Token, check its wrap
- if tkn, ok := node.(*ast.Token); ok {
- if tkn.Wrap == nil {
- return false
- }
- return m.matchNode(tkn.Wrap, pattern)
- }
-
- // If node is a CatchallNode, check its wrap and operands
- if c, ok := node.(*ast.CatchallNode); ok {
- if c.Wrap != nil && m.matchNode(c.Wrap, pattern) {
- return true
- }
- for _, op := range c.Operands {
- if m.matchNode(op, pattern) {
- return true
- }
- }
- return false
- }
-
- return false
}
return false
}
+// tryMatchWrapped attempts to match a node that might wrap other nodes
+func (m *Matcher) tryMatchWrapped(node, pattern ast.Node) bool {
+ switch n := node.(type) {
+ case *ast.Token:
+ if n.Wrap != nil {
+ return m.matchNode(n.Wrap, pattern)
+ }
+ case *ast.CatchallNode:
+ if n.Wrap != nil && m.matchNode(n.Wrap, pattern) {
+ return true
+ }
+ for _, op := range n.Operands {
+ if m.matchNode(op, pattern) {
+ return true
+ }
+ }
+ case *ast.TermGroup:
+ for _, op := range n.Operands {
+ if m.matchNode(op, pattern) {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// matchTerm checks if a node matches a term pattern
+func (m *Matcher) matchTerm(node ast.Node, pattern *ast.Term) bool {
+ if t, ok := node.(*ast.Term); ok {
+ return t.Foundry == pattern.Foundry &&
+ t.Key == pattern.Key &&
+ t.Layer == pattern.Layer &&
+ t.Match == pattern.Match &&
+ (pattern.Value == "" || t.Value == pattern.Value)
+ }
+ return m.tryMatchWrapped(node, pattern)
+}
+
+// matchAndTermGroup checks if a TermGroup matches an AND pattern
+func (m *Matcher) matchAndTermGroup(node *ast.TermGroup, pattern *ast.TermGroup) bool {
+ if len(node.Operands) < len(pattern.Operands) {
+ return false
+ }
+ matched := make([]bool, len(node.Operands))
+ for _, pOp := range pattern.Operands {
+ found := false
+ for j, tOp := range node.Operands {
+ if !matched[j] && m.matchNode(tOp, pOp) {
+ matched[j] = true
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
// cloneNode creates a deep copy of a node
func (m *Matcher) cloneNode(node ast.Node) ast.Node {
if node == nil {