1// SPDX-License-Identifier: MIT
2
3package template
4
5import (
6 "strings"
7
8 "bnema.dev/ermite-actions/expr"
9)
10
11// Kind is the type of a [Token], numbered as in the reference.
12type Kind int
13
14// The token kinds.
15const (
16 KindString Kind = iota
17 KindSequence
18 KindMapping
19 KindBasicExpression
20 KindInsertExpression
21 KindBoolean
22 KindNumber
23 KindNull
24)
25
26// String returns the reference's name for the kind, such as "StringToken".
27func (k Kind) String() string {
28 switch k {
29 case KindString:
30 return "StringToken"
31 case KindSequence:
32 return "SequenceToken"
33 case KindMapping:
34 return "MappingToken"
35 case KindBasicExpression:
36 return "BasicExpressionToken"
37 case KindInsertExpression:
38 return "InsertExpressionToken"
39 case KindBoolean:
40 return "BooleanToken"
41 case KindNumber:
42 return "NumberToken"
43 case KindNull:
44 return "NullToken"
45 }
46 return "unknown"
47}
48
49// Pos is a position in a source file. Line and Col are 1-based; the zero
50// value means the position is unknown (a token made by the program).
51type Pos struct{ Line, Col int }
52
53// Token is a node of a template: a scalar, a sequence or a mapping. The
54// concrete types are [*String], [*Number], [*Boolean], [*Null],
55// [*Sequence], [*Mapping], [*BasicExpression] and [*InsertExpression].
56type Token interface {
57 // Kind reports the type of the token.
58 Kind() Kind
59 // File is the name of the file the token was read from, or "".
60 File() string
61 // Pos is where the token starts in File.
62 Pos() Pos
63 // Definition is the schema definition the token was read against, or nil.
64 Definition() *DefinitionInfo
65 // String is the text the reference shows in messages: the value of a
66 // literal, "${{ expr }}" for an expression.
67 String() string
68 base() *tokenBase
69}
70
71type tokenBase struct {
72 file string
73 pos Pos
74 def *DefinitionInfo
75}
76
77func (m *tokenBase) File() string { return m.file }
78func (m *tokenBase) Pos() Pos { return m.pos }
79func (m *tokenBase) Definition() *DefinitionInfo { return m.def }
80func (m *tokenBase) base() *tokenBase { return m }
81
82// Origin is where a token comes from; the zero value is a token made by the
83// program.
84type Origin struct {
85 File string
86 Pos Pos
87}
88
89func mk(o Origin) tokenBase { return tokenBase{file: o.File, pos: o.Pos} }
90
91// String is a string literal.
92type String struct {
93 tokenBase
94 Value string
95}
96
97// Number is a number literal.
98type Number struct {
99 tokenBase
100 Value float64
101}
102
103// Boolean is a true or false literal.
104type Boolean struct {
105 tokenBase
106 Value bool
107}
108
109// Null is a null literal.
110type Null struct{ tokenBase }
111
112// Sequence is a list of tokens.
113type Sequence struct {
114 tokenBase
115 Items []Token
116}
117
118// Pair is a key and its value in a [Mapping]. The key is a scalar: a
119// [*String] or an expression.
120type Pair struct {
121 Key, Value Token
122}
123
124// Mapping is an ordered list of key-value pairs. Duplicate keys are rejected
125// when reading, comparing case-insensitively.
126type Mapping struct {
127 tokenBase
128 Pairs []Pair
129}
130
131// BasicExpression is a ${{ }} expression. Expression is the text between the
132// braces, trimmed, or a format() call when a string mixes text and
133// expressions.
134type BasicExpression struct {
135 tokenBase
136 Expression string
137 // Original holds the individual expressions when Expression is the
138 // format() call built from a string with several parts.
139 Original []*BasicExpression
140 // Source is the unprocessed text of the value the token was read from.
141 Source string
142
143 node expr.Node
144 parseErr error
145}
146
147// InsertExpression is the ${{ insert }} directive.
148type InsertExpression struct{ tokenBase }
149
150func (*String) Kind() Kind { return KindString }
151func (*Number) Kind() Kind { return KindNumber }
152func (*Boolean) Kind() Kind { return KindBoolean }
153func (*Null) Kind() Kind { return KindNull }
154func (*Sequence) Kind() Kind { return KindSequence }
155func (*Mapping) Kind() Kind { return KindMapping }
156func (*BasicExpression) Kind() Kind { return KindBasicExpression }
157func (*InsertExpression) Kind() Kind { return KindInsertExpression }
158
159func (t *String) String() string { return t.Value }
160func (t *Number) String() string { return JSNumberString(t.Value) }
161func (*Null) String() string { return "" }
162func (*Sequence) String() string { return "" }
163func (*Mapping) String() string { return "" }
164func (t *Boolean) String() string { return boolString(t.Value) }
165
166func (t *BasicExpression) String() string { return "${{ " + t.Expression + " }}" }
167func (*InsertExpression) String() string { return "${{ insert }}" }
168
169func boolString(b bool) string {
170 if b {
171 return "true"
172 }
173 return "false"
174}
175
176// NewString returns a string token.
177func NewString(o Origin, v string) *String { return &String{tokenBase: mk(o), Value: v} }
178
179// NewNumber returns a number token.
180func NewNumber(o Origin, v float64) *Number { return &Number{tokenBase: mk(o), Value: v} }
181
182// NewBoolean returns a boolean token.
183func NewBoolean(o Origin, v bool) *Boolean { return &Boolean{tokenBase: mk(o), Value: v} }
184
185// NewNull returns a null token.
186func NewNull(o Origin) *Null { return &Null{tokenBase: mk(o)} }
187
188// NewSequence returns a sequence token holding items.
189func NewSequence(o Origin, items ...Token) *Sequence {
190 return &Sequence{tokenBase: mk(o), Items: items}
191}
192
193// NewMapping returns an empty mapping token.
194func NewMapping(o Origin) *Mapping { return &Mapping{tokenBase: mk(o)} }
195
196// NewExpression returns an expression token. The text is parsed at once
197// against the contexts and functions allowed by def; with a nil def no
198// context and no function besides the built-in ones is allowed. A parse error
199// is returned by [Evaluate] and [BasicExpression.Err].
200func NewExpression(o Origin, expression string, def *DefinitionInfo) *BasicExpression {
201 t := &BasicExpression{tokenBase: mk(o), Expression: expression}
202 t.def = def
203 t.node, t.parseErr = parseWith(expression, def)
204 return t
205}
206
207// Err returns the error of parsing the expression, or nil.
208func (t *BasicExpression) Err() error { return t.parseErr }
209
210// Add appends a pair.
211func (m *Mapping) Add(key, value Token) { m.Pairs = append(m.Pairs, Pair{key, value}) }
212
213// Find returns the value of the first pair whose key's text is exactly key,
214// or nil.
215func (m *Mapping) Find(key string) Token {
216 for _, p := range m.Pairs {
217 if p.Key.String() == key {
218 return p.Value
219 }
220 }
221 return nil
222}
223
224// IsScalar reports whether t is neither a sequence nor a mapping.
225func IsScalar(t Token) bool {
226 switch t.Kind() {
227 case KindSequence, KindMapping:
228 return false
229 }
230 return true
231}
232
233// IsLiteral reports whether t is a string, number, boolean or null.
234func IsLiteral(t Token) bool {
235 switch t.Kind() {
236 case KindString, KindNumber, KindBoolean, KindNull:
237 return true
238 }
239 return false
240}
241
242// IsExpression reports whether t is a basic or insert expression.
243func IsExpression(t Token) bool {
244 k := t.Kind()
245 return k == KindBasicExpression || k == KindInsertExpression
246}
247
248// Walk calls fn for t and for every token below it, depth first, mapping keys
249// included. It stops descending into a token when fn returns false.
250func Walk(t Token, fn func(Token) bool) {
251 if t == nil || !fn(t) {
252 return
253 }
254 switch t := t.(type) {
255 case *Sequence:
256 for _, it := range t.Items {
257 Walk(it, fn)
258 }
259 case *Mapping:
260 for _, p := range t.Pairs {
261 Walk(p.Key, fn)
262 Walk(p.Value, fn)
263 }
264 }
265}
266
267// HasExpression reports whether t, or anything below it, is an expression.
268func HasExpression(t Token) bool {
269 found := false
270 Walk(t, func(x Token) bool {
271 if IsExpression(x) {
272 found = true
273 }
274 return !found
275 })
276 return found
277}
278
279func trimDisplay(s string) string {
280 s = strings.TrimLeft(s, " \t\r\n\v\f")
281 if i := strings.IndexAny(s, "\r\n"); i >= 0 {
282 s = s[:i]
283 }
284 return s
285}
286
287// Node returns the parsed expression, or nil when the text did not parse or
288// is empty. Callers that only inspect the tree (to look for function calls,
289// for instance) use it with the types of package expr.
290func (t *BasicExpression) Node() expr.Node { return t.node }
291
292// UsesContext reports whether the expression reads the named context
293// ("matrix", "secrets"...), anywhere in its syntax tree and whatever the
294// case. A function call is not a use of a context named like the function.
295func (t *BasicExpression) UsesContext(name string) bool { return nodeUsesContext(t.node, name) }
296
297func nodeUsesContext(n expr.Node, name string) bool {
298 switch n := n.(type) {
299 case *expr.ContextAccess:
300 return strings.EqualFold(n.Name.Lexeme, name)
301 case *expr.Unary:
302 return nodeUsesContext(n.Expr, name)
303 case *expr.Binary:
304 return nodeUsesContext(n.Left, name) || nodeUsesContext(n.Right, name)
305 case *expr.Grouping:
306 return nodeUsesContext(n.Group, name)
307 case *expr.IndexAccess:
308 return nodeUsesContext(n.Expr, name) || nodeUsesContext(n.Index, name)
309 case *expr.Logical:
310 for _, a := range n.Args {
311 if nodeUsesContext(a, name) {
312 return true
313 }
314 }
315 case *expr.FunctionCall:
316 for _, a := range n.Args {
317 if nodeUsesContext(a, name) {
318 return true
319 }
320 }
321 }
322 return false
323}