blob: 8780550a3cc5b951d029bd8ad602823295bccb2c [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
Akron32958422025-05-16 16:33:05 +020045 case *ast.CatchallNode:
46 newNode := &ast.CatchallNode{
47 NodeType: n.NodeType,
48 RawContent: n.RawContent,
49 }
50 if n.Wrap != nil {
51 newNode.Wrap = m.Replace(n.Wrap)
52 }
53 if len(n.Operands) > 0 {
54 newNode.Operands = make([]ast.Node, len(n.Operands))
55 for i, op := range n.Operands {
56 newNode.Operands[i] = m.Replace(op)
57 }
58 }
59 return newNode
60
Akronb7e1f352025-05-16 15:45:23 +020061 default:
62 return node
63 }
64}
65
66// matchNode recursively checks if two nodes match
67func (m *Matcher) matchNode(node, pattern ast.Node) bool {
68 if pattern == nil {
69 return true
70 }
71 if node == nil {
72 return false
73 }
74
75 switch p := pattern.(type) {
76 case *ast.Token:
77 if t, ok := node.(*ast.Token); ok {
78 return m.matchNode(t.Wrap, p.Wrap)
79 }
Akron32958422025-05-16 16:33:05 +020080 return false
Akronb7e1f352025-05-16 15:45:23 +020081
82 case *ast.TermGroup:
83 // If we're matching against a term, try to match it against any operand
84 if t, ok := node.(*ast.Term); ok && p.Relation == ast.OrRelation {
85 for _, op := range p.Operands {
86 if m.matchNode(t, op) {
87 return true
88 }
89 }
90 return false
91 }
92
93 // If we're matching against a term group
94 if t, ok := node.(*ast.TermGroup); ok {
95 if t.Relation != p.Relation {
96 return false
97 }
98
99 if p.Relation == ast.OrRelation {
100 // For OR relation, at least one operand must match
101 for _, pOp := range p.Operands {
102 for _, tOp := range t.Operands {
103 if m.matchNode(tOp, pOp) {
104 return true
105 }
106 }
107 }
108 return false
109 }
110
111 // For AND relation, all pattern operands must match
112 if len(t.Operands) < len(p.Operands) {
113 return false
114 }
115
116 // Try to match pattern operands against node operands in any order
117 matched := make([]bool, len(t.Operands))
118 for _, pOp := range p.Operands {
119 found := false
120 for j, tOp := range t.Operands {
121 if !matched[j] && m.matchNode(tOp, pOp) {
122 matched[j] = true
123 found = true
124 break
125 }
126 }
127 if !found {
128 return false
129 }
130 }
131 return true
132 }
Akron32958422025-05-16 16:33:05 +0200133 return false
134
135 case *ast.CatchallNode:
136 // For catchall nodes, we need to check both wrap and operands
137 if t, ok := node.(*ast.CatchallNode); ok {
138 // If pattern has wrap, match it
139 if p.Wrap != nil && !m.matchNode(t.Wrap, p.Wrap) {
140 return false
141 }
142
143 // If pattern has operands, match them
144 if len(p.Operands) > 0 {
145 if len(t.Operands) < len(p.Operands) {
146 return false
147 }
148
149 // Try to match pattern operands against node operands in any order
150 matched := make([]bool, len(t.Operands))
151 for _, pOp := range p.Operands {
152 found := false
153 for j, tOp := range t.Operands {
154 if !matched[j] && m.matchNode(tOp, pOp) {
155 matched[j] = true
156 found = true
157 break
158 }
159 }
160 if !found {
161 return false
162 }
163 }
164 return true
165 }
166
167 // If no wrap or operands to match, it's a match
168 return true
169 }
170 return false
Akronb7e1f352025-05-16 15:45:23 +0200171
172 case *ast.Term:
173 // If we're matching against a term group with OR relation,
174 // try to match against any of its operands
175 if t, ok := node.(*ast.TermGroup); ok && t.Relation == ast.OrRelation {
176 for _, op := range t.Operands {
177 if m.matchNode(op, p) {
178 return true
179 }
180 }
181 return false
182 }
183
184 // Direct term to term matching
185 if t, ok := node.(*ast.Term); ok {
186 return t.Foundry == p.Foundry &&
187 t.Key == p.Key &&
188 t.Layer == p.Layer &&
189 t.Match == p.Match &&
190 (p.Value == "" || t.Value == p.Value)
191 }
Akron32958422025-05-16 16:33:05 +0200192 return false
Akronb7e1f352025-05-16 15:45:23 +0200193 }
194
195 return false
196}
197
198// cloneNode creates a deep copy of a node
199func (m *Matcher) cloneNode(node ast.Node) ast.Node {
200 if node == nil {
201 return nil
202 }
203
204 switch n := node.(type) {
205 case *ast.Token:
206 return &ast.Token{
207 Wrap: m.cloneNode(n.Wrap),
208 }
209
210 case *ast.TermGroup:
211 operands := make([]ast.Node, len(n.Operands))
212 for i, op := range n.Operands {
213 operands[i] = m.cloneNode(op)
214 }
215 return &ast.TermGroup{
216 Operands: operands,
217 Relation: n.Relation,
218 }
219
220 case *ast.Term:
221 return &ast.Term{
222 Foundry: n.Foundry,
223 Key: n.Key,
224 Layer: n.Layer,
225 Match: n.Match,
226 Value: n.Value,
227 }
228
Akron32958422025-05-16 16:33:05 +0200229 case *ast.CatchallNode:
230 newNode := &ast.CatchallNode{
231 NodeType: n.NodeType,
232 RawContent: n.RawContent,
233 }
234 if n.Wrap != nil {
235 newNode.Wrap = m.cloneNode(n.Wrap)
236 }
237 if len(n.Operands) > 0 {
238 newNode.Operands = make([]ast.Node, len(n.Operands))
239 for i, op := range n.Operands {
240 newNode.Operands[i] = m.cloneNode(op)
241 }
242 }
243 return newNode
244
Akronb7e1f352025-05-16 15:45:23 +0200245 default:
246 return nil
247 }
248}