blob: 46b4cc48f7326a1d7c80ae136c3394768a8eb328 [file] [log] [blame]
Akronb7e1f352025-05-16 15:45:23 +02001package matcher
2
3import (
4 "github.com/KorAP/KoralPipe-TermMapper2/pkg/ast"
5)
6
7// Matcher handles pattern matching and replacement in the AST
8type Matcher struct {
9 pattern ast.Pattern
10 replacement ast.Replacement
11}
12
13// NewMatcher creates a new Matcher with the given pattern and replacement
14func NewMatcher(pattern ast.Pattern, replacement ast.Replacement) *Matcher {
15 return &Matcher{
16 pattern: pattern,
17 replacement: replacement,
18 }
19}
20
21// Match checks if the given node matches the pattern
22func (m *Matcher) Match(node ast.Node) bool {
23 return m.matchNode(node, m.pattern.Root)
24}
25
26// Replace replaces all occurrences of the pattern in the given node with the replacement
27func (m *Matcher) Replace(node ast.Node) ast.Node {
28 if m.Match(node) {
29 return m.cloneNode(m.replacement.Root)
30 }
31
32 switch n := node.(type) {
33 case *ast.Token:
34 n.Wrap = m.Replace(n.Wrap)
35 return n
36
37 case *ast.TermGroup:
38 newOperands := make([]ast.Node, len(n.Operands))
39 for i, op := range n.Operands {
40 newOperands[i] = m.Replace(op)
41 }
42 n.Operands = newOperands
43 return n
44
45 default:
46 return node
47 }
48}
49
50// matchNode recursively checks if two nodes match
51func (m *Matcher) matchNode(node, pattern ast.Node) bool {
52 if pattern == nil {
53 return true
54 }
55 if node == nil {
56 return false
57 }
58
59 switch p := pattern.(type) {
60 case *ast.Token:
61 if t, ok := node.(*ast.Token); ok {
62 return m.matchNode(t.Wrap, p.Wrap)
63 }
64
65 case *ast.TermGroup:
66 // If we're matching against a term, try to match it against any operand
67 if t, ok := node.(*ast.Term); ok && p.Relation == ast.OrRelation {
68 for _, op := range p.Operands {
69 if m.matchNode(t, op) {
70 return true
71 }
72 }
73 return false
74 }
75
76 // If we're matching against a term group
77 if t, ok := node.(*ast.TermGroup); ok {
78 if t.Relation != p.Relation {
79 return false
80 }
81
82 if p.Relation == ast.OrRelation {
83 // For OR relation, at least one operand must match
84 for _, pOp := range p.Operands {
85 for _, tOp := range t.Operands {
86 if m.matchNode(tOp, pOp) {
87 return true
88 }
89 }
90 }
91 return false
92 }
93
94 // For AND relation, all pattern operands must match
95 if len(t.Operands) < len(p.Operands) {
96 return false
97 }
98
99 // Try to match pattern operands against node operands in any order
100 matched := make([]bool, len(t.Operands))
101 for _, pOp := range p.Operands {
102 found := false
103 for j, tOp := range t.Operands {
104 if !matched[j] && m.matchNode(tOp, pOp) {
105 matched[j] = true
106 found = true
107 break
108 }
109 }
110 if !found {
111 return false
112 }
113 }
114 return true
115 }
116
117 case *ast.Term:
118 // If we're matching against a term group with OR relation,
119 // try to match against any of its operands
120 if t, ok := node.(*ast.TermGroup); ok && t.Relation == ast.OrRelation {
121 for _, op := range t.Operands {
122 if m.matchNode(op, p) {
123 return true
124 }
125 }
126 return false
127 }
128
129 // Direct term to term matching
130 if t, ok := node.(*ast.Term); ok {
131 return t.Foundry == p.Foundry &&
132 t.Key == p.Key &&
133 t.Layer == p.Layer &&
134 t.Match == p.Match &&
135 (p.Value == "" || t.Value == p.Value)
136 }
137 }
138
139 return false
140}
141
142// cloneNode creates a deep copy of a node
143func (m *Matcher) cloneNode(node ast.Node) ast.Node {
144 if node == nil {
145 return nil
146 }
147
148 switch n := node.(type) {
149 case *ast.Token:
150 return &ast.Token{
151 Wrap: m.cloneNode(n.Wrap),
152 }
153
154 case *ast.TermGroup:
155 operands := make([]ast.Node, len(n.Operands))
156 for i, op := range n.Operands {
157 operands[i] = m.cloneNode(op)
158 }
159 return &ast.TermGroup{
160 Operands: operands,
161 Relation: n.Relation,
162 }
163
164 case *ast.Term:
165 return &ast.Term{
166 Foundry: n.Foundry,
167 Key: n.Key,
168 Layer: n.Layer,
169 Match: n.Match,
170 Value: n.Value,
171 }
172
173 default:
174 return nil
175 }
176}