blob: dc7d7f35b065e20861600e2d3325ea59af08c3b6 [file] [log] [blame]
Akronb7e1f352025-05-16 15:45:23 +02001package ast
2
Akron753f49a2025-05-26 16:53:18 +02003// ast is the abstract syntax tree for the query term mapper.
Akronbf5149c2025-05-20 15:53:41 +02004
Akron32958422025-05-16 16:33:05 +02005import (
6 "encoding/json"
7)
8
Akronb7e1f352025-05-16 15:45:23 +02009// NodeType represents the type of a node in the AST
10type NodeType string
11
Akronb7e1f352025-05-16 15:45:23 +020012// RelationType represents the type of relation between nodes
13type RelationType string
14
Akronb7e1f352025-05-16 15:45:23 +020015// MatchType represents the type of match operation
16type MatchType string
17
18const (
Akron753f49a2025-05-26 16:53:18 +020019 TokenNode NodeType = "token"
20 TermGroupNode NodeType = "termGroup"
21 TermNode NodeType = "term"
Akron1a5fccd2025-05-27 09:54:09 +020022 RewriteNode NodeType = "rewrite"
Akron753f49a2025-05-26 16:53:18 +020023 AndRelation RelationType = "and"
24 OrRelation RelationType = "or"
25 MatchEqual MatchType = "eq"
26 MatchNotEqual MatchType = "ne"
Akronb7e1f352025-05-16 15:45:23 +020027)
28
29// Node represents a node in the AST
30type Node interface {
31 Type() NodeType
32}
33
Akron1a5fccd2025-05-27 09:54:09 +020034// Rewrite represents a koral:rewrite
35type Rewrite struct {
36 Editor string `json:"editor,omitempty"`
37 Operation string `json:"operation,omitempty"`
38 Scope string `json:"scope,omitempty"`
39 Src string `json:"src,omitempty"`
40 Comment string `json:"_comment,omitempty"`
Akron8f1970f2025-05-30 12:52:03 +020041 Original any `json:"original,omitempty"`
42}
43
44// UnmarshalJSON implements custom JSON unmarshaling for backward compatibility
45func (r *Rewrite) UnmarshalJSON(data []byte) error {
46 // Create a temporary struct to hold all possible fields
47 var temp struct {
48 Type string `json:"@type,omitempty"`
49 Editor string `json:"editor,omitempty"`
50 Source string `json:"source,omitempty"` // legacy field
51 Operation string `json:"operation,omitempty"` // legacy field
52 Scope string `json:"scope,omitempty"`
53 Src string `json:"src,omitempty"`
54 Origin string `json:"origin,omitempty"` // legacy field
55 Original any `json:"original,omitempty"`
56 Comment string `json:"_comment,omitempty"`
57 }
58
59 if err := json.Unmarshal(data, &temp); err != nil {
60 return err
61 }
62
63 // Apply precedence for editor field: editor >> source
64 if temp.Editor != "" {
65 r.Editor = temp.Editor
66 } else if temp.Source != "" {
67 r.Editor = temp.Source
68 }
69
70 // Apply precedence for original/src/origin: original >> src >> origin
71 if temp.Original != nil {
72 r.Original = temp.Original
73 } else if temp.Src != "" {
74 r.Src = temp.Src
75 } else if temp.Origin != "" {
76 r.Src = temp.Origin
77 }
78
79 // Copy other fields
80 r.Operation = temp.Operation
81 r.Scope = temp.Scope
82 r.Comment = temp.Comment
83
84 return nil
Akron1a5fccd2025-05-27 09:54:09 +020085}
86
87func (r *Rewrite) Type() NodeType {
88 return RewriteNode
89}
90
Akron8f1970f2025-05-30 12:52:03 +020091// MarshalJSON implements custom JSON marshaling to ensure clean output
92func (r *Rewrite) MarshalJSON() ([]byte, error) {
93 // Create a map with only the modern field names
94 result := make(map[string]any)
95
96 // Always include @type if this is a rewrite
97 result["@type"] = "koral:rewrite"
98
99 if r.Editor != "" {
100 result["editor"] = r.Editor
101 }
102 if r.Operation != "" {
103 result["operation"] = r.Operation
104 }
105 if r.Scope != "" {
106 result["scope"] = r.Scope
107 }
108 if r.Src != "" {
109 result["src"] = r.Src
110 }
111 if r.Comment != "" {
112 result["_comment"] = r.Comment
113 }
114 if r.Original != nil {
115 result["original"] = r.Original
116 }
117
118 return json.Marshal(result)
119}
120
Akron753f49a2025-05-26 16:53:18 +0200121// Token represents a koral:token
Akronb7e1f352025-05-16 15:45:23 +0200122type Token struct {
Akron1a5fccd2025-05-27 09:54:09 +0200123 Wrap Node `json:"wrap"`
124 Rewrites []Rewrite `json:"rewrites,omitempty"`
Akronb7e1f352025-05-16 15:45:23 +0200125}
126
127func (t *Token) Type() NodeType {
128 return TokenNode
129}
130
Akron753f49a2025-05-26 16:53:18 +0200131// TermGroup represents a koral:termGroup
Akronb7e1f352025-05-16 15:45:23 +0200132type TermGroup struct {
133 Operands []Node `json:"operands"`
134 Relation RelationType `json:"relation"`
Akron1a5fccd2025-05-27 09:54:09 +0200135 Rewrites []Rewrite `json:"rewrites,omitempty"`
Akronb7e1f352025-05-16 15:45:23 +0200136}
137
138func (tg *TermGroup) Type() NodeType {
139 return TermGroupNode
140}
141
Akron753f49a2025-05-26 16:53:18 +0200142// Term represents a koral:term
Akronb7e1f352025-05-16 15:45:23 +0200143type Term struct {
Akron1a5fccd2025-05-27 09:54:09 +0200144 Foundry string `json:"foundry"`
145 Key string `json:"key"`
146 Layer string `json:"layer"`
147 Match MatchType `json:"match"`
148 Value string `json:"value,omitempty"`
149 Rewrites []Rewrite `json:"rewrites,omitempty"`
Akronb7e1f352025-05-16 15:45:23 +0200150}
151
152func (t *Term) Type() NodeType {
153 return TermNode
154}
155
156// Pattern represents a pattern to match in the AST
157type Pattern struct {
158 Root Node
159}
160
161// Replacement represents a replacement pattern
162type Replacement struct {
163 Root Node
164}
Akron32958422025-05-16 16:33:05 +0200165
166// CatchallNode represents any node type not explicitly handled
167type CatchallNode struct {
168 NodeType string // The original @type value
169 RawContent json.RawMessage // The original JSON content
170 Wrap Node // Optional wrapped node
171 Operands []Node // Optional operands
172}
173
174func (c *CatchallNode) Type() NodeType {
175 return NodeType(c.NodeType)
176}