fusee-framework 2.7.0 → 2.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -26
- package/bin/create-fusee-linux +0 -0
- package/bin/create-fusee-mac-arm64 +0 -0
- package/bin/create-fusee-mac-intel +0 -0
- package/bin/create-fusee-win.exe +0 -0
- package/framework/auto-import-preset.js +20 -12
- package/framework/auto-imports.d.ts +68 -39
- package/framework/core/compiler.js +310 -310
- package/framework/core/component-core.js +445 -0
- package/framework/core/component.js +11 -440
- package/framework/core/composable.js +29 -29
- package/framework/core/h.js +119 -44
- package/framework/core/jsx-helpers.js +11 -2
- package/framework/core/jsx-runtime.js +73 -73
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm +0 -0
- package/framework/core/store.js +333 -333
- package/framework/globals.js +42 -0
- package/framework/index.js +22 -55
- package/framework/router/file-router.js +184 -184
- package/framework/server/actions.js +183 -183
- package/framework/server/generate-manifest.js +3 -3
- package/framework/types/component.d.ts +97 -86
- package/framework/types/index.d.ts +118 -117
- package/package.json +83 -80
- package/framework/core/rust-compiler/Cargo.toml +0 -31
- package/framework/core/rust-compiler/README.md +0 -69
- package/framework/core/rust-compiler/pkg/README.md +0 -69
- package/framework/core/rust-compiler/pkg/fusee_compiler.d.ts +0 -6
- package/framework/core/rust-compiler/pkg/fusee_compiler.js +0 -9
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.js +0 -137
- package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm.d.ts +0 -11
- package/framework/core/rust-compiler/pkg/package.json +0 -18
- package/framework/core/rust-compiler/src/ast.rs +0 -274
- package/framework/core/rust-compiler/src/errors.rs +0 -204
- package/framework/core/rust-compiler/src/generator.rs +0 -583
- package/framework/core/rust-compiler/src/lexer.rs +0 -414
- package/framework/core/rust-compiler/src/lib.rs +0 -87
- package/framework/core/rust-compiler/src/main_compiler.rs +0 -165
- package/framework/core/rust-compiler/src/parser.rs +0 -516
- package/framework/core/rust-compiler/src/ssr_generator.rs +0 -688
- package/framework/core/rust-compiler/src/transformer.rs +0 -562
- package/framework/core/rust-compiler/src/utils.rs +0 -10
- package/framework/engine-go/actions.go +0 -112
- package/framework/engine-go/cmd/server/main.go +0 -233
- package/framework/engine-go/comet/comet.go +0 -41
- package/framework/engine-go/comet/fragment.go +0 -51
- package/framework/engine-go/dispatcher.go +0 -120
- package/framework/engine-go/elements.go +0 -248
- package/framework/engine-go/evalexpr/evalexpr.go +0 -733
- package/framework/engine-go/go.mod +0 -3
- package/framework/engine-go/hydration.go +0 -43
- package/framework/engine-go/load-helpers.go +0 -29
- package/framework/engine-go/manifest.go +0 -41
- package/framework/engine-go/reactive.go +0 -123
- package/framework/engine-go/render.go +0 -635
- package/framework/engine-go/reqcontext.go +0 -48
- package/framework/engine-go/router.go +0 -85
- package/framework/engine-go/rustast/rustast.go +0 -317
- package/framework/engine-go/ssr.go +0 -103
- package/framework/engine-go/static-assets.go +0 -139
- package/framework/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
|
@@ -1,733 +0,0 @@
|
|
|
1
|
-
// Package evalexpr provides a small, safe expression interpreter for Fusée
|
|
2
|
-
// template expressions. It handles:
|
|
3
|
-
// - Identifiers and member access (a.b.c)
|
|
4
|
-
// - Index access (a[0], a["key"])
|
|
5
|
-
// - Arithmetic: +, -, *, /, %
|
|
6
|
-
// - Comparison: ==, !=, <, <=, >, >=
|
|
7
|
-
// - Logical: &&, ||, !
|
|
8
|
-
// - String concatenation via +
|
|
9
|
-
// - Ternary: cond ? a : b
|
|
10
|
-
// - Null-coalescing: a ?? b
|
|
11
|
-
//
|
|
12
|
-
// No code-execution path exists here — this is intentionally safer than
|
|
13
|
-
// JS's new Function() approach, since Go has no eval capability anyway.
|
|
14
|
-
package evalexpr
|
|
15
|
-
|
|
16
|
-
import (
|
|
17
|
-
"fmt"
|
|
18
|
-
"math"
|
|
19
|
-
"strconv"
|
|
20
|
-
"strings"
|
|
21
|
-
"unicode"
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
// Scope is the data context passed to Eval. Keys are variable names.
|
|
25
|
-
type Scope map[string]any
|
|
26
|
-
|
|
27
|
-
// Eval evaluates a template expression string against the given scope.
|
|
28
|
-
// It returns the computed value (string, float64, bool, []any, map[string]any, nil).
|
|
29
|
-
func Eval(expr string, scope Scope) (any, error) {
|
|
30
|
-
expr = strings.TrimSpace(expr)
|
|
31
|
-
if expr == "" {
|
|
32
|
-
return nil, nil
|
|
33
|
-
}
|
|
34
|
-
p := &parser{input: expr, scope: scope}
|
|
35
|
-
v, err := p.parseTernary()
|
|
36
|
-
if err != nil {
|
|
37
|
-
return nil, fmt.Errorf("evalexpr: %w (in %q)", err, expr)
|
|
38
|
-
}
|
|
39
|
-
return v, nil
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// EvalBool evaluates an expression and coerces the result to bool.
|
|
43
|
-
func EvalBool(expr string, scope Scope) (bool, error) {
|
|
44
|
-
v, err := Eval(expr, scope)
|
|
45
|
-
if err != nil {
|
|
46
|
-
return false, err
|
|
47
|
-
}
|
|
48
|
-
return truthy(v), nil
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── parser ──────────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
type parser struct {
|
|
54
|
-
input string
|
|
55
|
-
pos int
|
|
56
|
-
scope Scope
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
func (p *parser) peek() byte {
|
|
60
|
-
p.skipWS()
|
|
61
|
-
if p.pos >= len(p.input) {
|
|
62
|
-
return 0
|
|
63
|
-
}
|
|
64
|
-
return p.input[p.pos]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
func (p *parser) skipWS() {
|
|
68
|
-
for p.pos < len(p.input) && isWS(p.input[p.pos]) {
|
|
69
|
-
p.pos++
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func (p *parser) consume(n int) {
|
|
74
|
-
p.pos += n
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
func (p *parser) peekStr(s string) bool {
|
|
78
|
-
p.skipWS()
|
|
79
|
-
return strings.HasPrefix(p.input[p.pos:], s)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
func (p *parser) consumeStr(s string) bool {
|
|
83
|
-
p.skipWS()
|
|
84
|
-
if strings.HasPrefix(p.input[p.pos:], s) {
|
|
85
|
-
p.pos += len(s)
|
|
86
|
-
return true
|
|
87
|
-
}
|
|
88
|
-
return false
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ─── grammar (recursive-descent) ─────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
// ternary: nullCoalesce ( '?' ternary ':' ternary )?
|
|
94
|
-
func (p *parser) parseTernary() (any, error) {
|
|
95
|
-
cond, err := p.parseNullCoalesce()
|
|
96
|
-
if err != nil {
|
|
97
|
-
return nil, err
|
|
98
|
-
}
|
|
99
|
-
if !p.consumeStr("?") {
|
|
100
|
-
return cond, nil
|
|
101
|
-
}
|
|
102
|
-
yes, err := p.parseTernary()
|
|
103
|
-
if err != nil {
|
|
104
|
-
return nil, err
|
|
105
|
-
}
|
|
106
|
-
if !p.consumeStr(":") {
|
|
107
|
-
return nil, fmt.Errorf("expected ':' in ternary")
|
|
108
|
-
}
|
|
109
|
-
no, err := p.parseTernary()
|
|
110
|
-
if err != nil {
|
|
111
|
-
return nil, err
|
|
112
|
-
}
|
|
113
|
-
if truthy(cond) {
|
|
114
|
-
return yes, nil
|
|
115
|
-
}
|
|
116
|
-
return no, nil
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// nullCoalesce: or ( '??' or )*
|
|
120
|
-
func (p *parser) parseNullCoalesce() (any, error) {
|
|
121
|
-
left, err := p.parseOr()
|
|
122
|
-
if err != nil {
|
|
123
|
-
return nil, err
|
|
124
|
-
}
|
|
125
|
-
for p.consumeStr("??") {
|
|
126
|
-
right, err := p.parseOr()
|
|
127
|
-
if err != nil {
|
|
128
|
-
return nil, err
|
|
129
|
-
}
|
|
130
|
-
if left == nil {
|
|
131
|
-
left = right
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return left, nil
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// or: and ( '||' and )*
|
|
138
|
-
func (p *parser) parseOr() (any, error) {
|
|
139
|
-
left, err := p.parseAnd()
|
|
140
|
-
if err != nil {
|
|
141
|
-
return nil, err
|
|
142
|
-
}
|
|
143
|
-
for p.consumeStr("||") {
|
|
144
|
-
right, err := p.parseAnd()
|
|
145
|
-
if err != nil {
|
|
146
|
-
return nil, err
|
|
147
|
-
}
|
|
148
|
-
if !truthy(left) {
|
|
149
|
-
left = right
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return left, nil
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// and: equality ( '&&' equality )*
|
|
157
|
-
func (p *parser) parseAnd() (any, error) {
|
|
158
|
-
left, err := p.parseEquality()
|
|
159
|
-
if err != nil {
|
|
160
|
-
return nil, err
|
|
161
|
-
}
|
|
162
|
-
for p.consumeStr("&&") {
|
|
163
|
-
right, err := p.parseEquality()
|
|
164
|
-
if err != nil {
|
|
165
|
-
return nil, err
|
|
166
|
-
}
|
|
167
|
-
if !truthy(left) {
|
|
168
|
-
// short-circuit
|
|
169
|
-
} else {
|
|
170
|
-
left = right
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return left, nil
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// equality: comparison ( ('=='|'!='|'==='|'!==') comparison )*
|
|
177
|
-
func (p *parser) parseEquality() (any, error) {
|
|
178
|
-
left, err := p.parseComparison()
|
|
179
|
-
if err != nil {
|
|
180
|
-
return nil, err
|
|
181
|
-
}
|
|
182
|
-
for {
|
|
183
|
-
if p.consumeStr("===") || p.consumeStr("==") {
|
|
184
|
-
right, err := p.parseComparison()
|
|
185
|
-
if err != nil {
|
|
186
|
-
return nil, err
|
|
187
|
-
}
|
|
188
|
-
left = looseEq(left, right)
|
|
189
|
-
} else if p.consumeStr("!==") || p.consumeStr("!=") {
|
|
190
|
-
right, err := p.parseComparison()
|
|
191
|
-
if err != nil {
|
|
192
|
-
return nil, err
|
|
193
|
-
}
|
|
194
|
-
left = !looseEq(left, right)
|
|
195
|
-
} else {
|
|
196
|
-
break
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return left, nil
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// comparison: addition ( ('<='|'>='|'<'|'>') addition )*
|
|
203
|
-
func (p *parser) parseComparison() (any, error) {
|
|
204
|
-
left, err := p.parseAddition()
|
|
205
|
-
if err != nil {
|
|
206
|
-
return nil, err
|
|
207
|
-
}
|
|
208
|
-
for {
|
|
209
|
-
if p.consumeStr("<=") {
|
|
210
|
-
right, err := p.parseAddition()
|
|
211
|
-
if err != nil {
|
|
212
|
-
return nil, err
|
|
213
|
-
}
|
|
214
|
-
left = compareOp(left, right) <= 0
|
|
215
|
-
} else if p.consumeStr(">=") {
|
|
216
|
-
right, err := p.parseAddition()
|
|
217
|
-
if err != nil {
|
|
218
|
-
return nil, err
|
|
219
|
-
}
|
|
220
|
-
left = compareOp(left, right) >= 0
|
|
221
|
-
} else if p.peekByte('<') {
|
|
222
|
-
p.consume(1)
|
|
223
|
-
right, err := p.parseAddition()
|
|
224
|
-
if err != nil {
|
|
225
|
-
return nil, err
|
|
226
|
-
}
|
|
227
|
-
left = compareOp(left, right) < 0
|
|
228
|
-
} else if p.peekByte('>') {
|
|
229
|
-
p.consume(1)
|
|
230
|
-
right, err := p.parseAddition()
|
|
231
|
-
if err != nil {
|
|
232
|
-
return nil, err
|
|
233
|
-
}
|
|
234
|
-
left = compareOp(left, right) > 0
|
|
235
|
-
} else {
|
|
236
|
-
break
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return left, nil
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
func (p *parser) peekByte(b byte) bool {
|
|
243
|
-
p.skipWS()
|
|
244
|
-
return p.pos < len(p.input) && p.input[p.pos] == b
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// addition: multiplication ( ('+'|'-') multiplication )*
|
|
248
|
-
func (p *parser) parseAddition() (any, error) {
|
|
249
|
-
left, err := p.parseMultiplication()
|
|
250
|
-
if err != nil {
|
|
251
|
-
return nil, err
|
|
252
|
-
}
|
|
253
|
-
for {
|
|
254
|
-
if p.peekByte('+') {
|
|
255
|
-
p.consume(1)
|
|
256
|
-
right, err := p.parseMultiplication()
|
|
257
|
-
if err != nil {
|
|
258
|
-
return nil, err
|
|
259
|
-
}
|
|
260
|
-
left = addValues(left, right)
|
|
261
|
-
} else if p.peekByte('-') {
|
|
262
|
-
p.consume(1)
|
|
263
|
-
right, err := p.parseMultiplication()
|
|
264
|
-
if err != nil {
|
|
265
|
-
return nil, err
|
|
266
|
-
}
|
|
267
|
-
left = numOp(left, right, func(a, b float64) float64 { return a - b })
|
|
268
|
-
} else {
|
|
269
|
-
break
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return left, nil
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// multiplication: unary ( ('*'|'/'|'%') unary )*
|
|
276
|
-
func (p *parser) parseMultiplication() (any, error) {
|
|
277
|
-
left, err := p.parseUnary()
|
|
278
|
-
if err != nil {
|
|
279
|
-
return nil, err
|
|
280
|
-
}
|
|
281
|
-
for {
|
|
282
|
-
if p.peekByte('*') {
|
|
283
|
-
p.consume(1)
|
|
284
|
-
right, err := p.parseUnary()
|
|
285
|
-
if err != nil {
|
|
286
|
-
return nil, err
|
|
287
|
-
}
|
|
288
|
-
left = numOp(left, right, func(a, b float64) float64 { return a * b })
|
|
289
|
-
} else if p.peekByte('/') {
|
|
290
|
-
p.consume(1)
|
|
291
|
-
right, err := p.parseUnary()
|
|
292
|
-
if err != nil {
|
|
293
|
-
return nil, err
|
|
294
|
-
}
|
|
295
|
-
left = numOp(left, right, func(a, b float64) float64 {
|
|
296
|
-
if b == 0 {
|
|
297
|
-
return math.NaN()
|
|
298
|
-
}
|
|
299
|
-
return a / b
|
|
300
|
-
})
|
|
301
|
-
} else if p.peekByte('%') {
|
|
302
|
-
p.consume(1)
|
|
303
|
-
right, err := p.parseUnary()
|
|
304
|
-
if err != nil {
|
|
305
|
-
return nil, err
|
|
306
|
-
}
|
|
307
|
-
left = numOp(left, right, func(a, b float64) float64 {
|
|
308
|
-
if b == 0 {
|
|
309
|
-
return math.NaN()
|
|
310
|
-
}
|
|
311
|
-
return math.Mod(a, b)
|
|
312
|
-
})
|
|
313
|
-
} else {
|
|
314
|
-
break
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return left, nil
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// unary: ('!'|'-')? postfix
|
|
321
|
-
func (p *parser) parseUnary() (any, error) {
|
|
322
|
-
p.skipWS()
|
|
323
|
-
if p.pos < len(p.input) && p.input[p.pos] == '!' {
|
|
324
|
-
p.pos++
|
|
325
|
-
v, err := p.parseUnary()
|
|
326
|
-
if err != nil {
|
|
327
|
-
return nil, err
|
|
328
|
-
}
|
|
329
|
-
return !truthy(v), nil
|
|
330
|
-
}
|
|
331
|
-
if p.pos < len(p.input) && p.input[p.pos] == '-' {
|
|
332
|
-
p.pos++
|
|
333
|
-
v, err := p.parseUnary()
|
|
334
|
-
if err != nil {
|
|
335
|
-
return nil, err
|
|
336
|
-
}
|
|
337
|
-
if n, ok := toNumber(v); ok {
|
|
338
|
-
return -n, nil
|
|
339
|
-
}
|
|
340
|
-
return math.NaN(), nil
|
|
341
|
-
}
|
|
342
|
-
return p.parsePostfix()
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// postfix: primary ( '.' ident | '[' expr ']' | '(' args ')' )*
|
|
346
|
-
func (p *parser) parsePostfix() (any, error) {
|
|
347
|
-
val, err := p.parsePrimary()
|
|
348
|
-
if err != nil {
|
|
349
|
-
return nil, err
|
|
350
|
-
}
|
|
351
|
-
for {
|
|
352
|
-
p.skipWS()
|
|
353
|
-
if p.pos >= len(p.input) {
|
|
354
|
-
break
|
|
355
|
-
}
|
|
356
|
-
ch := p.input[p.pos]
|
|
357
|
-
if ch == '.' {
|
|
358
|
-
p.pos++
|
|
359
|
-
name, err := p.parseIdent()
|
|
360
|
-
if err != nil {
|
|
361
|
-
return nil, err
|
|
362
|
-
}
|
|
363
|
-
val = memberAccess(val, name)
|
|
364
|
-
} else if ch == '[' {
|
|
365
|
-
p.pos++
|
|
366
|
-
key, err := p.parseTernary()
|
|
367
|
-
if err != nil {
|
|
368
|
-
return nil, err
|
|
369
|
-
}
|
|
370
|
-
p.skipWS()
|
|
371
|
-
if p.pos < len(p.input) && p.input[p.pos] == ']' {
|
|
372
|
-
p.pos++
|
|
373
|
-
}
|
|
374
|
-
val = indexAccess(val, key)
|
|
375
|
-
} else {
|
|
376
|
-
break
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return val, nil
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// primary: literal | '(' expr ')' | ident
|
|
383
|
-
func (p *parser) parsePrimary() (any, error) {
|
|
384
|
-
p.skipWS()
|
|
385
|
-
if p.pos >= len(p.input) {
|
|
386
|
-
return nil, fmt.Errorf("unexpected end of expression")
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
ch := p.input[p.pos]
|
|
390
|
-
|
|
391
|
-
// Grouped
|
|
392
|
-
if ch == '(' {
|
|
393
|
-
p.pos++
|
|
394
|
-
v, err := p.parseTernary()
|
|
395
|
-
if err != nil {
|
|
396
|
-
return nil, err
|
|
397
|
-
}
|
|
398
|
-
p.skipWS()
|
|
399
|
-
if p.pos < len(p.input) && p.input[p.pos] == ')' {
|
|
400
|
-
p.pos++
|
|
401
|
-
}
|
|
402
|
-
return v, nil
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// String literals
|
|
406
|
-
if ch == '\'' || ch == '"' || ch == '`' {
|
|
407
|
-
return p.parseString(ch)
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Array literal
|
|
411
|
-
if ch == '[' {
|
|
412
|
-
return p.parseArray()
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Object literal
|
|
416
|
-
if ch == '{' {
|
|
417
|
-
return p.parseObject()
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Number
|
|
421
|
-
if ch >= '0' && ch <= '9' {
|
|
422
|
-
return p.parseNumber()
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Keyword / identifier
|
|
426
|
-
if isIdentStart(ch) {
|
|
427
|
-
return p.parseIdentOrKeyword()
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
return nil, fmt.Errorf("unexpected character %q", ch)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
func (p *parser) parseString(quote byte) (any, error) {
|
|
434
|
-
p.pos++ // skip opening quote
|
|
435
|
-
var sb strings.Builder
|
|
436
|
-
for p.pos < len(p.input) {
|
|
437
|
-
c := p.input[p.pos]
|
|
438
|
-
if c == quote {
|
|
439
|
-
p.pos++
|
|
440
|
-
return sb.String(), nil
|
|
441
|
-
}
|
|
442
|
-
if c == '\\' && p.pos+1 < len(p.input) {
|
|
443
|
-
p.pos++
|
|
444
|
-
switch p.input[p.pos] {
|
|
445
|
-
case 'n':
|
|
446
|
-
sb.WriteByte('\n')
|
|
447
|
-
case 't':
|
|
448
|
-
sb.WriteByte('\t')
|
|
449
|
-
case 'r':
|
|
450
|
-
sb.WriteByte('\r')
|
|
451
|
-
default:
|
|
452
|
-
sb.WriteByte(p.input[p.pos])
|
|
453
|
-
}
|
|
454
|
-
p.pos++
|
|
455
|
-
continue
|
|
456
|
-
}
|
|
457
|
-
// Template literal interpolation (backtick): skip for now, treat as plain string
|
|
458
|
-
sb.WriteByte(c)
|
|
459
|
-
p.pos++
|
|
460
|
-
}
|
|
461
|
-
return sb.String(), nil
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
func (p *parser) parseNumber() (any, error) {
|
|
465
|
-
start := p.pos
|
|
466
|
-
for p.pos < len(p.input) && (p.input[p.pos] >= '0' && p.input[p.pos] <= '9' || p.input[p.pos] == '.' || p.input[p.pos] == 'e' || p.input[p.pos] == 'E' || p.input[p.pos] == '-' || p.input[p.pos] == '+') {
|
|
467
|
-
p.pos++
|
|
468
|
-
}
|
|
469
|
-
n, err := strconv.ParseFloat(p.input[start:p.pos], 64)
|
|
470
|
-
if err != nil {
|
|
471
|
-
return nil, fmt.Errorf("invalid number %q", p.input[start:p.pos])
|
|
472
|
-
}
|
|
473
|
-
return n, nil
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
func (p *parser) parseArray() (any, error) {
|
|
477
|
-
p.pos++ // skip '['
|
|
478
|
-
var arr []any
|
|
479
|
-
for {
|
|
480
|
-
p.skipWS()
|
|
481
|
-
if p.pos >= len(p.input) || p.input[p.pos] == ']' {
|
|
482
|
-
break
|
|
483
|
-
}
|
|
484
|
-
v, err := p.parseTernary()
|
|
485
|
-
if err != nil {
|
|
486
|
-
return nil, err
|
|
487
|
-
}
|
|
488
|
-
arr = append(arr, v)
|
|
489
|
-
p.skipWS()
|
|
490
|
-
if p.pos < len(p.input) && p.input[p.pos] == ',' {
|
|
491
|
-
p.pos++
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if p.pos < len(p.input) && p.input[p.pos] == ']' {
|
|
495
|
-
p.pos++
|
|
496
|
-
}
|
|
497
|
-
return arr, nil
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
func (p *parser) parseObject() (any, error) {
|
|
501
|
-
p.pos++ // skip '{'
|
|
502
|
-
obj := map[string]any{}
|
|
503
|
-
for {
|
|
504
|
-
p.skipWS()
|
|
505
|
-
if p.pos >= len(p.input) || p.input[p.pos] == '}' {
|
|
506
|
-
break
|
|
507
|
-
}
|
|
508
|
-
// key: string or ident
|
|
509
|
-
var key string
|
|
510
|
-
ch := p.input[p.pos]
|
|
511
|
-
if ch == '\'' || ch == '"' || ch == '`' {
|
|
512
|
-
v, err := p.parseString(ch)
|
|
513
|
-
if err != nil {
|
|
514
|
-
return nil, err
|
|
515
|
-
}
|
|
516
|
-
key = fmt.Sprintf("%v", v)
|
|
517
|
-
} else {
|
|
518
|
-
k, err := p.parseIdent()
|
|
519
|
-
if err != nil {
|
|
520
|
-
return nil, err
|
|
521
|
-
}
|
|
522
|
-
key = k
|
|
523
|
-
}
|
|
524
|
-
p.skipWS()
|
|
525
|
-
if p.pos < len(p.input) && p.input[p.pos] == ':' {
|
|
526
|
-
p.pos++
|
|
527
|
-
}
|
|
528
|
-
val, err := p.parseTernary()
|
|
529
|
-
if err != nil {
|
|
530
|
-
return nil, err
|
|
531
|
-
}
|
|
532
|
-
obj[key] = val
|
|
533
|
-
p.skipWS()
|
|
534
|
-
if p.pos < len(p.input) && p.input[p.pos] == ',' {
|
|
535
|
-
p.pos++
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
if p.pos < len(p.input) && p.input[p.pos] == '}' {
|
|
539
|
-
p.pos++
|
|
540
|
-
}
|
|
541
|
-
return obj, nil
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
func (p *parser) parseIdentOrKeyword() (any, error) {
|
|
545
|
-
name, err := p.parseIdent()
|
|
546
|
-
if err != nil {
|
|
547
|
-
return nil, err
|
|
548
|
-
}
|
|
549
|
-
switch name {
|
|
550
|
-
case "true":
|
|
551
|
-
return true, nil
|
|
552
|
-
case "false":
|
|
553
|
-
return false, nil
|
|
554
|
-
case "null", "undefined":
|
|
555
|
-
return nil, nil
|
|
556
|
-
case "NaN":
|
|
557
|
-
return math.NaN(), nil
|
|
558
|
-
case "Infinity":
|
|
559
|
-
return math.Inf(1), nil
|
|
560
|
-
}
|
|
561
|
-
// Scope lookup
|
|
562
|
-
if p.scope != nil {
|
|
563
|
-
if v, ok := p.scope[name]; ok {
|
|
564
|
-
return v, nil
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// Unknown identifier — return nil (like JS undefined)
|
|
568
|
-
return nil, nil
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
func (p *parser) parseIdent() (string, error) {
|
|
572
|
-
p.skipWS()
|
|
573
|
-
if p.pos >= len(p.input) || !isIdentStart(p.input[p.pos]) {
|
|
574
|
-
return "", fmt.Errorf("expected identifier at %q", p.input[p.pos:])
|
|
575
|
-
}
|
|
576
|
-
start := p.pos
|
|
577
|
-
for p.pos < len(p.input) && isIdentPart(p.input[p.pos]) {
|
|
578
|
-
p.pos++
|
|
579
|
-
}
|
|
580
|
-
return p.input[start:p.pos], nil
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
584
|
-
|
|
585
|
-
func isWS(c byte) bool {
|
|
586
|
-
return c == ' ' || c == '\t' || c == '\n' || c == '\r'
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
func isIdentStart(c byte) bool {
|
|
590
|
-
return c == '_' || c == '$' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c > 127 && unicode.IsLetter(rune(c))
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
func isIdentPart(c byte) bool {
|
|
594
|
-
return isIdentStart(c) || (c >= '0' && c <= '9')
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
func truthy(v any) bool {
|
|
598
|
-
if v == nil {
|
|
599
|
-
return false
|
|
600
|
-
}
|
|
601
|
-
switch t := v.(type) {
|
|
602
|
-
case bool:
|
|
603
|
-
return t
|
|
604
|
-
case float64:
|
|
605
|
-
return t != 0 && !math.IsNaN(t)
|
|
606
|
-
case string:
|
|
607
|
-
return t != ""
|
|
608
|
-
case []any:
|
|
609
|
-
return true
|
|
610
|
-
case map[string]any:
|
|
611
|
-
return true
|
|
612
|
-
}
|
|
613
|
-
return true
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
func toNumber(v any) (float64, bool) {
|
|
617
|
-
switch t := v.(type) {
|
|
618
|
-
case float64:
|
|
619
|
-
return t, true
|
|
620
|
-
case bool:
|
|
621
|
-
if t {
|
|
622
|
-
return 1, true
|
|
623
|
-
}
|
|
624
|
-
return 0, true
|
|
625
|
-
case string:
|
|
626
|
-
n, err := strconv.ParseFloat(strings.TrimSpace(t), 64)
|
|
627
|
-
if err != nil {
|
|
628
|
-
return 0, false
|
|
629
|
-
}
|
|
630
|
-
return n, true
|
|
631
|
-
case nil:
|
|
632
|
-
return 0, true
|
|
633
|
-
}
|
|
634
|
-
return 0, false
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
func addValues(a, b any) any {
|
|
638
|
-
// If either is a string, concatenate
|
|
639
|
-
as, aStr := a.(string)
|
|
640
|
-
bs, bStr := b.(string)
|
|
641
|
-
if aStr || bStr {
|
|
642
|
-
return fmt.Sprintf("%v%v", as+bStr2str(a, aStr), bs+bStr2str(b, bStr))
|
|
643
|
-
}
|
|
644
|
-
if an, ok := toNumber(a); ok {
|
|
645
|
-
if bn, ok := toNumber(b); ok {
|
|
646
|
-
return an + bn
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
return fmt.Sprintf("%v%v", a, b)
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
func bStr2str(v any, isStr bool) string {
|
|
653
|
-
if isStr {
|
|
654
|
-
return ""
|
|
655
|
-
}
|
|
656
|
-
return fmt.Sprintf("%v", v)
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
func numOp(a, b any, fn func(float64, float64) float64) any {
|
|
660
|
-
an, _ := toNumber(a)
|
|
661
|
-
bn, _ := toNumber(b)
|
|
662
|
-
return fn(an, bn)
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
func looseEq(a, b any) bool {
|
|
666
|
-
if a == nil && b == nil {
|
|
667
|
-
return true
|
|
668
|
-
}
|
|
669
|
-
if a == nil || b == nil {
|
|
670
|
-
return false
|
|
671
|
-
}
|
|
672
|
-
// Both numeric
|
|
673
|
-
an, aOK := toNumber(a)
|
|
674
|
-
bn, bOK := toNumber(b)
|
|
675
|
-
if aOK && bOK {
|
|
676
|
-
return an == bn
|
|
677
|
-
}
|
|
678
|
-
return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
func compareOp(a, b any) int {
|
|
682
|
-
an, aOK := toNumber(a)
|
|
683
|
-
bn, bOK := toNumber(b)
|
|
684
|
-
if aOK && bOK {
|
|
685
|
-
if an < bn {
|
|
686
|
-
return -1
|
|
687
|
-
}
|
|
688
|
-
if an > bn {
|
|
689
|
-
return 1
|
|
690
|
-
}
|
|
691
|
-
return 0
|
|
692
|
-
}
|
|
693
|
-
as := fmt.Sprintf("%v", a)
|
|
694
|
-
bs := fmt.Sprintf("%v", b)
|
|
695
|
-
if as < bs {
|
|
696
|
-
return -1
|
|
697
|
-
}
|
|
698
|
-
if as > bs {
|
|
699
|
-
return 1
|
|
700
|
-
}
|
|
701
|
-
return 0
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
func memberAccess(obj any, name string) any {
|
|
705
|
-
switch t := obj.(type) {
|
|
706
|
-
case map[string]any:
|
|
707
|
-
return t[name]
|
|
708
|
-
case []any:
|
|
709
|
-
if name == "length" {
|
|
710
|
-
return float64(len(t))
|
|
711
|
-
}
|
|
712
|
-
case string:
|
|
713
|
-
if name == "length" {
|
|
714
|
-
return float64(len(t))
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
return nil
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
func indexAccess(obj any, key any) any {
|
|
721
|
-
switch t := obj.(type) {
|
|
722
|
-
case []any:
|
|
723
|
-
if n, ok := toNumber(key); ok {
|
|
724
|
-
idx := int(n)
|
|
725
|
-
if idx >= 0 && idx < len(t) {
|
|
726
|
-
return t[idx]
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
case map[string]any:
|
|
730
|
-
return t[fmt.Sprintf("%v", key)]
|
|
731
|
-
}
|
|
732
|
-
return nil
|
|
733
|
-
}
|