detached c69a394

Merge pull request 'feat(expr): follow the C# runner semantics' (#3) from test/expr-fixtures into main

commit
c69a3945dc52c8652647a1fe1aba8669e2ee6664
author
bnema · · 2026-10-12 18:40 UTC
committer
brice · · 2026-10-18 14:59 UTC
merge of
b6c4c3a + 9668b06 · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
96 files · +13705 -1113
M THIRD_PARTY.md +2 -0
@@ -6,6 +6,8 @@ Test fixtures and schemas under `testdata/`, `workflow/testdata/` and `template/
6 6
7 https://github.com/actions/languageservices7 https://github.com/actions/languageservices
8 8
9+Used: `expr/testdata/languageservices/*.json` (the cross-language expression fixtures, commit 4043eda158e16579cc5fb1b0b07a4bce2a76f0b5) and the `@actions/expressions` package that the other `expr` oracle files were generated from.
10+
9 ```11 ```
10 MIT License12 MIT License
11 13
M expr/PORTING.md +392 -153
@@ -1,26 +1,88 @@
1 # Porting notes for package expr1 # Porting notes for package expr
2 2
3-The package is a port of `@actions/expressions` 0.3.61 (MIT, GitHub,3+The package implements the expression language of GitHub Actions as **the C#
4-actions/languageservices). The JavaScript is the specification wherever this4+runner** (actions/runner, `src/Sdk/DTExpressions2`) runs it on github.com. It
5-package could be checked against it: `testdata/oracle.json.gz` holds what the5+started as a port of `@actions/expressions` 0.3.61 (MIT, GitHub,
6-reference answers for 33 575 expressions (tokens with positions, error6+actions/languageservices), the JavaScript used by the editors, and was then
7-messages, result kinds, string coercions, `toJSON` output), and7+checked against the runner. Where the two differ the runner wins, because it
8-`TestOracle` replays it. `gen/oracle.mjs` regenerates it, `gen/golden.mjs` the8+is what users see at run time; the differences are listed below, with the C#
9-`testdata/tojson` goldens, and `gen/casing_dump.mjs` + `gen/casing.go` the9+source that decides each of them. The JavaScript is still the reference for
10-casing tables (`go generate ./expr`; Node 25 / V8 14.1, Unicode 16). The10+the token stream and positions of `Lex`, which `Parse` no longer uses.
11-tables are relative to the Go toolchain's own Unicode tables11+
12-(`unicode.Version`); `TestCasingTablesMatchGoUnicodeVersion` fails with the12+Three kinds of evidence back the tests:
13-regeneration command when the toolchain changes. The oracle records the13+
14-npm integrity (sha512) of the reference tarball and the Node/V8/Unicode14+- `testdata/oracle.json.gz`: what the JavaScript answers on 34 765 expressions
15-versions in its `reference` field, and `TestOracle` logs them.15+ (tokens, error messages, result kinds, string forms, `toJSON`), generated by
16-16+ `gen/oracle.mjs`; `TestOracle` replays it, and excuses a mismatch only where
17-The C# runner (actions/runner, `DistributedTask/Expressions2`) is the17+ the runner gives this package's answer.
18-implementation that runs on github.com. It was not available while this was18+- `testdata/runner.json.gz`: what the runner's own code answers on the same
19-written. Nothing below has been checked against it.19+ expressions. `gen/runner` compiles `src/Sdk/DTExpressions2` (plus stubs for
20-20+ the few assemblies it needs, see `gen/runner/README.md`) and runs it;
21-## Limits added by this port (not in the JavaScript)21+ `TestRunnerOracle` holds the package to it: 34 765 cases with the same
22-22+ result, string form, `toJSON` text and, for errors, the same message (parse
23-The reference has no bound on what an evaluation may build. These are23+ errors included, to the position); the only exception is the message text of
24+ Json.NET failures, which must be `fromJSON` failures on both sides.
25+ The same driver recorded `testdata/tojson/*.runner`, `ordinal_cases.txt`
26+ and `number_cases.txt`, and checked the number formatting on 200 000 random
27+ doubles.
28+- `testdata/languageservices/*.json`: the 1 020 cross-language fixtures of
29+ actions/languageservices (see `xlang_test.go`): 1 013 pass, 7 are known
30+ differences with a reason: negative zero (5 cases, below), `case/27` (the runner reports an even `case()` on
31+ the `(`, not the name) and `syntax-errors/memory-errors/0` (the runner's
32+ memory accounting). `testdata/languageservices/SOURCE` has the commit.
33+
34+The runner's own expression tests (`ExpressionParserL0`, `ConditionFunctionsL0`)
35+are ported in `runner_l0_test.go`; they are the only tests of the expression
36+engine in the runner's `src/Test/L0` (the other files that mention it test
37+templates and actions). The case-mapping tables of the JavaScript (`casing.go`,
38+`casing_tables.go`, `gen/casing*`) are kept for `Lex` and the oracle tests; the
39+code `Parse` and `Evaluate` use for strings is `ordinal.go`, `number.go` and
40+`json.go`. The WTF-8 helpers in `casing.go` are only reached through `Lex` and
41+tests.
42+
43+Regenerating: see `gen/runner/README.md`. The scripts need a clone of
44+actions/runner at the commit `5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798`
45+(`gen/runner/runner-commit.txt`, asserted by the scripts and by
46+`TestRunnerCommit`), the .NET SDK and a 9.0.x runtime (recorded: 9.0.20); none of
47+it is needed by `go test`.
48+
49+### The two engines of the runner, and which one runs
50+
51+The runner contains two copies of the expression engine:
52+`src/Sdk/DTExpressions2/Expressions2` (namespace `GitHub.DistributedTask.
53+Expressions2`, "legacy") and `src/Sdk/Expressions` (namespace `GitHub.Actions.
54+Expressions`, "newer", with its own `ExpressionData` types, `JsonParser` and
55+options such as `StrictJsonParsing`). They are separate implementations, almost
56+line for line the same.
57+
58+- **Which one runs.** The legacy one. `Runner.Worker/PipelineTemplateEvaluatorWrapper.cs`
59+ (used by `ExecutionContext.ToPipelineTemplateEvaluator`) and
60+ `ActionManifestManagerWrapper.cs` call `EvaluateAndCompare(legacy, new, ...)`:
61+ both evaluators run, the **legacy result (or its exception) is returned**, and a
62+ difference is only reported as job telemetry (`TemplateEvaluatorMismatch`,
63+ `RecordMismatch`). So the newer engine is a shadow being validated, and what
64+ users see is the legacy behaviour. This is why `gen/runner` builds the
65+ legacy engine.
66+- **How they differ.** `gen/runner-new` builds the newer one with the same driver
67+ and `gen/runner-new/engines.mjs` runs 46 148 expressions through both (the
68+ 34 765 of the oracle, the 1 791 `fromJSON` inputs, the number, comparison,
69+ `contains`, `startsWith` and `endsWith` corpora): the results are identical
70+ except for **negative zero** (the newer engine's `EvaluationResult.
71+ ConvertToString` maps `-0` to `0`, the legacy one prints `-0`; 3 expressions)
72+ and the **message of a `fromJSON` failure** (the newer one wraps it in "Error
73+ parsing fromJson"; both are `JsonReaderException`). In the sources the
74+ newer engine also has options the legacy one lacks: `StrictJsonParsing` (a
75+ `System.Text.Json` path in `fromJSON`, off by default, "temporary for
76+ telemetry"), `AlwaysTraceExpanded` and `MaxCacheMemory` (tracing), and its
77+ own `JsonParser`; none changes a result with the default options.
78+- **Consequence for this package.** It follows the legacy engine, as run today.
79+ Negative zero is the one place where the newer engine, the shared
80+ cross-language fixtures and this package agree against the legacy one; if
81+ the runner switches engines nothing else changes. See "Still open".
82+
83+## Limits added by this port (not in the JavaScript or the runner)
84+
85+The JavaScript has no bound on what an evaluation may build. These are
24 additions, each reported as an `*EvalError` (or `*Error` for the parser) and86 additions, each reported as an `*EvalError` (or `*Error` for the parser) and
25 none changes the result of an evaluation that stays within them.87 none changes the result of an evaluation that stays within them.
26 88
@@ -38,8 +100,8 @@ none changes the result of an evaluation that stays within them.
38 loops of filters, `join` and `contains`, and passes `ctx` to extension100 loops of filters, `join` and `contains`, and passes `ctx` to extension
39 functions.101 functions.
40 - **Evaluation depth** of 1000 for hand-built trees (parsed trees stay far102 - **Evaluation depth** of 1000 for hand-built trees (parsed trees stay far
41- below it), **JSON depth** `MaxJSONDepth` = 1000 for `FromJSON` and for the103+ below it), **JSON depth** `MaxJSONDepth` = 1000 for the
42- encoder (cyclic values built with `Array.Append` yield an error from104+ encoder (`FromJSON` is limited to 64 levels like Json.NET) (cyclic values built with `Array.Append` yield an error from
43 `EncodeJSON`, `"null"` from `ToJSON`, an `*EvalError` from `toJSON()`).105 `EncodeJSON`, `"null"` from `ToJSON`, an `*EvalError` from `toJSON()`).
44 The JS recursion limit is engine-dependent, around 5 000 to 10 000.106 The JS recursion limit is engine-dependent, around 5 000 to 10 000.
45 - **Built-in arity is re-checked at evaluation**, so a hand-built AST gets an107 - **Built-in arity is re-checked at evaluation**, so a hand-built AST gets an
@@ -47,8 +109,8 @@ none changes the result of an evaluation that stays within them.
47 - **Input length** is checked before the input is converted, without109 - **Input length** is checked before the input is converted, without
48 allocating.110 allocating.
49 111
50-Other limits that exist in the JS and are ported: expression length 21000112+Limits that the runner has and this package follows: expression length 21000
51-UTF-16 units and parser depth 50 (see below).113+UTF-16 units and tree depth 50 (see below).
52 114
53 ## Performance notes (deferred)115 ## Performance notes (deferred)
54 116
@@ -58,100 +120,242 @@ UTF-16 units and parser depth 50 (see below).
58 same contexts, add a prepared `Environment` (fold once, reuse) and cache120 same contexts, add a prepared `Environment` (fold once, reuse) and cache
59 folded names in the AST.121 folded names in the AST.
60 122
61-## Where the JavaScript is believed to differ from the C# runner123+## Where the JavaScript differs from the C# runner (and what this package does)
62-124+
63-Each item is implemented as the JavaScript does. Check the runner's tests125+Each item names the runner source that decides it. All are resolved; the
64-(`src/Test/L0/Expressions*`, `Sdk/Expressions2/...`) and decide for each one126+package follows the runner.
65-whether to keep the JS behaviour or follow the runner.127+
66-128+- [x] **String escapes: every `''` is unescaped.** Resolved: follows the C#.
67-- [ ] **String escapes: only the first `''` is unescaped.** The lexer does129+ `LexicalAnalyzer.ReadStringToken` (`Tokens/LexicalAnalyzer.cs`, the
68- `value.replace("''", "'")` with a string pattern, which replaces one130+ "Escaped single quote" branch) skips the second quote of each pair and
69- occurrence: `'a''b''c'` is `a'b''c`. The C# lexer should unescape every131+ appends the first, so `'a''b''c'` is `a'b'c`. The JavaScript lexer does
70- pair (`StringBuilder`/`Replace`). Probable JS bug. (`lexer.go`,132+ `value.replace("''", "'")` (first pair only) and gives `a'b''c`; that is
71- `consumeString`; tests: `TestEvalLiteralsAndOperators`.)133+ a bug of the editor library. Tests: `TestEvalLiteralsAndOperators`; the
72-- [ ] **`case()` availability.** The JS gates it behind the experimental flag134+ oracle excuses the JS mismatch because `testdata/runner.json.gz` agrees
73- `allowCaseFunction`, but only in completion; the parser and the135+ with this package.
74- evaluator always accept it. Here it is always on. Confirm whether the136+- [x] **`case()` availability.** Resolved: the runner has `case` always
75- hosted service accepts `case` and with which arity rules (the JS: at137+ (`ExpressionConstants`: `AddFunction<Case>("case", 3, Byte.MaxValue)`),
76- least 3 arguments, an odd number, predicates must be booleans).138+ with an odd number of arguments enforced at parse time
77-- [ ] **`format()`**: the reference uses `parseInt` on the digits, so139+ (`ExpressionParser.FlushTopEndParameters`: `EvenParameters`, reported on
78- `{99999999999999999999}` is an Infinity-like index and reports "more140+ the `(` token) and at evaluation (`Sdk/Functions/Case.cs`), predicates
79- arguments than were supplied". The `{0:x}` form silently ignores the141+ must be booleans ("case predicate must evaluate to a boolean value"), and
80- specifier; C# may throw. The error texts are the JS ones. Check the C#142+ only the matching branch is evaluated. `ExpressionParserL0` has a
81- `Format` node for the exact messages and for `{N}` with `N` > 255 or143+ regression test that `case(...)` must not turn on "allow unknown
82- leading zeros.144+ keywords" (ported in `runner_l0_test.go`). Here it is always on, with
83-- [ ] **`contains()` lower-cases (`toLowerCase`), `startsWith`/`endsWith`145+ the maximum of 255 arguments (the JS has no maximum).
84- upper-case (`toUpperSpecial`).** The C# uses `OrdinalIgnoreCase`146+- [x] **`format()`**: resolved, follows the C#. `Sdk/Functions/Format.cs`: the
85- comparisons, which differ for `ß`, `İ`, `ı`, the Kelvin sign and final147+ format string is scanned first (syntax and "more arguments than were
86- sigma. `contains('ß', 'ss')` is false in JS; `startsWith('ß', 'ss')` is148+ supplied" are reported before any argument is evaluated), arguments are
87- true. Check what the runner does.149+ then evaluated lazily, once each. The index is a **byte**
88-- [ ] **`==` on strings uses `toUpperSpecial`** (upper-case with the dotless150+ (`Byte.TryParse`): `{255}` is the last valid index, `{256}` and
89- `ı` left alone, full Unicode mapping so `ß == 'SS'`), and `<`/`>` compare151+ `{12345678901234567890123}` are "format string is invalid", not "more
90- the upper-cased UTF-16 code units. C# `OrdinalIgnoreCase` upper-cases152+ arguments". After the digits only `}` or `:specifiers}` are allowed
91- per UTF-16 code unit with simple mappings only: `ß == 'SS'` should be153+ (`{0 }`, `{0,5}` are invalid); **specifiers are an error** for every
92- false there.154+ value: `The format specifiers 'x' are not valid for objects of type
93-- [ ] **Number to string (`CoerceString`)**: `(+x.toFixed(15)).toString()`.155+ 'Number'` (kind names `Null Boolean Number String Array Object`).
94- C# formats a `double` with `"G15"`-like rules. They agree for ordinary156+ Messages are the same as the JS ones otherwise (`ExpressionResources.resx`:
95- values; check very small and very large numbers (`1e-7` here is `1e-7`,157+ `InvalidFormatString`, `InvalidFormatArgIndex`, `InvalidFormatSpecifiers`).
96- `1e21` is `1e+21`), `-0`, and 16-17 significant digit values.158+ The runner has the same 255-argument cap as the JS (`Byte.MaxValue`,
97-- [ ] **String to number**: `Number()` semantics (hex, octal, binary,159+ `ExpressionConstants`).
98- `Infinity`, whitespace trimmed, empty is 0). C# `double.TryParse` with160+- [x] **`contains()`, `startsWith`/`endsWith`, `==`, `<`, `>` and key lookup use
99- `NumberStyles` differs (no octal/binary; check `0x` and `Infinity`).161+ `OrdinalIgnoreCase`.** Resolved: follows the C#. Every string comparison
100- The lexer applies the same conversion to number literals, so `0x10`,162+ in the runner is `StringComparison.OrdinalIgnoreCase`
101- `0b11`, `1e3`, `.5`, `5.` and `+1` are all literals here.163+ (`EvaluationResult.cs` `AbstractEqual`/`AbstractGreaterThan`/
102-- [ ] **Number literals after `-` / `+`**: `-1` is one literal token; `a -1`164+ `AbstractLessThan`; `Sdk/Functions/Contains.cs`, `StartsWith.cs`,
103- is therefore a parse error, as in the JS.165+ `EndsWith.cs`; `StringComparer.OrdinalIgnoreCase` for context names,
104-- [ ] **Object key order from `fromJSON`**: array-index keys first, ascending166+ function names (`ExpressionParser.cs` `ParseContext`,
105- (JavaScript `Object.keys`), then insertion order. `Json.NET` keeps167+ `ExpressionConstants.WellKnownFunctions`) and dictionary keys
106- document order. The same ordering is applied by `toJSON` (the JS168+ (`DictionaryContextData.IndexLookup`)). That folds one code point at a
107- replacer builds a plain object). Check against the runner for169+ time with a simple mapping: `ß` is not `SS`, `ı` and `İ` and `ſ` and the
108- `toJSON(fromJSON('{"b":1,"1":2}'))`.170+ Kelvin sign are not `i`/`I`/`s`/`k`, `ς` and `σ` are equal, `Dž` is `dž`.
109-- [ ] **Duplicate JSON keys**: last value wins at the first position (JS);171+ `contains()` no longer lower-cases (JS) and `startsWith`/`endsWith` no
110- keys that differ only by case collapse to the first.172+ longer upper-case with full mappings (JS). Ordering (`<`, `>`) is by
111-- [ ] **`fromJSON` errors**: the JS message is173+ code point after folding, so a supplementary character sorts after
112- `Error parsing JSON when evaluating fromJson` with the V8 `SyntaxError`174+ U+E000..U+FFFF (JS orders UTF-16 units). `ordinal.go` implements it;
113- as cause; here the cause is a Go error with similar but not identical175+ `fold_tables.go` is generated from the runner's behaviour
114- wording (only the outer message is stable). `empty input` for blank176+ (`go generate`, `gen/runner --fold`), and
115- input. C# messages differ; check.177+ `TestOrdinalFoldMatchesRunner` replays 1 564 pairs recorded from .NET.
116-- [ ] **`toJSON`**: `JSON.stringify(v, replacer, " ")`: two-space indent,178+ The table is the one of the .NET runtime that produced it (9.0); the
117- `[]` and `{}` for empties, `NaN`/`Infinity` as `null`, `-0` as `0`,179+ runner ships its own runtime, so a different Unicode version could differ
118- no escaping of `<`, `>`, `&`, U+2028/U+2029; lone surrogates as180+ on newly assigned letters.
119- `\udXXX`. Json.NET differs on some of these (`Infinity`, `-0`, escapes).181+- [x] **`==` on strings, `<`, `>`**: see the previous item (same folding).
120-- [ ] **Index access**: `a[b]` where `b` is a number uses `floor(b)` and182+- [x] **Number to string (`CoerceString`)**: resolved, follows the C#. The
121- rejects NaN, negatives and infinities; where `b` is a string it is a key183+ runner converts every number with `Double.ToString("G15",
122- or a decimal index (`'1'`, `' 1 '`, `'1.9'` all index 1 of an array).184+ InvariantCulture)` (`ExpressionConstants.NumberFormat`,
123- Booleans and null index as `1`/`0` on arrays and as `"true"`/`""` keys on185+ `EvaluationResult.ConvertToString`, `src/Sdk/DTExpressions2/Expressions2/EvaluationResult.cs`),
124- dictionaries.186+ not `(+x.toFixed(15)).toString()`: 15 significant digits, positional
125-- [ ] **Filtered arrays** (`a.*`): a dictionary filtered by `*` yields its187+ for decimal exponents -5 < X < 15, otherwise `1E+21`, `1E-07`,
126- values; `.*.b` skips elements without `b`; `.*.*` flattens one level;188+ `1.23456789012346E+17` (uppercase `E`, at least two exponent digits).
127- arrays produced by `*` are fresh, so `a.* == a.*` is false.189+ So `1e15` is `1E+15`, `123456789012345678` is `1.23456789012346E+17`,
128-- [ ] **Comparison**: `null` is only ordered against numbers/booleans (it190+ `0.000001` is `1E-06`, `5e-324` is `4.94065645841247E-324`, `1e309` is
129- becomes 0); arrays and dictionaries are never ordered and `==` is191+ `Infinity`. Negative zero is `-0` (see the Negative zero item below). Checked against the
130- identity; `NaN` is unordered and unequal to itself; `true > false`.192+ runner's code on 200 000 random doubles. The number *literal* in an
131-- [ ] **`&&`/`||` return an operand**, not a boolean (`'' || 'x'` is `'x'`).193+ expression is parsed independently of this (see below) and the JSON
132-- [ ] **Limits**: max expression length 21000 (UTF-16 units; the JS throws a194+ writer of `toJSON` uses the same conversion (next item).
133- bare `Error("ErrorExceededMaxLength")`), max parse depth 50. The depth195+- [x] **String to number**: resolved, follows the C#. `ExpressionUtility.
134- counting (including `index()` and the flat logical chains) is ported196+ ParseNumber` (`src/Sdk/DTExpressions2/Expressions2/Sdk/ExpressionUtility.cs`)
135- literally; C# `MaxDepth` is 50 with its own counting. Cross-check with197+ trims with `String.Trim()` (so U+0085 is white space and U+FEFF is not),
136- deep-nesting tests. There is no node-count or evaluation-memory limit198+ accepts what `Double.TryParse(AllowLeadingSign | AllowDecimalPoint |
137- here; the C# runner has `MaxMemory` (1 MB) and a result-size limit.199+ AllowExponent)` accepts (ASCII digits only, trailing NULs ignored,
138-- [ ] **Trailing comma in calls**: `contains('a', 'b',)` parses (the JS loop200+ `Infinity` in any letter case with an optional sign), then `0x` hex and
139- only requires a comma when the next token is not `)`). `f(,)` does not.201+ `0o` octal (lower-case prefix only, no sign) read as a **32-bit signed
140-- [ ] **Whitespace**: only space, `\t`, `\r`, `\n` separate tokens. U+00A0 and202+ integer** (`0xffffffff` is `-1`, anything over 32 bits is NaN). There is
141- other Unicode spaces end a word but are not skipped, so `1 \u00a0 2`203+ no `0b` form and no upper-case `0X`/`0O`. The lexer uses the same
142- reports an unexpected symbol. `\v`, `\f` likewise.204+ function for number literals, so `0b101`, `0X10` and `0xFFFFFFFFF` are
143-- [ ] **Empty expression**: the JS parser returns `undefined` for no tokens.205+ "Unexpected symbol" errors and `0xffffffff` is `-1`. The same function is
144- `Parse` returns a nil Node and no error; `Evaluate(nil)` errors.206+ implemented as `parseNumber` in `number.go`;
145-- [ ] **Unknown contexts at evaluation time** are null (the JS `Dictionary.get`207+ `TestParseNumberMatchesRunner` replays 443 strings recorded from the
146- returns `undefined` and `visitContextAccess` then crashes later); here208+ runner's code (`gen/runner/number_cases.mjs`).
147- declared-but-absent contexts are null, never a crash.209+- [x] **Number literals after `-` / `+`**: `-1` is one literal token; `a -1`
148-- [ ] **Error positions**: the JS EOF token's start column is negative after a210+ is therefore a parse error. Same in the C# lexer (`LexicalAnalyzer.
149- trailing newline (stale `start`); ported as is211+ TryGetNextToken`: `c == '-' || c == '+' || digit` starts `ReadNumberToken`),
150- (`TestEOFTokenPosition`).212+ checked by the differential run.
151-- [ ] **Function names and context names are case-insensitive**; the JS213+- [x] **Object key order from `fromJSON`**: resolved, follows the C#. Keys keep
152- lower-cases with `toLowerCase`, as does this port (`foldKey`). Extension214+ the document order (`JTokenExtensions.ToPipelineContextData` iterates
153- functions override built-ins at evaluation (`functions?.get(...) ||215+ `JObject.Properties()` into `DictionaryContextData`, a list); the JS
154- wellKnownFunctions[...]`) but built-ins win at validation time (arity).216+ `Object.keys` order (array-index keys first) is gone, from `FromJSON` and
217+ from `toJSON`. `toJSON(fromJSON('{"b":1,"1":2}'))` is `b` then `1`.
218+- [x] **Duplicate JSON keys**: resolved, follows the C#. `subContext[property.Name] =
219+ ...` on a `DictionaryContextData` whose index is `OrdinalIgnoreCase`
220+ (`DictionaryContextData.this[]` setter): the last value wins, at the
221+ position and with the spelling of the first. `{"a":1,"A":2}` is
222+ `{"a": 2}` (the JS keeps `1`). Json.NET itself also merges exact
223+ duplicates (last wins).
224+- [x] **`fromJSON` errors and grammar**: resolved, follows the C#. The reader is
225+ Json.NET's (`Sdk/Functions/FromJson.cs`: `JToken.ReadFrom` on a
226+ `JsonTextReader` with `DateParseHandling.None`,
227+ `FloatParseHandling.Double`), which is lenient: single-quoted strings,
228+ unquoted names, trailing commas, `[,1]` (an empty element is `""`),
229+ `NaN`/`Infinity`/`-Infinity`/`undefined`, `.5`, `5.`, `1.e2`, `0x1F`, raw
230+ control characters in strings, anything after the first value, and
231+ `MaxDepth` 64. A comma where the document should start (`,1`) is an
232+ undefined value, `""`. **Leading zeros are octal**: a non-negative
233+ integer with a leading zero is `Convert.ToInt64(s, 8)` (`010` is 8, `050`
234+ is 40, `0777` is 511, wraps like a signed 64-bit value), `08` and `09`
235+ are errors, as are `01.5`, `012e1`, `00.5` (a leading zero before `.`,
236+ `e` or `E` is only valid for a single `0`); a negative number is decimal
237+ (`-010` is -10, `-01.5` is -1.5). An unpaired surrogate escape becomes
238+ U+FFFD. Blank input is a `JsonReaderException` (here: "empty input", kept
239+ as the outer message; the Json.NET message texts are not reproduced, only
240+ that the call fails). Not ported: `/* */` and `//` comments (the runner
241+ reads `/*x*/1` as the string `x`) and `new Date(1)` constructors; both are
242+ errors here. A `/` after `null`, `true`, `false`, `NaN`, `Infinity`,
243+ `-Infinity` or `undefined`, and a `/` between values inside an array or
244+ object, is an error here as in the runner (where it starts a comment or is
245+ an error); after a number or a string at the top level the rest is ignored
246+ in both. Checked on 1 791 inputs recorded from the runner
247+ (`testdata/fromjson_cases.json`, hand picked and seeded mutations; the
248+ 24 that contain a `/` or `new ` are skipped by the test), also used as
249+ fuzz seeds.
250+- [x] **`toJSON`**: resolved, follows the C#. `Sdk/Functions/ToJson.cs` builds
251+ the text itself: two-space indentation, `[]`/`{}` for empties, numbers
252+ through `ConvertToString` (so `NaN`, `Infinity` and `-Infinity` appear
253+ bare, `1E+21`, 15 significant digits, `-0` is `-0`) and strings through
254+ `JsonUtility.ToString` (Json.NET defaults: `\b \f \n \r \t`, `\u00XX`
255+ for other controls, `\u0085 \u2028 \u2029`, no escaping of `< > & '`, and
256+ non-ASCII as is). Checked against the runner's code
257+ (`testdata/tojson/*.runner`). The JS `null` for non-finite numbers, the
258+ shortest round-trip form and the raw U+2028 are not followed.
259+ Caveat: `JsonUtility` is stubbed in `gen/runner/Stubs.cs` with
260+ `JsonConvert.SerializeObject`; the real one passes the runner's
261+ `VssJsonMediaTypeFormatter` settings, which do not change string
262+ escaping.
263+- [x] **Lazy evaluation of built-in function arguments and of index
264+ expressions**: found by the differential run, not in the original list.
265+ In the runner a function evaluates only the arguments it uses
266+ (`Parameters[i].Evaluate(context)` inside `EvaluateCore`): `case` stops
267+ at the first true predicate, `contains`/`startsWith`/`endsWith` do not
268+ evaluate the second argument when the first is not primitive (or, for
269+ `contains`, not a non-empty array), `join` evaluates the separator only
270+ with two or more items, `format` evaluates only referenced arguments. And
271+ `a[b]` (`Index.cs`) evaluates `b` only if `a` is an array or a
272+ dictionary, and does not wrap its errors (the JS evaluates `b` first and
273+ wraps them in "could not evaluate index for index access"). So
274+ `join(success(), format('{2}'))` is `true` in the runner and an error in
275+ the JS. Built-ins now receive the call and evaluate what they need;
276+ extension functions still get evaluated arguments.
277+- [x] **Large integers in `fromJSON`**: found by the differential run. Json.NET
278+ reads an integer that does not fit in 64 bits as a `BigInteger`, and
279+ `(Double)BigInteger` truncates toward zero to 53 bits instead of rounding
280+ to nearest, so `fromJSON('18446744073709551615')` is 2^64-2048, not 2^64,
281+ and does not equal the literal `18446744073709551615` of an expression
282+ (which `Double.TryParse` rounds to nearest). Reproduced in
283+ `jsonParser.number` (`TestFromJSONBigIntegers`); checked on 57 values.
284+- [x] **Index access**: resolved, same in the C# (`Sdk/Operators/Index.cs`,
285+ `IndexHelper`): a number is floored, NaN, negatives and numbers above
286+ Int32.MaxValue give no index; a primitive also has a string form for
287+ dictionary keys, so booleans and null are `"true"`/`""` there and 1/0 on
288+ arrays; `' 1 '` and `'1.9'` index element 1. Differences found: the
289+ index is only evaluated when the object is an array or a dictionary, and
290+ its errors are not wrapped (see the lazy evaluation item above).
291+ `TestRunnerSemantics`.
292+- [x] **Filtered arrays** (`a.*`): resolved, same in the C#
293+ (`Index.HandleFilteredArray`): `*` on a dictionary yields its values,
294+ `.*.b` skips elements without `b`, `.*.*` flattens one level, and every
295+ `*` builds a new array, so `a.* == a.*` is false. `TestRunnerSemantics`.
296+- [x] **Comparison**: resolved, same in the C# (`EvaluationResult.cs`
297+ `CoerceTypes`/`AbstractEqual`/`AbstractGreaterThan`): null and booleans
298+ become numbers when compared with another kind, a string compared with a
299+ number is parsed with `ParseNumber`, arrays and dictionaries compare by
300+ reference and are never ordered, NaN is unequal to itself and unordered.
301+ Strings compare with `OrdinalIgnoreCase` (see above). `TestRunnerSemantics`.
302+- [x] **`&&`/`||` return an operand**: resolved, same in the C# (`And.cs`,
303+ `Or.cs`: they return `result.Value` of the deciding operand).
304+- [x] **Limits**: resolved, follows the C#. Length 21000 UTF-16 units
305+ (`ExpressionConstants.MaxLength`): "Exceeded max expression length 21000",
306+ without a token (the JS throws a bare `ErrorExceededMaxLength`). Depth 50
307+ is checked on the finished tree (`ExpressionParser.CheckMaxDepth`): the
308+ root is depth 1, an operand of an operator, an argument of a call and the
309+ two sides of an index are one deeper, **parentheses add nothing**, and a
310+ chain of `&&` or `||` is one node, nested ones included, so
311+ `((((...1...))))` with 500 parentheses and `1 || (1 || (1 ...))` parse;
312+ `"!" * 50 + "true"` and 50 comparisons chained with `==` do not. The error
313+ has no token: "Exceeded max expression depth 50". The JS limit counted
314+ parser recursion (parentheses included). Memory: the runner has
315+ `EvaluationOptions.MaxMemory` (1 MiB by default) and fails with "The
316+ maximum allowed memory size was exceeded while evaluating the following
317+ expression: ...", counting strings at 26 bytes + 2 per UTF-16 unit and 24
318+ per object, per call depth (`EvaluationMemory`). This package keeps its
319+ own output budget (see above), which is not byte-for-byte the same, so
320+ the fixture `syntax-errors.json` "memory-errors" is a known difference.
321+- [x] **Trailing comma in calls**: resolved, follows the C#. `contains('a', 'b',)`
322+ is "Unexpected symbol: ')'" (the tokenizer rejects a `)` after a `,`). The JS
323+ accepted it. `f(,)` and `contains(,'a')` fail as before.
324+- [x] **Whitespace**: resolved, follows the C#. `Char.IsWhiteSpace` separates
325+ tokens: U+0009..U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000..U+200A,
326+ U+2028, U+2029, U+202F, U+205F, U+3000. So `1 \\u00a0 2` is "Unexpected
327+ symbol: '2'" (not the NBSP), and `\\v1` is 1. The JS skipped only space,
328+ tab, CR and LF. U+FEFF is not white space.
329+- [x] **Empty expression**: same in the C# (`CreateTree` returns null when there
330+ is no token). `Parse` returns a nil Node and no error; `Evaluate(nil)`
331+ errors; callers (the template layer) decide.
332+- [x] **Unknown contexts at evaluation time** are null: in the runner a
333+ declared named value is evaluated by the caller's node; here an absent
334+ key of `EvalContext.Contexts` is null.
335+- [x] **Error positions**: resolved, follows the C#. A `ParseException` names
336+ the token and its **absolute 1-based index in the expression**
337+ (`Located at position N within expression: <whole expression>`);
338+ `Error.Pos` is the same place as a zero-based line and column.
339+ Arity and even-argument errors name the `(` of the call (the token
340+ `FlushTopEndParameters` holds), not the function: `contains()` is
341+ "Too few parameters supplied: '('. Located at position 9". The
342+ end-of-input error is "Unexpected end of expression: '<last token>'"
343+ (the JS said "Unexpected symbol: 'EOF'"), and the JS's negative EOF column
344+ is gone with the JS lexer. Unexpected-symbol tokens are the
345+ boundary-delimited run (`ReadKeywordToken`): `github..event` reports `.`
346+ where the JS reported `.event`. Lexing and parsing are interleaved, so the
347+ first problem in reading order wins (the JS lexed everything first).
348+ `TestParseErrorPositions`, and `TestRunnerOracle` on the 1 715 parse errors of the corpus (identical message, position included).
349+ One fixture still differs, see `xlangKnownDifferences`: `case/27` expects
350+ the name for an even `case()`, the runner passes the `(`.
351+- [x] **Function names and context names are case-insensitive**: same in the
352+ C# (`StringComparer.OrdinalIgnoreCase` dictionaries; see the folding
353+ item). Built-ins win over extension functions of the same name at
354+ validation **and at evaluation** (`ExpressionParser.TryGetFunctionInfo`
355+ looks in `ExpressionConstants.WellKnownFunctions` first and builds the
356+ node from it); the JS let an extension override a built-in at
357+ evaluation. An extension supplied under a built-in's name is now never
358+ called.
155 359
156 ## JavaScript behaviours deliberately NOT ported360 ## JavaScript behaviours deliberately NOT ported
157 361
@@ -170,37 +374,72 @@ a bug.
170 evaluating e.g. `github.true` with an empty context: the reference does not374 evaluating e.g. `github.true` with an empty context: the reference does not
171 handle a missing root context. Here it is null.375 handle a missing root context. Here it is null.
172 - Recursion limits: JS recursion depth for `fromJSON` input is engine376 - Recursion limits: JS recursion depth for `fromJSON` input is engine
173- dependent (about 5 000 to 10 000 levels). `FromJSON` accepts `MaxJSONDepth`377+ dependent. `FromJSON` follows Json.NET's `MaxDepth` of 64.
174- (1000) levels.378+- Lone surrogates: JS strings are UTF-16 and can hold them. .NET strings can
175-- Lone surrogates: JS strings are UTF-16 and can hold them; Go strings are379+ too, but no value of an expression has one: Json.NET reads an unpaired
176- UTF-8. They are kept as WTF-8 (`0xED 0xA0-0xBF xx`) so that `fromJSON` /380+ `\ud800` escape as U+FFFD (checked: `fromJSON('"\ud800"') == '\ufffd'` is
177- `toJSON` round-trip, `==`/`<`/`contains`/`startsWith`/`endsWith` compare381+ true), and Go strings cannot hold them either. The WTF-8 helpers of the first
178- them like code units, case mapping copies them, and `format`/`join` rejoin a382+ version (`wtf8Surrogate`, `normalizeWTF8` in `casing.go`) are only reached
179- high surrogate followed by a low one into the character they form. This is383+ through `Lex`-era code and the tests.
180- covered by oracle cases. Two surrogate strings concatenated by other means384+
181- (an extension function) are not rejoined.385+## Still open
182-386+
183-## Other things to check once the runner is available387+- [ ] **Memory accounting.** The runner caps an evaluation at `MaxMemory` (1 MiB)
184-388+ counted by `EvaluationMemory`/`MemoryCounter` (26 bytes + 2 per UTF-16 unit
185-- [ ] Run the runner's expression test vectors (`ExpressionParserL0`,389+ per string, 24 per object, trimmed per call depth); this package caps
186- `ExpressionTests`, `ExpressionValuesL0`, `ExpressionTestUtil`) through a390+ `MaxResultBytes` (1 MiB of output, different units) and accepts
187- table adapter; list every disagreement here before deciding.391+ `format(...)` chains the runner rejects. Fixture
188-- [ ] Decide whether `EvalContext` needs a hook for the functions the runner392+ `syntax-errors/memory-errors/0` is the known difference. Reproducing
189- implements natively with special semantics: `success()`, `failure()`,393+ `EvaluationMemory` exactly would need the node depth of every value;
190- `always()`, `cancelled()` (take no arguments; read job status) and394+ worth doing only if the error text matters ("The maximum allowed memory
191- `hashFiles()` (variadic glob patterns, string result). The API supports395+ size was exceeded while evaluating the following expression: ...", which
192- them through `FuncInfo` + `Func` without changes; arity is declared by396+ needs `ConvertToExpression` of the root, also not ported).
193- the caller.397+- [x] **Negative zero**: `-0` is `-0`, as the legacy engine that runs today
194-- [ ] Context names the runner allows per location (`github`, `env`, `vars`,398+ (`DTExpressions2/EvaluationResult.ConvertToString` formats with `G15` and
195- `job`, `jobs`, `steps`, `runner`, `secrets`, `strategy`, `matrix`,399+ has no special case; .NET Core writes `-0`): `format('{0}', -0)`, `-0`
196- `needs`, `inputs`) and which functions each location may call400+ as a result and `toJSON(-0)` (`toJSON` uses `ConvertToString`). A JSON
197- (`success`... only in `if`/`jobs.*.if`...). This belongs to the template401+ integer token `-0` or `-00` is read as the long 0, but `-0.0` keeps its
198- layer, not to package expr.402+ sign. The newer engine (`Expressions/EvaluationResult.cs`, "keep our
199-- [ ] `format()` argument limit is 255 in the JS (`MAX_ARGUMENTS`); the C#403+ behavior consistent") maps it to `0`, and so do these shared fixtures:
200- limit is `ExpressionConstants.MaxArguments` (check).404+ `coerce_string/number#10-11`, `format/format#19-20` and
201-- [ ] Case-insensitive comparison and `contains`: if C# `OrdinalIgnoreCase`405+ `tojson/basics#4` (5 cases) are listed in `xlangKnownDifferences` with this
202- is the truth, replace `toUpperSpecial`/`jsToLower` by per-UTF-16-unit406+ evidence. `engines.mjs` shows it is the only result on which the engines
203- simple upper-casing and regenerate the oracle tests accordingly.407+ differ. **Track the runner's switch to `src/Sdk/Expressions`
204-- [ ] Secret masking and the `${{ }}` splitting of a larger string are not408+ (`TemplateEvaluatorMismatch` telemetry); re-run
205- part of this package (they belong to the template layer): `Parse`409+ `gen/runner-new/engines.mjs` on each runner release.** If it switches,
206- takes the text between the braces only.410+ change `numberToCoercedString` back to `"0"` (one line) and the tests
411+ that mention `-0`.
412+- [ ] **Comments and constructors in `fromJSON`.** Json.NET accepts `/* */` and
413+ `//` comments and `new Date(1)` constructors; the first is read in a
414+ surprising way (`/*x*/1` is the string `x`). They fail here.
415+- [ ] **`fromJSON` error texts.** Json.NET messages ("Unexpected character
416+ encountered while parsing value: a. Path '', line 0, position 0.") are
417+ not reproduced; the outer message "Error parsing JSON when evaluating
418+ fromJson" is the JS one, the runner's exception message is the Json.NET
419+ one. Only the failure is checked.
420+- [ ] **`StrictJsonParsing`.** The newer engine has an option that switches
421+ `fromJSON` to `System.Text.Json` (strict, 64 levels, different
422+ messages); default off, "temporary for telemetry". Not implemented.
423+- [ ] **Unicode version of the folding table** is that of the .NET 9 runtime that
424+ generated `fold_tables.go`; a runner on another runtime may fold newly
425+ assigned letters differently.
426+- [ ] **Tracing and secret masking** (`ITraceWriter`, `ISecretMasker`,
427+ `EvaluationResult` tracing) are runner features outside this package.
428+
429+## Left to the callers
430+
431+- Run the runner's expression test vectors: done for `ExpressionParserL0` and
432+ `ConditionFunctionsL0` (`runner_l0_test.go`). The runner has no other
433+ expression tests in `src/Test/L0` (the larger suites are in the closed
434+ service); the cross-language fixtures cover the rest.
435+- The functions the runner implements natively with special semantics:
436+ `success()`, `failure()`, `always()`, `cancelled()` (no arguments; read job
437+ status, or `action_status` in a composite action's main step) and
438+ `hashFiles()` (variadic, string result) are supplied by the caller through
439+ `FuncInfo` + `Func`; `runner_l0_test.go` shows the declaration the runner
440+ uses (`FunctionInfo(name, 0, 0)`).
441+- Context names per location and which functions each location may call belong
442+ to the template layer.
443+- The `format()` argument limit is 255 in both (`Byte.MaxValue`).
444+- Secret masking and the `${{ }}` splitting of a larger string are not part of
445+ this package: `Parse` takes the text between the braces only.
M expr/casing.go +0 -5
@@ -27,11 +27,6 @@ func isJSSpace(r rune) bool {
27 return r >= 0x2000 && r <= 0x200A27 return r >= 0x2000 && r <= 0x200A
28 }28 }
29 29
30-// jsTrim is String.prototype.trim.
31-func jsTrim(s string) string {
32- return strings.TrimFunc(s, isJSSpace)
33-}
34-
35 func isASCII(s string) bool {30 func isASCII(s string) bool {
36 for i := 0; i < len(s); i++ {31 for i := 0; i < len(s); i++ {
37 if s[i] >= utf8.RuneSelf {32 if s[i] >= utf8.RuneSelf {
M expr/data.go +14 -5
@@ -160,6 +160,19 @@ func (d *Dictionary) Add(key string, v Value) bool {
160 return true160 return true
161 }161 }
162 162
163+// set stores v under key: a key that differs only by case from an existing one
164+// replaces its value and keeps its spelling and position.
165+func (d *Dictionary) set(key string, v Value) {
166+ if d.index == nil {
167+ d.index = map[string]int{}
168+ }
169+ if i, ok := d.index[foldKey(key)]; ok {
170+ d.vals[i] = v
171+ return
172+ }
173+ d.Add(key, v)
174+}
175+
163 // Get returns the value stored under key, compared case-insensitively.176 // Get returns the value stored under key, compared case-insensitively.
164 func (d *Dictionary) Get(key string) (Value, bool) {177 func (d *Dictionary) Get(key string) (Value, bool) {
165 if d == nil {178 if d == nil {
@@ -212,10 +225,6 @@ func (d *Dictionary) Pairs() []Pair {
212 return out225 return out
213 }226 }
214 227
215-// foldKey is the case-insensitive form of a dictionary key. GitHub lower-cases
216-// keys with JavaScript's toLowerCase.
217-func foldKey(key string) string { return jsToLower(key) }
218-
219 // normalize maps nil values to Null so that callers can switch on Kind.228 // normalize maps nil values to Null so that callers can switch on Kind.
220 func normalize(v Value) Value {229 func normalize(v Value) Value {
221 switch x := v.(type) {230 switch x := v.(type) {
@@ -257,7 +266,7 @@ func toNumber(v Value) float64 {
257 case Number:266 case Number:
258 return float64(x)267 return float64(x)
259 case String:268 case String:
260- return jsToNumber(string(x))269+ return parseNumber(string(x))
261 }270 }
262 return nan271 return nan
263 }272 }
M expr/doc.go +9 -6
@@ -6,12 +6,15 @@
6 // Package expr implements GitHub Actions expressions: the language found6 // Package expr implements GitHub Actions expressions: the language found
7 // between the braces of ${{ }} in workflow files.7 // between the braces of ${{ }} in workflow files.
8 //8 //
9-// The behaviour is a faithful port of GitHub's own implementation published9+// The behaviour is that of the C# runner (actions/runner, MIT), the
10-// as the npm package @actions/expressions (MIT, actions/languageservices):10+// implementation that evaluates expressions on github.com: its tokenizer and
11-// same tokens, precedence, error messages, coercions and built-in functions.11+// parser, error messages, coercions, case-insensitive comparison, number
12-// Where that implementation is believed to differ from the hosted service12+// formatting, JSON reader and writer, and built-in functions. It was first
13-// (the C# runner in actions/runner), the Go code follows the JavaScript and13+// ported from GitHub's editor library, the npm package @actions/expressions
14-// says so in a comment; PORTING.md lists what remains to be checked.14+// (MIT, actions/languageservices), then checked against the runner on 34 765
15+// expressions and against the shared cross-language fixtures; where the two
16+// differ, the runner wins. PORTING.md lists every difference with the C#
17+// source that decides it.
15 //18 //
16 // The entry points are [Parse], which turns the text of one expression into a19 // The entry points are [Parse], which turns the text of one expression into a
17 // [Node], and [Evaluate], which computes its [Value] against the named20 // [Node], and [Evaluate], which computes its [Value] against the named
M expr/errors.go +0 -9
@@ -82,15 +82,6 @@ type Error struct {
82 82
83 func (e *Error) Error() string { return e.Message }83 func (e *Error) Error() string { return e.Message }
84 84
85-func newTokenError(t ErrorType, tok Token) *Error {
86- return &Error{
87- Type: t,
88- Message: t.description() + ": '" + tokenString(tok) + "'",
89- Pos: tok.Range.Start,
90- Token: tokenString(tok),
91- }
92-}
93-
94 // EvalError is an error raised while evaluating an expression: a failing85 // EvalError is an error raised while evaluating an expression: a failing
95 // function, such as fromJSON on invalid input, or an invalid format string.86 // function, such as fromJSON on invalid input, or an invalid format string.
96 type EvalError struct {87 type EvalError struct {
M expr/eval.go +50 -38
@@ -42,8 +42,8 @@ type EvalContext struct {
42 Contexts map[string]Value42 Contexts map[string]Value
43 // Funcs implements the extra functions declared to [Parse], such as43 // Funcs implements the extra functions declared to [Parse], such as
44 // hashFiles, success, always, cancelled and failure. Names are matched44 // hashFiles, success, always, cancelled and failure. Names are matched
45- // case-insensitively. As in the reference implementation these take45+ // case-insensitively. The built-in functions cannot be
46- // precedence over the built-in functions.46+ // replaced: as in the runner, they win over a function of the same name.
47 Funcs map[string]Func47 Funcs map[string]Func
48 // MaxResultBytes is the output budget of the evaluation, see48 // MaxResultBytes is the output budget of the evaluation, see
49 // [MaxResultBytes]. Zero means the default; a negative value removes the49 // [MaxResultBytes]. Zero means the default; a negative value removes the
@@ -233,21 +233,22 @@ func (e *evaluator) logical(l *Logical) (Value, error) {
233 233
234 func (e *evaluator) call(c *FunctionCall) (Value, error) {234 func (e *evaluator) call(c *FunctionCall) (Value, error) {
235 name := foldKey(c.Name.Lexeme)235 name := foldKey(c.Name.Lexeme)
236- ext, isExt := e.funcs[name]236+ // Built-ins win over extension functions of the same name, as in the
237- isExt = isExt && ext != nil237+ // runner (ExpressionParser.TryGetFunctionInfo looks in WellKnownFunctions
238- var impl builtinFunc238+ // first); the JavaScript reference let an extension override them here.
239- if !isExt {239+ if f, ok := builtinFuncs[name]; ok {
240- // Arity of the built-ins is checked by the parser, and again here240+ // Arity is checked by the parser, and again here so that a hand-built
241- // so that a hand-built tree cannot reach an implementation with the241+ // tree cannot reach an implementation with the wrong number of
242- // wrong number of arguments.242+ // arguments.
243- f, ok := builtinFuncs[name]
244- if !ok {
245- return nil, evalErrorf("function %s is not implemented", c.Name.Lexeme)
246- }
247 if t, bad := arityError(name, f, len(c.Args)); bad {243 if t, bad := arityError(name, f, len(c.Args)); bad {
248 return nil, &EvalError{Message: t.description() + ": '" + c.Name.Lexeme + "'"}244 return nil, &EvalError{Message: t.description() + ": '" + c.Name.Lexeme + "'"}
249 }245 }
250- impl = builtinImpls[name]246+ // Built-ins evaluate their own arguments, lazily.
247+ return builtinImpls[name](e, c)
248+ }
249+ ext, ok := e.funcs[name]
250+ if !ok || ext == nil {
251+ return nil, evalErrorf("function %s is not implemented", c.Name.Lexeme)
251 }252 }
252 args := make([]Value, len(c.Args))253 args := make([]Value, len(c.Args))
253 for i, a := range c.Args {254 for i, a := range c.Args {
@@ -257,18 +258,15 @@ func (e *evaluator) call(c *FunctionCall) (Value, error) {
257 }258 }
258 args[i] = v259 args[i] = v
259 }260 }
260- if isExt {261+ v, err := ext(e.ctx, args)
261- v, err := ext(e.ctx, args)262+ if err != nil {
262- if err != nil {263+ var ee *EvalError
263- var ee *EvalError264+ if errors.As(err, &ee) {
264- if errors.As(err, &ee) {265+ return nil, err
265- return nil, err
266- }
267- return nil, &EvalError{Message: err.Error(), Err: err}
268 }266 }
269- return v, nil267+ return nil, &EvalError{Message: err.Error(), Err: err}
270 }268 }
271- return impl(e, args)269+ return v, nil
272 }270 }
273 271
274 // indexer is idxHelper of the reference: how an index value is read, as a272 // indexer is idxHelper of the reference: how an index value is read, as a
@@ -309,36 +307,50 @@ func (ix indexer) pos(n int) (int, bool) {
309 return int(ix.intv), true307 return int(ix.intv), true
310 }308 }
311 309
310+// indexAccess evaluates a[b] as Index.cs does: the object first, then, only if
311+// it is an array or a dictionary, the index, whose errors are reported as they
312+// are (the JavaScript reference evaluates the index first and wraps its errors
313+// in "could not evaluate index for index access").
312 func (e *evaluator) indexAccess(ia *IndexAccess) (Value, error) {314 func (e *evaluator) indexAccess(ia *IndexAccess) (Value, error) {
313- var ix indexer315+ _, isStar := ia.Index.(*Star)
314- if _, isStar := ia.Index.(*Star); isStar {
315- ix = newIndexer(true, nil)
316- } else {
317- v, err := e.eval(ia.Index)
318- if err != nil {
319- return nil, &EvalError{Message: "could not evaluate index for index access: " + err.Error(), Err: err}
320- }
321- ix = newIndexer(false, v)
322- }
323 obj, err := e.eval(ia.Expr)316 obj, err := e.eval(ia.Expr)
324 if err != nil {317 if err != nil {
325 return nil, err318 return nil, err
326 }319 }
327 switch o := normalize(obj).(type) {320 switch o := normalize(obj).(type) {
328 case *Array:321 case *Array:
322+ ix, err := e.indexer(ia, isStar)
323+ if err != nil {
324+ return nil, err
325+ }
329 if o.filtered {326 if o.filtered {
330 return e.filteredArrayAccess(o, ix)327 return e.filteredArrayAccess(o, ix)
331 }328 }
332 return e.arrayAccess(o, ix)329 return e.arrayAccess(o, ix)
333 case *Dictionary:330 case *Dictionary:
331+ ix, err := e.indexer(ia, isStar)
332+ if err != nil {
333+ return nil, err
334+ }
334 return e.objectAccess(o, ix)335 return e.objectAccess(o, ix)
335 }336 }
336- if ix.star {337+ if isStar {
337 return &Array{filtered: true}, nil338 return &Array{filtered: true}, nil
338 }339 }
339 return Null{}, nil340 return Null{}, nil
340 }341 }
341 342
343+func (e *evaluator) indexer(ia *IndexAccess, isStar bool) (indexer, error) {
344+ if isStar {
345+ return newIndexer(true, nil), nil
346+ }
347+ v, err := e.eval(ia.Index)
348+ if err != nil {
349+ return indexer{}, err
350+ }
351+ return newIndexer(false, v), nil
352+}
353+
342 // filteredArrayAccess applies an index to each element of a filtered array354 // filteredArrayAccess applies an index to each element of a filtered array
343 // and collects the hits: "a.*.b" is the b of every element that has one.355 // and collects the hits: "a.*.b" is the b of every element that has one.
344 func (e *evaluator) filteredArrayAccess(fa *Array, ix indexer) (Value, error) {356 func (e *evaluator) filteredArrayAccess(fa *Array, ix indexer) (Value, error) {
@@ -437,7 +449,7 @@ func coerceTypes(l, r Value) (Value, Value) {
437 }449 }
438 450
439 // equals is the == operator: loose equality, case-insensitive for strings451 // equals is the == operator: loose equality, case-insensitive for strings
440-// (see toUpperSpecial), false for NaN, and by identity for arrays and452+// (see ordinal.go), false for NaN, and by identity for arrays and
441 // dictionaries.453 // dictionaries.
442 func equals(lhs, rhs Value) bool {454 func equals(lhs, rhs Value) bool {
443 l, r := coerceTypes(lhs, rhs)455 l, r := coerceTypes(lhs, rhs)
@@ -450,7 +462,7 @@ func equals(lhs, rhs Value) bool {
450 case Number:462 case Number:
451 return float64(lv) == float64(r.(Number)) // false when either is NaN463 return float64(lv) == float64(r.(Number)) // false when either is NaN
452 case String:464 case String:
453- return toUpperSpecial(string(lv)) == toUpperSpecial(string(r.(String)))465+ return ordinalEqual(string(lv), string(r.(String)))
454 case Boolean:466 case Boolean:
455 return lv == r.(Boolean)467 return lv == r.(Boolean)
456 case *Array:468 case *Array:
@@ -482,7 +494,7 @@ func compare(lhs, rhs Value) (c int, ok bool) {
482 }494 }
483 return 0, true495 return 0, true
484 case String:496 case String:
485- return compareUTF16(toUpperSpecial(string(lv)), toUpperSpecial(string(r.(String)))), true497+ return ordinalCompare(string(lv), string(r.(String))), true
486 case Boolean:498 case Boolean:
487 // false < true499 // false < true
488 switch rb := r.(Boolean); {500 switch rb := r.(Boolean); {
M expr/eval_test.go +160 -119
@@ -80,19 +80,26 @@ func TestEvalLiteralsAndOperators(t *testing.T) {
80 {"1e3", "Number:1000"},80 {"1e3", "Number:1000"},
81 {"0x10", "Number:16"},81 {"0x10", "Number:16"},
82 {"0o17", "Number:15"},82 {"0o17", "Number:15"},
83- {"0b101", "Number:5"},83+ {"0xffffffff", "Number:-1"}, // read as a 32-bit integer, like the runner
84- {"-0x10", "PARSE Unexpected symbol: '-0x10'. Located at position 1 within expression: -0x10"},84+ {"0b101", "PARSE Unexpected symbol: '0b101'"},
85+ {"0X10", "PARSE Unexpected symbol: '0X10'"},
86+ {"'0b11' == 3", "Boolean:false"},
87+ {"'0x1F' == 31", "Boolean:true"},
88+ {"'infinity' == Infinity", "Boolean:true"},
89+ {"'\u0085 1 \u0085' == 1", "Boolean:true"},
90+ {"'\ufeff1' == 1", "Boolean:false"},
91+ {"-0x10", "PARSE Unexpected symbol: '-0x10'"},
85 {"NaN", "Number:NaN"},92 {"NaN", "Number:NaN"},
86 {"Infinity", "Number:Infinity"},93 {"Infinity", "Number:Infinity"},
87 {"-Infinity", "Number:-Infinity"},94 {"-Infinity", "Number:-Infinity"},
88 {"'a'", "String:a"},95 {"'a'", "String:a"},
89 {"''", "String:"},96 {"''", "String:"},
90 {"'it''s'", "String:it's"},97 {"'it''s'", "String:it's"},
91- // Only the first doubled quote is unescaped: the reference's98+ // Every doubled quote is unescaped, as the C# runner does (the
92- // value.replace("''", "'") is not global.99+ // JavaScript reference stops after the first).
93- {"'a''b''c'", "String:a'b''c"},100+ {"'a''b''c'", "String:a'b'c"},
94 {"''''", "String:'"},101 {"''''", "String:'"},
95- {"'a''''b'", "String:a'''b"},102+ {"'a''''b'", "String:a''b"},
96 103
97 {"1 == 1", "Boolean:true"},104 {"1 == 1", "Boolean:true"},
98 {"1 == 2", "Boolean:false"},105 {"1 == 2", "Boolean:false"},
@@ -149,8 +156,16 @@ func TestEvalLiteralsAndOperators(t *testing.T) {
149 {"'a' <= 'A'", "Boolean:true"},156 {"'a' <= 'A'", "Boolean:true"},
150 {"'Z' < 'a'", "Boolean:false"},157 {"'Z' < 'a'", "Boolean:false"},
151 {"'_' < 'a'", "Boolean:false"}, // '_' (0x5F) sorts after upper-case letters158 {"'_' < 'a'", "Boolean:false"}, // '_' (0x5F) sorts after upper-case letters
152- {"'ß' == 'SS'", "Boolean:true"},159+ // OrdinalIgnoreCase folds one code point at a time: no ß to SS.
153- {"'ß' == 'ss'", "Boolean:true"},160+ {"'ß' == 'SS'", "Boolean:false"},
161+ {"'ß' == 'ss'", "Boolean:false"},
162+ {"'ß' == 'ß'", "Boolean:true"},
163+ {"'é' == 'É'", "Boolean:true"},
164+ {"'dž' == 'Dž'", "Boolean:true"},
165+ {"'ı' == 'I'", "Boolean:false"},
166+ {"'\u212a' == 'k'", "Boolean:false"}, // Kelvin sign
167+ {"'ſ' == 's'", "Boolean:false"},
168+ {"'ς' == 'σ'", "Boolean:true"}, // both fold to Σ
154 {"'ı' == 'I'", "Boolean:false"}, // dotless i is not upper-cased169 {"'ı' == 'I'", "Boolean:false"}, // dotless i is not upper-cased
155 {"'ı' == 'i'", "Boolean:false"},170 {"'ı' == 'i'", "Boolean:false"},
156 {"'ı' == 'ı'", "Boolean:true"},171 {"'ı' == 'ı'", "Boolean:true"},
@@ -275,7 +290,7 @@ func TestEvalAccess(t *testing.T) {
275 {"steps[10].outputs.r", "String:ten"},290 {"steps[10].outputs.r", "String:ten"},
276 {"steps['10'].outputs.r", "String:ten"},291 {"steps['10'].outputs.r", "String:ten"},
277 // After an identifier, ".10" is not a number: the lexer reads a DOT and then "10.outputs.r" as one number-like word.292 // After an identifier, ".10" is not a number: the lexer reads a DOT and then "10.outputs.r" as one number-like word.
278- {"steps.10.outputs.r", "PARSE Unexpected symbol: '10.outputs.r'. Located at position 7 within expression: steps.10.outputs.r"},293+ {"steps.10.outputs.r", "PARSE Unexpected symbol: '10.outputs.r'"},
279 {"steps[2].outputs.r", "String:two"},294 {"steps[2].outputs.r", "String:two"},
280 {"matrix.obj[0]", "Null:"},295 {"matrix.obj[0]", "Null:"},
281 {"matrix.node['0']", "Number:14"},296 {"matrix.node['0']", "Number:14"},
@@ -313,7 +328,9 @@ func TestEvalFilters(t *testing.T) {
313 {"toJSON(matrix.node.*)", "String:[\n 14,\n 16,\n 18\n]"},328 {"toJSON(matrix.node.*)", "String:[\n 14,\n 16,\n 18\n]"},
314 {"toJSON(matrix.nothing.*)", "String:[]"},329 {"toJSON(matrix.nothing.*)", "String:[]"},
315 {"toJSON(inputs.empty.*)", "String:[]"},330 {"toJSON(inputs.empty.*)", "String:[]"},
316- {"toJSON(steps.*.outputs.r)", "String:[\n \"two\",\n \"ten\"\n]"},331+ // Object members keep the order of the document (the JavaScript
332+ // reference puts array-index keys first).
333+ {"toJSON(steps.*.outputs.r)", "String:[\n \"ten\",\n \"two\"\n]"},
317 {"toJSON(fromJSON('{\"a\":{\"x\":1},\"b\":{\"x\":2}}').*.x)", "String:[\n 1,\n 2\n]"},334 {"toJSON(fromJSON('{\"a\":{\"x\":1},\"b\":{\"x\":2}}').*.x)", "String:[\n 1,\n 2\n]"},
318 {"toJSON(fromJSON('[1,2]').*.x)", "String:[]"},335 {"toJSON(fromJSON('[1,2]').*.x)", "String:[]"},
319 {"toJSON(fromJSON('[{\"a\":[1,2]},{\"a\":[3]}]').*.a.*)", "String:[\n 1,\n 2,\n 3\n]"},336 {"toJSON(fromJSON('[{\"a\":[1,2]},{\"a\":[3]}]').*.a.*)", "String:[\n 1,\n 2,\n 3\n]"},
@@ -343,13 +360,15 @@ func TestEvalBuiltinFunctions(t *testing.T) {
343 {"contains(fromJSON('[1,\"2\",null]'), null)", "Boolean:true"},360 {"contains(fromJSON('[1,\"2\",null]'), null)", "Boolean:true"},
344 {"contains(fromJSON('[1,\"2\",null]'), '')", "Boolean:true"},361 {"contains(fromJSON('[1,\"2\",null]'), '')", "Boolean:true"},
345 {"contains(fromJSON('[[1]]'), fromJSON('[1]'))", "Boolean:false"},362 {"contains(fromJSON('[[1]]'), fromJSON('[1]'))", "Boolean:false"},
346- {"contains('İ', 'i')", "Boolean:true"}, // İ lower-cases to i + U+0307, which contains i363+ {"contains('İ', 'i')", "Boolean:false"}, // OrdinalIgnoreCase: İ is not i
347- {"contains('ß', 'ss')", "Boolean:false"}, // contains lower-cases: no ß folding364+ {"contains('ß', 'ss')", "Boolean:false"}, // no ß folding
365+ {"contains('\u212a', 'k')", "Boolean:false"}, // Kelvin sign
366+ {"contains('Dž', 'dž')", "Boolean:true"},
348 {"contains('A\u03a3', 'a\u03c2')", "Boolean:true"},367 {"contains('A\u03a3', 'a\u03c2')", "Boolean:true"},
349 {"contains('K', 'k')", "Boolean:true"}, // KELVIN SIGN lower-cases to k368 {"contains('K', 'k')", "Boolean:true"}, // KELVIN SIGN lower-cases to k
350- {"contains()", "PARSE Too few parameters supplied: 'contains'"},369+ {"contains()", "PARSE Too few parameters supplied: '('"},
351- {"contains(1)", "PARSE Too few parameters supplied: 'contains'"},370+ {"contains(1)", "PARSE Too few parameters supplied: '('"},
352- {"contains(1, 2, 3)", "PARSE Too many parameters supplied: 'contains'"},371+ {"contains(1, 2, 3)", "PARSE Too many parameters supplied: '('"},
353 372
354 // startsWith, endsWith373 // startsWith, endsWith
355 {"startsWith('abc', 'AB')", "Boolean:true"},374 {"startsWith('abc', 'AB')", "Boolean:true"},
@@ -358,13 +377,13 @@ func TestEvalBuiltinFunctions(t *testing.T) {
358 {"startsWith(123, 12)", "Boolean:true"},377 {"startsWith(123, 12)", "Boolean:true"},
359 {"startsWith(github, 'a')", "Boolean:false"},378 {"startsWith(github, 'a')", "Boolean:false"},
360 {"startsWith('a', github)", "Boolean:false"},379 {"startsWith('a', github)", "Boolean:false"},
361- {"startsWith('ß', 'ss')", "Boolean:true"}, // upper-cases to SS380+ {"startsWith('ß', 'ss')", "Boolean:false"}, // no ß folding
362 {"startsWith('ı', 'I')", "Boolean:false"},381 {"startsWith('ı', 'I')", "Boolean:false"},
363 {"endsWith('abc', 'BC')", "Boolean:true"},382 {"endsWith('abc', 'BC')", "Boolean:true"},
364 {"endsWith('abc', 'abcd')", "Boolean:false"},383 {"endsWith('abc', 'abcd')", "Boolean:false"},
365 {"endsWith(1.50, '5')", "Boolean:true"},384 {"endsWith(1.50, '5')", "Boolean:true"},
366 {"endsWith(1.50, 5)", "Boolean:true"},385 {"endsWith(1.50, 5)", "Boolean:true"},
367- {"endsWith('abc')", "PARSE Too few parameters supplied: 'endsWith'"},386+ {"endsWith('abc')", "PARSE Too few parameters supplied: '('"},
368 {"STARTSWITH('abc', 'a')", "Boolean:true"},387 {"STARTSWITH('abc', 'a')", "Boolean:true"},
369 388
370 // join389 // join
@@ -381,25 +400,25 @@ func TestEvalBuiltinFunctions(t *testing.T) {
381 {"join(fromJSON('[1,2,3]'), null)", "String:123"},400 {"join(fromJSON('[1,2,3]'), null)", "String:123"},
382 {"join(fromJSON('[1,2,3]'), github)", "String:1,2,3"}, // a non-primitive separator is ignored401 {"join(fromJSON('[1,2,3]'), github)", "String:1,2,3"}, // a non-primitive separator is ignored
383 {"join(fromJSON('[null,true,\"a\",1.5,[1],{}]'))", "String:,true,a,1.5,Array,Object"},402 {"join(fromJSON('[null,true,\"a\",1.5,[1],{}]'))", "String:,true,a,1.5,Array,Object"},
384- {"join()", "PARSE Too few parameters supplied: 'join'"},403+ {"join()", "PARSE Too few parameters supplied: '('"},
385- {"join(1, 2, 3)", "PARSE Too many parameters supplied: 'join'"},404+ {"join(1, 2, 3)", "PARSE Too many parameters supplied: '('"},
386 405
387 // toJSON406 // toJSON
388 {"toJSON(null)", "String:null"},407 {"toJSON(null)", "String:null"},
389 {"toJSON(true)", "String:true"},408 {"toJSON(true)", "String:true"},
390 {"toJSON(1)", "String:1"},409 {"toJSON(1)", "String:1"},
391- {"toJSON(NaN)", "String:null"},410+ {"toJSON(NaN)", "String:NaN"},
392- {"toJSON(Infinity)", "String:null"},411+ {"toJSON(Infinity)", "String:Infinity"},
393- {"toJSON(-0)", "String:0"},412+ {"toJSON(-0)", "String:-0"},
394 {"toJSON('x')", "String:\"x\""},413 {"toJSON('x')", "String:\"x\""},
395- {"toJSON(1e21)", "String:1e+21"},414+ {"toJSON(1e21)", "String:1E+21"},
396- {"toJSON(1e-7)", "String:1e-7"},415+ {"toJSON(1e-7)", "String:1E-07"},
397 {"toJSON(0.1)", "String:0.1"},416 {"toJSON(0.1)", "String:0.1"},
398 {"toJSON(fromJSON('[]'))", "String:[]"},417 {"toJSON(fromJSON('[]'))", "String:[]"},
399 {"toJSON(fromJSON('{}'))", "String:{}"},418 {"toJSON(fromJSON('{}'))", "String:{}"},
400 {"toJSON(fromJSON('[[],{}]'))", "String:[\n [],\n {}\n]"},419 {"toJSON(fromJSON('[[],{}]'))", "String:[\n [],\n {}\n]"},
401- {"toJSON(1, 2)", "PARSE Too many parameters supplied: 'toJSON'"},420+ {"toJSON(1, 2)", "PARSE Too many parameters supplied: '('"},
402- {"toJSON()", "PARSE Too few parameters supplied: 'toJSON'"},421+ {"toJSON()", "PARSE Too few parameters supplied: '('"},
403 {"fromJSON(toJSON(github.event.commits[0])).author.name", "String:Ann"},422 {"fromJSON(toJSON(github.event.commits[0])).author.name", "String:Ann"},
404 423
405 // fromJSON424 // fromJSON
@@ -408,11 +427,13 @@ func TestEvalBuiltinFunctions(t *testing.T) {
408 {"fromJSON('\n')", "ERR empty input"},427 {"fromJSON('\n')", "ERR empty input"},
409 {"fromJSON(null)", "ERR empty input"},428 {"fromJSON(null)", "ERR empty input"},
410 {"fromJSON('{')", "ERR Error parsing JSON when evaluating fromJson"},429 {"fromJSON('{')", "ERR Error parsing JSON when evaluating fromJson"},
411- {"fromJSON('[1,]')", "ERR Error parsing JSON when evaluating fromJson"},430+ // Json.NET is lenient: trailing commas, unquoted names, leading zeros,
412- {"fromJSON('{a:1}')", "ERR Error parsing JSON when evaluating fromJson"},431+ // NaN and Infinity and anything after the first value are accepted.
413- {"fromJSON('01')", "ERR Error parsing JSON when evaluating fromJson"},432+ {"fromJSON('[1,]')", "Array:Array"},
414- {"fromJSON('NaN')", "ERR Error parsing JSON when evaluating fromJson"},433+ {"fromJSON('{a:1}').a", "Number:1"},
415- {"fromJSON('1 2')", "ERR Error parsing JSON when evaluating fromJson"},434+ {"fromJSON('01')", "Number:1"},
435+ {"fromJSON('NaN')", "Number:NaN"},
436+ {"fromJSON('1 2')", "Number:1"}, // only the first value is read
416 {"fromJSON(' 1 ')", "Number:1"},437 {"fromJSON(' 1 ')", "Number:1"},
417 {"fromJSON('-1.5e2')", "Number:-150"},438 {"fromJSON('-1.5e2')", "Number:-150"},
418 {"fromJSON('1e999')", "Number:Infinity"},439 {"fromJSON('1e999')", "Number:Infinity"},
@@ -421,7 +442,7 @@ func TestEvalBuiltinFunctions(t *testing.T) {
421 {"fromJSON(1)", "Number:1"},442 {"fromJSON(1)", "Number:1"},
422 {"fromJSON(true)", "Boolean:true"},443 {"fromJSON(true)", "Boolean:true"},
423 {"fromJSON(matrix.node)", "ERR Error parsing JSON when evaluating fromJson"}, // the string "Array"444 {"fromJSON(matrix.node)", "ERR Error parsing JSON when evaluating fromJson"}, // the string "Array"
424- {"fromJSON(1, 2)", "PARSE Too many parameters supplied: 'fromJSON'"},445+ {"fromJSON(1, 2)", "PARSE Too many parameters supplied: '('"},
425 446
426 // format447 // format
427 {"format('a')", "String:a"},448 {"format('a')", "String:a"},
@@ -437,8 +458,18 @@ func TestEvalBuiltinFunctions(t *testing.T) {
437 {"format('{{0}}', 1)", "String:{0}"},458 {"format('{{0}}', 1)", "String:{0}"},
438 {"format('{{{0}}}', 1)", "String:{1}"},459 {"format('{{{0}}}', 1)", "String:{1}"},
439 {"format('{0}', 'a', 'b')", "String:a"}, // extra arguments are fine460 {"format('{0}', 'a', 'b')", "String:a"}, // extra arguments are fine
440- {"format('{0:x}', 1)", "String:1"}, // a format specifier is skipped461+ // The runner rejects format specifiers (Format.cs) and anything but
441- {"format('{0 anything}', 1)", "String:1"},462+ // digits between the index and the closing brace; the JavaScript
463+ // reference skips them.
464+ {"format('{0:x}', 1)", "ERR The format specifiers 'x' are not valid for objects of type 'Number'"},
465+ {"format('{0:}', 1)", "String:1"},
466+ {"format('{0:x}}y}', true)", "ERR The format specifiers 'x}y' are not valid for objects of type 'Boolean'"},
467+ {"format('{0 anything}', 1)", "ERR The following format string is invalid: {0 anything}"},
468+ {"format('{0 }', 1)", "ERR The following format string is invalid: {0 }"},
469+ {"format('{0,5}', 1)", "ERR The following format string is invalid: {0,5}"},
470+ {"format('{255}', 1)", "ERR The following format string references more arguments than were supplied: {255}"},
471+ {"format('{256}', 1)", "ERR The following format string is invalid: {256}"},
472+ {"format('{00}', 7)", "String:7"},
442 {"format('{00}', 1)", "String:1"},473 {"format('{00}', 1)", "String:1"},
443 {"format('{01}', 1, 2)", "String:2"},474 {"format('{01}', 1, 2)", "String:2"},
444 {"format('{0}', null)", "String:"},475 {"format('{0}', null)", "String:"},
@@ -460,8 +491,9 @@ func TestEvalBuiltinFunctions(t *testing.T) {
460 {"format('0}', 1)", "ERR The following format string is invalid: 0}"},491 {"format('0}', 1)", "ERR The following format string is invalid: 0}"},
461 {"format('{{0}', 1)", "ERR The following format string is invalid: {{0}"},492 {"format('{{0}', 1)", "ERR The following format string is invalid: {{0}"},
462 {"format('{0}}', 1)", "ERR The following format string is invalid: {0}}"},493 {"format('{0}}', 1)", "ERR The following format string is invalid: {0}}"},
463- {"format('{12345678901234567890123}', 1)", "ERR The following format string references more arguments than were supplied: {12345678901234567890123}"},494+ // The index is a byte: a longer number makes the string invalid.
464- {"format()", "PARSE Too few parameters supplied: 'format'"},495+ {"format('{12345678901234567890123}', 1)", "ERR The following format string is invalid: {12345678901234567890123}"},
496+ {"format()", "PARSE Too few parameters supplied: '('"},
465 497
466 // case498 // case
467 {"case(true, 'a', 'b')", "String:a"},499 {"case(true, 'a', 'b')", "String:a"},
@@ -474,9 +506,9 @@ func TestEvalBuiltinFunctions(t *testing.T) {
474 {"case(1, 2, 3)", "ERR case predicate must evaluate to a boolean value"},506 {"case(1, 2, 3)", "ERR case predicate must evaluate to a boolean value"},
475 {"case('true', 2, 3)", "ERR case predicate must evaluate to a boolean value"},507 {"case('true', 2, 3)", "ERR case predicate must evaluate to a boolean value"},
476 {"case(null, 2, 3)", "ERR case predicate must evaluate to a boolean value"},508 {"case(null, 2, 3)", "ERR case predicate must evaluate to a boolean value"},
477- {"case(true, 1, 2, 3)", "PARSE Even number of parameters supplied, requires an odd number of parameters: 'case'"},509+ {"case(true, 1, 2, 3)", "PARSE Even number of parameters supplied, requires an odd number of parameters: '('"},
478- {"case(true, 1)", "PARSE Too few parameters supplied: 'case'"},510+ {"case(true, 1)", "PARSE Too few parameters supplied: '('"},
479- {"case()", "PARSE Too few parameters supplied: 'case'"},511+ {"case()", "PARSE Too few parameters supplied: '('"},
480 // Predicates after the first true one are not checked...512 // Predicates after the first true one are not checked...
481 {"case(true, 1, 'bad', 2, 3)", "Number:1"},513 {"case(true, 1, 'bad', 2, 3)", "Number:1"},
482 // ...but earlier ones are.514 // ...but earlier ones are.
@@ -491,7 +523,7 @@ func TestEvalErrorMessages(t *testing.T) {
491 {"", "PARSE Unexpected end of expression: 'x'"}, // replaced below: the empty expression is not an error523 {"", "PARSE Unexpected end of expression: 'x'"}, // replaced below: the empty expression is not an error
492 }[:0])524 }[:0])
493 cases := []evalCase{525 cases := []evalCase{
494- {"1 +", "PARSE Unexpected symbol: '+'. Located at position 3 within expression: 1 +"},526+ {"1 +", "PARSE Unexpected symbol: '+'"},
495 {"a b", "PARSE Unrecognized named-value: 'a'"},527 {"a b", "PARSE Unrecognized named-value: 'a'"},
496 {"github github", "PARSE Unexpected symbol: 'github'"},528 {"github github", "PARSE Unexpected symbol: 'github'"},
497 {"github.event_name ==", "PARSE Unexpected end of expression: '=='"},529 {"github.event_name ==", "PARSE Unexpected end of expression: '=='"},
@@ -499,9 +531,9 @@ func TestEvalErrorMessages(t *testing.T) {
499 {"(github.event_name", "PARSE Unexpected end of expression: 'event_name'"},531 {"(github.event_name", "PARSE Unexpected end of expression: 'event_name'"},
500 {"github.event_name)", "PARSE Unexpected symbol: ')'"},532 {"github.event_name)", "PARSE Unexpected symbol: ')'"},
501 {"contains('a',", "PARSE Unexpected end of expression: ','"},533 {"contains('a',", "PARSE Unexpected end of expression: ','"},
502- {"contains('a'", "PARSE Unexpected symbol: 'EOF'"},534+ {"contains('a'", "PARSE Unexpected end of expression: ''a''"},
503- {"contains('a' 'b')", "PARSE Unexpected symbol: 'b'"},535+ {"contains('a' 'b')", "PARSE Unexpected symbol: ''b''"},
504- {"foo(", "PARSE Unexpected end of expression: '('"},536+ {"foo(", "PARSE Unrecognized function: 'foo'"},
505 {"foo()", "PARSE Unrecognized function: 'foo'"},537 {"foo()", "PARSE Unrecognized function: 'foo'"},
506 {"foo", "PARSE Unrecognized named-value: 'foo'"},538 {"foo", "PARSE Unrecognized named-value: 'foo'"},
507 {"unknown(1)", "PARSE Unrecognized function: 'unknown'"},539 {"unknown(1)", "PARSE Unrecognized function: 'unknown'"},
@@ -513,22 +545,22 @@ func TestEvalErrorMessages(t *testing.T) {
513 {"1 !", "PARSE Unexpected symbol: '!'"},545 {"1 !", "PARSE Unexpected symbol: '!'"},
514 {"[1]", "PARSE Unexpected symbol: '['"},546 {"[1]", "PARSE Unexpected symbol: '['"},
515 {"github.[0]", "PARSE Unexpected symbol: '['"},547 {"github.[0]", "PARSE Unexpected symbol: '['"},
516- {"github.", "PARSE Unexpected symbol: 'EOF'"},548+ {"github.", "PARSE Unexpected end of expression: '.'"},
517 {"github[", "PARSE Unexpected end of expression: '['"},549 {"github[", "PARSE Unexpected end of expression: '['"},
518 {"github[]", "PARSE Unexpected symbol: ']'"},550 {"github[]", "PARSE Unexpected symbol: ']'"},
519- {"github[*", "PARSE Unexpected symbol: 'EOF'"},551+ {"github[*", "PARSE Unexpected end of expression: '*'"},
520 {"github.true", "Null:"}, // keywords are plain identifiers after a dot552 {"github.true", "Null:"}, // keywords are plain identifiers after a dot
521 {"env.true", "Null:"},553 {"env.true", "Null:"},
522- {"1 = 1", "PARSE Unexpected symbol: '='. Located at position 3 within expression: 1 = 1"},554+ {"1 = 1", "PARSE Unexpected symbol: '='"},
523- {"1 & 1", "PARSE Unexpected symbol: '&'. Located at position 3 within expression: 1 & 1"},555+ {"1 & 1", "PARSE Unexpected symbol: '&'"},
524- {"1 | 1", "PARSE Unexpected symbol: '|'. Located at position 3 within expression: 1 | 1"},556+ {"1 | 1", "PARSE Unexpected symbol: '|'"},
525- {"1 + 1", "PARSE Unexpected symbol: '+'. Located at position 3 within expression: 1 + 1"},557+ {"1 + 1", "PARSE Unexpected symbol: '+'"},
526 {"a-b", "PARSE Unrecognized named-value: 'a-b'"},558 {"a-b", "PARSE Unrecognized named-value: 'a-b'"},
527- {"1a", "PARSE Unexpected symbol: '1a'. Located at position 1 within expression: 1a"},559+ {"1a", "PARSE Unexpected symbol: '1a'"},
528- {"'unterminated", "PARSE Unexpected symbol: ''unterminated'. Located at position 1 within expression: 'unterminated"},560+ {"'unterminated", "PARSE Unexpected symbol: ''unterminated'"},
529- {"@", "PARSE Unexpected symbol: '@'. Located at position 1 within expression: @"},561+ {"@", "PARSE Unexpected symbol: '@'"},
530- {"${{ 1 }}", "PARSE Unexpected symbol: '${{'. Located at position 1 within expression: ${{ 1 }}"},562+ {"${{ 1 }}", "PARSE Unexpected symbol: '${{'"},
531- {"1 \u00a0 2", "PARSE Unexpected symbol: '\u00a0'. Located at position 3 within expression: 1 \u00a0 2"},563+ {"1 \u00a0 2", "PARSE Unexpected symbol: '2'"}, // U+00A0 is white space for .NET
532 }564 }
533 runEvalCases(t, cases)565 runEvalCases(t, cases)
534 }566 }
@@ -545,6 +577,9 @@ func TestEmptyExpression(t *testing.T) {
545 }577 }
546 }578 }
547 579
580+// TestParseErrorPositions: the runner (ParseException) reports the absolute
581+// index of the token in the expression, 1-based, and the whole expression;
582+// Error.Pos is the same place as a line and a column.
548 func TestParseErrorPositions(t *testing.T) {583 func TestParseErrorPositions(t *testing.T) {
549 tests := []struct {584 tests := []struct {
550 src string585 src string
@@ -552,14 +587,16 @@ func TestParseErrorPositions(t *testing.T) {
552 msg string587 msg string
553 }{588 }{
554 {"1 ==\n 2 ===\n 3", 1, 5, "Unexpected symbol: '='. Located at position 11 within expression: 1 ==\n 2 ===\n 3"},589 {"1 ==\n 2 ===\n 3", 1, 5, "Unexpected symbol: '='. Located at position 11 within expression: 1 ==\n 2 ===\n 3"},
555- {"true &&\n false ||\n nope", 2, 2, "Unrecognized named-value: 'nope'"},590+ {"true &&\n false ||\n nope", 2, 2, "Unrecognized named-value: 'nope'. Located at position 22 within expression: true &&\n false ||\n nope"},
556- {"\n\n bogus", 2, 2, "Unrecognized named-value: 'bogus'"},591+ {"\n\n bogus", 2, 2, "Unrecognized named-value: 'bogus'. Located at position 5 within expression: \n\n bogus"},
557- {"github github", 0, 7, "Unexpected symbol: 'github'"},592+ {"github github", 0, 7, "Unexpected symbol: 'github'. Located at position 8 within expression: github github"},
558- {" ) ", 0, 2, "Unexpected symbol: ')'"},593+ {" ) ", 0, 2, "Unexpected symbol: ')'. Located at position 3 within expression: ) "},
559- {"contains('a',", 0, 12, "Unexpected end of expression: ','"},594+ {"contains('a',", 0, 12, "Unexpected end of expression: ','. Located at position 13 within expression: contains('a',"},
560- {"foo()", 0, 0, "Unrecognized function: 'foo'"},595+ {"foo()", 0, 0, "Unrecognized function: 'foo'. Located at position 1 within expression: foo()"},
561- {"1 && foo()", 0, 5, "Unrecognized function: 'foo'"},596+ {"1 && foo()", 0, 5, "Unrecognized function: 'foo'. Located at position 6 within expression: 1 && foo()"},
562- {"'a'\n 'b'\n zzz", 1, 1, "Unexpected symbol: 'b'"},597+ {"'a'\n 'b'\n zzz", 1, 1, "Unexpected symbol: ''b''. Located at position 6 within expression: 'a'\n 'b'\n zzz"},
598+ {"contains()", 0, 8, "Too few parameters supplied: '('. Located at position 9 within expression: contains()"},
599+ {"1 2", 0, 2, "Unexpected symbol: '2'. Located at position 3 within expression: 1 2"},
563 }600 }
564 for _, tc := range tests {601 for _, tc := range tests {
565 _, err := Parse(tc.src, testContextNames, nil)602 _, err := Parse(tc.src, testContextNames, nil)
@@ -586,7 +623,7 @@ func TestParseErrorTypes(t *testing.T) {
586 {"contains(1)", ErrorTooFewParameters},623 {"contains(1)", ErrorTooFewParameters},
587 {"contains(1,2,3)", ErrorTooManyParameters},624 {"contains(1,2,3)", ErrorTooManyParameters},
588 {"case(true, 1, 2, 3)", ErrorEvenParameters},625 {"case(true, 1, 2, 3)", ErrorEvenParameters},
589- {strings.Repeat("(", 60) + "1" + strings.Repeat(")", 60), ErrorExceededMaxDepth},626+ {strings.Repeat("!", 60) + "1", ErrorExceededMaxDepth},
590 {"1 = 1", ErrorUnexpectedSymbol},627 {"1 = 1", ErrorUnexpectedSymbol},
591 }628 }
592 for _, tc := range tests {629 for _, tc := range tests {
@@ -598,6 +635,10 @@ func TestParseErrorTypes(t *testing.T) {
598 }635 }
599 }636 }
600 637
638+// TestLimits: the expectations were established with the runner's own parser.
639+// Depth is that of the finished tree (CheckMaxDepth): the root is 1, parentheses
640+// add nothing, a run of && or || is a single node, a call adds one level for
641+// its arguments, "a.b" and "a[b]" one level each, and "!" one.
601 func TestLimits(t *testing.T) {642 func TestLimits(t *testing.T) {
602 deepParens := func(n int) string { return strings.Repeat("(", n) + "1" + strings.Repeat(")", n) }643 deepParens := func(n int) string { return strings.Repeat("(", n) + "1" + strings.Repeat(")", n) }
603 tests := []struct {644 tests := []struct {
@@ -605,9 +646,7 @@ func TestLimits(t *testing.T) {
605 src string646 src string
606 ok bool647 ok bool
607 }{648 }{
608- // Each "(" costs one level, plus one for the outermost expression.649+ {"parentheses do not count", deepParens(500), true},
609- {"49 parens", deepParens(49), true},
610- {"50 parens", deepParens(50), false},
611 {"49 nots", strings.Repeat("!", 49) + "true", true},650 {"49 nots", strings.Repeat("!", 49) + "true", true},
612 {"50 nots", strings.Repeat("!", 50) + "true", false},651 {"50 nots", strings.Repeat("!", 50) + "true", false},
613 {"49 calls", strings.Repeat("format(", 49) + "'x'" + strings.Repeat(")", 49), true},652 {"49 calls", strings.Repeat("format(", 49) + "'x'" + strings.Repeat(")", 49), true},
@@ -615,7 +654,9 @@ func TestLimits(t *testing.T) {
615 {"long dotted path", "github" + strings.Repeat(".event", 49), true},654 {"long dotted path", "github" + strings.Repeat(".event", 49), true},
616 {"too long dotted path", "github" + strings.Repeat(".event", 50), false},655 {"too long dotted path", "github" + strings.Repeat(".event", 50), false},
617 {"long flat ||", "1" + strings.Repeat(" || 1", 3000), true},656 {"long flat ||", "1" + strings.Repeat(" || 1", 3000), true},
618- {"long flat ==", "1" + strings.Repeat(" == 1", 3000), true},657+ {"nested || is flat", "1" + strings.Repeat(" || (1", 60) + strings.Repeat(")", 60), true},
658+ {"49 ==", "1" + strings.Repeat(" == 1", 49), true},
659+ {"50 ==", "1" + strings.Repeat(" == 1", 50), false},
619 }660 }
620 for _, tc := range tests {661 for _, tc := range tests {
621 _, err := Parse(tc.src, testContextNames, nil)662 _, err := Parse(tc.src, testContextNames, nil)
@@ -623,18 +664,18 @@ func TestLimits(t *testing.T) {
623 t.Errorf("%s: err = %v, want ok=%v", tc.name, err, tc.ok)664 t.Errorf("%s: err = %v, want ok=%v", tc.name, err, tc.ok)
624 }665 }
625 var pe *Error666 var pe *Error
626- if err != nil && errors.As(err, &pe) && pe.Type == ErrorExceededMaxDepth && pe.Message != "Exceeded max expression depth 50: '1'" && !strings.HasPrefix(pe.Message, "Exceeded max expression depth 50: '") {667+ if err != nil && errors.As(err, &pe) && pe.Type == ErrorExceededMaxDepth && pe.Message != "Exceeded max expression depth 50" {
627 t.Errorf("%s: message %q", tc.name, pe.Message)668 t.Errorf("%s: message %q", tc.name, pe.Message)
628 }669 }
629 }670 }
630 671
631- // Length: counted in UTF-16 code units, error is the bare token.672+ // Length: counted in UTF-16 code units, no token in the message.
632 long := "'" + strings.Repeat("a", MaxExpressionLength-2) + "'"673 long := "'" + strings.Repeat("a", MaxExpressionLength-2) + "'"
633 if _, err := Parse(long, nil, nil); err != nil {674 if _, err := Parse(long, nil, nil); err != nil {
634 t.Errorf("expression of exactly %d characters: %v", MaxExpressionLength, err)675 t.Errorf("expression of exactly %d characters: %v", MaxExpressionLength, err)
635 }676 }
636 _, err := Parse(long+" ", nil, nil)677 _, err := Parse(long+" ", nil, nil)
637- if err == nil || err.Error() != "ErrorExceededMaxLength" {678+ if err == nil || err.Error() != "Exceeded max expression length 21000" {
638 t.Errorf("expression of %d characters: err = %v", MaxExpressionLength+1, err)679 t.Errorf("expression of %d characters: err = %v", MaxExpressionLength+1, err)
639 }680 }
640 // 10501 emoji are 21002 UTF-16 units but only 10501 runes.681 // 10501 emoji are 21002 UTF-16 units but only 10501 runes.
@@ -649,8 +690,8 @@ func TestLimits(t *testing.T) {
649 }690 }
650 691
651 func TestDepthMessage(t *testing.T) {692 func TestDepthMessage(t *testing.T) {
652- _, err := Parse(strings.Repeat("(", 60)+"1"+strings.Repeat(")", 60), nil, nil)693+ _, err := Parse(strings.Repeat("!", 60)+"1", nil, nil)
653- want := "Exceeded max expression depth 50: '('"694+ want := "Exceeded max expression depth 50"
654 if err == nil || err.Error() != want {695 if err == nil || err.Error() != want {
655 t.Errorf("got %v, want %q", err, want)696 t.Errorf("got %v, want %q", err, want)
656 }697 }
@@ -666,19 +707,23 @@ func TestExtensionFunctions(t *testing.T) {
666 for _, c := range []struct{ src, err string }{707 for _, c := range []struct{ src, err string }{
667 {"hashFiles('a')", ""},708 {"hashFiles('a')", ""},
668 {"HASHFILES('a', 'b', 'c', 'd')", ""},709 {"HASHFILES('a', 'b', 'c', 'd')", ""},
669- {"hashFiles()", "Too few parameters supplied: 'hashFiles'"},710+ {"hashFiles()", "Too few parameters supplied: '('"},
670 {"success()", ""},711 {"success()", ""},
671- {"success(1)", "Too many parameters supplied: 'success'"},712+ {"success(1)", "Too many parameters supplied: '('"},
672 {"bounded(1, 2)", ""},713 {"bounded(1, 2)", ""},
673- {"bounded(1, 2, 3)", "Too many parameters supplied: 'bounded'"},714+ {"bounded(1, 2, 3)", "Too many parameters supplied: '('"},
674- {"contains()", "Too few parameters supplied: 'contains'"},715+ {"contains()", "Too few parameters supplied: '('"},
675 {"nothere()", "Unrecognized function: 'nothere'"},716 {"nothere()", "Unrecognized function: 'nothere'"},
676 } {717 } {
677 _, err := Parse(c.src, nil, funcs)718 _, err := Parse(c.src, nil, funcs)
719+ msg := ""
720+ if err != nil {
721+ msg, _, _ = strings.Cut(err.Error(), ". Located at position ")
722+ }
678 switch {723 switch {
679 case c.err == "" && err != nil:724 case c.err == "" && err != nil:
680 t.Errorf("%s: %v", c.src, err)725 t.Errorf("%s: %v", c.src, err)
681- case c.err != "" && (err == nil || err.Error() != c.err):726+ case c.err != "" && (err == nil || msg != c.err):
682 t.Errorf("%s: err = %v, want %q", c.src, err, c.err)727 t.Errorf("%s: err = %v, want %q", c.src, err, c.err)
683 }728 }
684 }729 }
@@ -693,7 +738,7 @@ func TestExtensionFunctions(t *testing.T) {
693 },738 },
694 "success": func(context.Context, []Value) (Value, error) { return Boolean(true), nil },739 "success": func(context.Context, []Value) (Value, error) { return Boolean(true), nil },
695 "boom": func(context.Context, []Value) (Value, error) { return nil, errors.New("kaput") },740 "boom": func(context.Context, []Value) (Value, error) { return nil, errors.New("kaput") },
696- // An extra function replaces a built-in of the same name.741+ // An extra function does not replace a built-in of the same name.
697 "contains": func(context.Context, []Value) (Value, error) { return String("mine"), nil },742 "contains": func(context.Context, []Value) (Value, error) { return String("mine"), nil },
698 },743 },
699 }744 }
@@ -702,7 +747,7 @@ func TestExtensionFunctions(t *testing.T) {
702 t.Fatal(err)747 t.Fatal(err)
703 }748 }
704 v, err := Evaluate(context.Background(), node, ctx)749 v, err := Evaluate(context.Background(), node, ctx)
705- if err != nil || CoerceString(v) != "mine" || calls != 1 {750+ if err != nil || CoerceString(v) != "false" || calls != 1 {
706 t.Errorf("got %v, %v, %d calls", v, err, calls)751 t.Errorf("got %v, %v, %d calls", v, err, calls)
707 }752 }
708 node, err = Parse("boom()", nil, []FuncInfo{{Name: "boom"}})753 node, err = Parse("boom()", nil, []FuncInfo{{Name: "boom"}})
@@ -738,7 +783,7 @@ func TestContextNames(t *testing.T) {
738 t.Errorf("absent context: %v, %v", v, err)783 t.Errorf("absent context: %v, %v", v, err)
739 }784 }
740 // Names that were not declared to Parse are errors, even if present at evaluation.785 // Names that were not declared to Parse are errors, even if present at evaluation.
741- if _, err := Parse("secrets.X", []string{"github"}, nil); err == nil || err.Error() != "Unrecognized named-value: 'secrets'" {786+ if _, err := Parse("secrets.X", []string{"github"}, nil); err == nil || !strings.HasPrefix(err.Error(), "Unrecognized named-value: 'secrets'. Located at position 1 ") {
742 t.Errorf("undeclared context: %v", err)787 t.Errorf("undeclared context: %v", err)
743 }788 }
744 // A nil Value in a context behaves as null.789 // A nil Value in a context behaves as null.
@@ -782,13 +827,18 @@ func TestEvaluateDoesNotMutateContext(t *testing.T) {
782 }827 }
783 }828 }
784 829
830+// TestFloatCoercion checks the string form of numbers against the C# runner:
831+// Double.ToString("G15", InvariantCulture) (ExpressionConstants.NumberFormat,
832+// EvaluationResult.ConvertToString). Each expectation was obtained by running
833+// the runner's own code (gen/runner, --g15), and numberToCoercedString agrees
834+// with it on 200 000 random doubles.
785 func TestFloatCoercion(t *testing.T) {835 func TestFloatCoercion(t *testing.T) {
786 tests := []struct {836 tests := []struct {
787 in float64837 in float64
788 want string838 want string
789 }{839 }{
790 {0, "0"},840 {0, "0"},
791- {math.Copysign(0, -1), "0"},841+ {math.Copysign(0, -1), "-0"}, // as the legacy engine of the runner
792 {1, "1"},842 {1, "1"},
793 {-1, "-1"},843 {-1, "-1"},
794 {1.5, "1.5"},844 {1.5, "1.5"},
@@ -796,24 +846,34 @@ func TestFloatCoercion(t *testing.T) {
796 {0.30000000000000004, "0.3"},846 {0.30000000000000004, "0.3"},
797 {1.005, "1.005"},847 {1.005, "1.005"},
798 {100, "100"},848 {100, "100"},
799- {1e20, "100000000000000000000"},849+ {123456789012345, "123456789012345"},
800- {1e21, "1e+21"},850+ {999999999999999, "999999999999999"},
801- {123456789012345680000, "123456789012345680000"},851+ {1e14, "100000000000000"},
802- {1e-7, "1e-7"}, // toFixed(15) keeps 0.000000100000000, which prints as 1e-7852+ {1e15, "1E+15"},
803- {0.000001, "0.000001"},853+ {1e16, "1E+16"},
804- {5e-16, "1e-15"}, // toFixed rounds half up on the exact binary value854+ {1e20, "1E+20"},
805- {1e-15, "1e-15"},855+ {1e21, "1E+21"},
806- {1e-16, "0"},856+ {123456789012345680000, "1.23456789012346E+20"},
807- {-1e-16, "0"},857+ {1234567890123456, "1.23456789012346E+15"},
808- {12345678901234567890, "12345678901234567000"},858+ {0.0001, "0.0001"},
859+ {0.00001, "1E-05"},
860+ {1.5e-5, "1.5E-05"},
861+ {1e-6, "1E-06"},
862+ {1e-7, "1E-07"},
863+ {5e-16, "5E-16"},
864+ {-1e-16, "-1E-16"},
865+ {9.999999999999995e-5, "0.0001"},
866+ {99999999999999.95, "100000000000000"},
867+ {999999999999999.5, "1E+15"},
868+ {123456789.123456789, "123456789.123457"},
869+ {123456789012345.5, "123456789012346"},
809 {math.NaN(), "NaN"},870 {math.NaN(), "NaN"},
810 {math.Inf(1), "Infinity"},871 {math.Inf(1), "Infinity"},
811 {math.Inf(-1), "-Infinity"},872 {math.Inf(-1), "-Infinity"},
812 {2.5, "2.5"},873 {2.5, "2.5"},
813 {-0.25, "-0.25"},874 {-0.25, "-0.25"},
814- {4503599627370497.5, "4503599627370498"}, // the literal itself rounds to the nearest double875+ {1.7976931348623157e308, "1.79769313486232E+308"},
815- {1.7976931348623157e308, "1.7976931348623157e+308"},876+ {5e-324, "4.94065645841247E-324"},
816- {5e-324, "0"},
817 }877 }
818 for _, tc := range tests {878 for _, tc := range tests {
819 if got := CoerceString(Number(tc.in)); got != tc.want {879 if got := CoerceString(Number(tc.in)); got != tc.want {
@@ -841,28 +901,6 @@ func TestJSNumberToString(t *testing.T) {
841 }901 }
842 }902 }
843 903
844-func TestJSToNumber(t *testing.T) {
845- nan := math.NaN()
846- tests := []struct {
847- in string
848- want float64
849- }{
850- {"", 0}, {" ", 0}, {"\n\t", 0}, {"1", 1}, {" 1 ", 1}, {"\u00a01\u2003", 1}, {"\ufeff1", 1}, {"+1", 1}, {"-1", -1},
851- {"1.", 1}, {".5", 0.5}, {"-.5", -0.5}, {"1e3", 1000}, {"1E-3", 0.001}, {"1e+3", 1000}, {"0x10", 16}, {"0X1f", 31},
852- {"0o17", 15}, {"0b101", 5}, {"0x", nan}, {"0xg", nan}, {"-0x10", nan}, {"+0x10", nan}, {"0b2", nan}, {"1_000", nan},
853- {"Infinity", math.Inf(1)}, {"-Infinity", math.Inf(-1)}, {"+Infinity", math.Inf(1)}, {"infinity", nan}, {"inf", nan},
854- {"NaN", nan}, {"nan", nan}, {"abc", nan}, {"1e", nan}, {"1e+", nan}, {".", nan}, {"e5", nan}, {"1 2", nan}, {"1,5", nan},
855- {"00", 0}, {"007", 7}, {"0.0", 0}, {"1e999", math.Inf(1)}, {"-1e999", math.Inf(-1)}, {"0x1fffffffffffff", 9007199254740991},
856- {"12345678901234567890", 12345678901234567890}, {"٣", nan}, {"--1", nan}, {"+-1", nan}, {"1e1.5", nan}, {"0x1.8", nan},
857- }
858- for _, tc := range tests {
859- got := jsToNumber(tc.in)
860- if got != tc.want && !(math.IsNaN(got) && math.IsNaN(tc.want)) {
861- t.Errorf("jsToNumber(%q) = %v, want %v", tc.in, got, tc.want)
862- }
863- }
864-}
865-
866 func TestJSCasing(t *testing.T) {904 func TestJSCasing(t *testing.T) {
867 upper := map[string]string{"abc": "ABC", "ß": "SS", "ʼn": "ʼN", "dž": "DŽ", "ı": "I", "ffi": "FFI", "ὒ": "Υ̓̀", "ᾀ": "ἈΙ", "σ": "Σ", "ς": "Σ", "😀": "😀", "é": "É"}905 upper := map[string]string{"abc": "ABC", "ß": "SS", "ʼn": "ʼN", "dž": "DŽ", "ı": "I", "ffi": "FFI", "ὒ": "Υ̓̀", "ᾀ": "ἈΙ", "σ": "Σ", "ς": "Σ", "😀": "😀", "é": "É"}
868 for in, want := range upper {906 for in, want := range upper {
@@ -912,9 +950,12 @@ func TestCompareUTF16(t *testing.T) {
912 }950 }
913 951
914 var evalCasesUTF16 = []evalCase{952 var evalCasesUTF16 = []evalCase{
915- {"'\U00010000' < '\ue000'", "Boolean:true"},953+ // The JavaScript reference compares UTF-16 units, where U+10000 (D800 DC00)
916- {"'\ue000' > '\U00010000'", "Boolean:true"},954+ // sorts before U+E000. The runner compares code points: it sorts after.
917- {"'\U00010000' >= '\ue000'", "Boolean:false"},955+ {"'\U00010000' < '\ue000'", "Boolean:false"},
956+ {"'\ue000' > '\U00010000'", "Boolean:false"},
957+ {"'\U00010000' >= '\ue000'", "Boolean:true"},
958+ {"'\ue000' < '\U00010000'", "Boolean:true"},
918 }959 }
919 960
920 func TestTokenString(t *testing.T) {961 func TestTokenString(t *testing.T) {
M expr/example_test.go +4 -4
@@ -27,10 +27,10 @@ func ExampleParse() {
27 }27 }
28 // Output:28 // Output:
29 // true <nil>29 // true <nil>
30- // secrets.TOKEN -> Unrecognized named-value: 'secrets' (line 0, column 0)30+ // secrets.TOKEN -> Unrecognized named-value: 'secrets'. Located at position 1 within expression: secrets.TOKEN (line 0, column 0)
31- // contains('a') -> Too few parameters supplied: 'contains' (line 0, column 0)31+ // contains('a') -> Too few parameters supplied: '('. Located at position 9 within expression: contains('a') (line 0, column 8)
32- // github.ref == -> Unexpected end of expression: '==' (line 0, column 11)32+ // github.ref == -> Unexpected end of expression: '=='. Located at position 12 within expression: github.ref == (line 0, column 11)
33- // nope() -> Unrecognized function: 'nope' (line 0, column 0)33+ // nope() -> Unrecognized function: 'nope'. Located at position 1 within expression: nope() (line 0, column 0)
34 }34 }
35 35
36 func ExampleEvaluate() {36 func ExampleEvaluate() {
A expr/fold_tables.go +1466 -0
@@ -0,0 +1,1466 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by gen/fold.go from `runner.dll --fold` (gen/runner); DO NOT EDIT.
4+
5+package expr
6+
7+// ordinalUpper maps a code point to the one that .NET's OrdinalIgnoreCase comparison treats
8+// it as, sorted by the first element. Code points that are not listed stand for themselves.
9+var ordinalUpper = [...][2]rune{
10+ {0x0061, 0x0041},
11+ {0x0062, 0x0042},
12+ {0x0063, 0x0043},
13+ {0x0064, 0x0044},
14+ {0x0065, 0x0045},
15+ {0x0066, 0x0046},
16+ {0x0067, 0x0047},
17+ {0x0068, 0x0048},
18+ {0x0069, 0x0049},
19+ {0x006A, 0x004A},
20+ {0x006B, 0x004B},
21+ {0x006C, 0x004C},
22+ {0x006D, 0x004D},
23+ {0x006E, 0x004E},
24+ {0x006F, 0x004F},
25+ {0x0070, 0x0050},
26+ {0x0071, 0x0051},
27+ {0x0072, 0x0052},
28+ {0x0073, 0x0053},
29+ {0x0074, 0x0054},
30+ {0x0075, 0x0055},
31+ {0x0076, 0x0056},
32+ {0x0077, 0x0057},
33+ {0x0078, 0x0058},
34+ {0x0079, 0x0059},
35+ {0x007A, 0x005A},
36+ {0x00B5, 0x039C},
37+ {0x00E0, 0x00C0},
38+ {0x00E1, 0x00C1},
39+ {0x00E2, 0x00C2},
40+ {0x00E3, 0x00C3},
41+ {0x00E4, 0x00C4},
42+ {0x00E5, 0x00C5},
43+ {0x00E6, 0x00C6},
44+ {0x00E7, 0x00C7},
45+ {0x00E8, 0x00C8},
46+ {0x00E9, 0x00C9},
47+ {0x00EA, 0x00CA},
48+ {0x00EB, 0x00CB},
49+ {0x00EC, 0x00CC},
50+ {0x00ED, 0x00CD},
51+ {0x00EE, 0x00CE},
52+ {0x00EF, 0x00CF},
53+ {0x00F0, 0x00D0},
54+ {0x00F1, 0x00D1},
55+ {0x00F2, 0x00D2},
56+ {0x00F3, 0x00D3},
57+ {0x00F4, 0x00D4},
58+ {0x00F5, 0x00D5},
59+ {0x00F6, 0x00D6},
60+ {0x00F8, 0x00D8},
61+ {0x00F9, 0x00D9},
62+ {0x00FA, 0x00DA},
63+ {0x00FB, 0x00DB},
64+ {0x00FC, 0x00DC},
65+ {0x00FD, 0x00DD},
66+ {0x00FE, 0x00DE},
67+ {0x00FF, 0x0178},
68+ {0x0101, 0x0100},
69+ {0x0103, 0x0102},
70+ {0x0105, 0x0104},
71+ {0x0107, 0x0106},
72+ {0x0109, 0x0108},
73+ {0x010B, 0x010A},
74+ {0x010D, 0x010C},
75+ {0x010F, 0x010E},
76+ {0x0111, 0x0110},
77+ {0x0113, 0x0112},
78+ {0x0115, 0x0114},
79+ {0x0117, 0x0116},
80+ {0x0119, 0x0118},
81+ {0x011B, 0x011A},
82+ {0x011D, 0x011C},
83+ {0x011F, 0x011E},
84+ {0x0121, 0x0120},
85+ {0x0123, 0x0122},
86+ {0x0125, 0x0124},
87+ {0x0127, 0x0126},
88+ {0x0129, 0x0128},
89+ {0x012B, 0x012A},
90+ {0x012D, 0x012C},
91+ {0x012F, 0x012E},
92+ {0x0133, 0x0132},
93+ {0x0135, 0x0134},
94+ {0x0137, 0x0136},
95+ {0x013A, 0x0139},
96+ {0x013C, 0x013B},
97+ {0x013E, 0x013D},
98+ {0x0140, 0x013F},
99+ {0x0142, 0x0141},
100+ {0x0144, 0x0143},
101+ {0x0146, 0x0145},
102+ {0x0148, 0x0147},
103+ {0x014B, 0x014A},
104+ {0x014D, 0x014C},
105+ {0x014F, 0x014E},
106+ {0x0151, 0x0150},
107+ {0x0153, 0x0152},
108+ {0x0155, 0x0154},
109+ {0x0157, 0x0156},
110+ {0x0159, 0x0158},
111+ {0x015B, 0x015A},
112+ {0x015D, 0x015C},
113+ {0x015F, 0x015E},
114+ {0x0161, 0x0160},
115+ {0x0163, 0x0162},
116+ {0x0165, 0x0164},
117+ {0x0167, 0x0166},
118+ {0x0169, 0x0168},
119+ {0x016B, 0x016A},
120+ {0x016D, 0x016C},
121+ {0x016F, 0x016E},
122+ {0x0171, 0x0170},
123+ {0x0173, 0x0172},
124+ {0x0175, 0x0174},
125+ {0x0177, 0x0176},
126+ {0x017A, 0x0179},
127+ {0x017C, 0x017B},
128+ {0x017E, 0x017D},
129+ {0x0180, 0x0243},
130+ {0x0183, 0x0182},
131+ {0x0185, 0x0184},
132+ {0x0188, 0x0187},
133+ {0x018C, 0x018B},
134+ {0x0192, 0x0191},
135+ {0x0195, 0x01F6},
136+ {0x0199, 0x0198},
137+ {0x019A, 0x023D},
138+ {0x019B, 0xA7DC},
139+ {0x019E, 0x0220},
140+ {0x01A1, 0x01A0},
141+ {0x01A3, 0x01A2},
142+ {0x01A5, 0x01A4},
143+ {0x01A8, 0x01A7},
144+ {0x01AD, 0x01AC},
145+ {0x01B0, 0x01AF},
146+ {0x01B4, 0x01B3},
147+ {0x01B6, 0x01B5},
148+ {0x01B9, 0x01B8},
149+ {0x01BD, 0x01BC},
150+ {0x01BF, 0x01F7},
151+ {0x01C5, 0x01C4},
152+ {0x01C6, 0x01C4},
153+ {0x01C8, 0x01C7},
154+ {0x01C9, 0x01C7},
155+ {0x01CB, 0x01CA},
156+ {0x01CC, 0x01CA},
157+ {0x01CE, 0x01CD},
158+ {0x01D0, 0x01CF},
159+ {0x01D2, 0x01D1},
160+ {0x01D4, 0x01D3},
161+ {0x01D6, 0x01D5},
162+ {0x01D8, 0x01D7},
163+ {0x01DA, 0x01D9},
164+ {0x01DC, 0x01DB},
165+ {0x01DD, 0x018E},
166+ {0x01DF, 0x01DE},
167+ {0x01E1, 0x01E0},
168+ {0x01E3, 0x01E2},
169+ {0x01E5, 0x01E4},
170+ {0x01E7, 0x01E6},
171+ {0x01E9, 0x01E8},
172+ {0x01EB, 0x01EA},
173+ {0x01ED, 0x01EC},
174+ {0x01EF, 0x01EE},
175+ {0x01F2, 0x01F1},
176+ {0x01F3, 0x01F1},
177+ {0x01F5, 0x01F4},
178+ {0x01F9, 0x01F8},
179+ {0x01FB, 0x01FA},
180+ {0x01FD, 0x01FC},
181+ {0x01FF, 0x01FE},
182+ {0x0201, 0x0200},
183+ {0x0203, 0x0202},
184+ {0x0205, 0x0204},
185+ {0x0207, 0x0206},
186+ {0x0209, 0x0208},
187+ {0x020B, 0x020A},
188+ {0x020D, 0x020C},
189+ {0x020F, 0x020E},
190+ {0x0211, 0x0210},
191+ {0x0213, 0x0212},
192+ {0x0215, 0x0214},
193+ {0x0217, 0x0216},
194+ {0x0219, 0x0218},
195+ {0x021B, 0x021A},
196+ {0x021D, 0x021C},
197+ {0x021F, 0x021E},
198+ {0x0223, 0x0222},
199+ {0x0225, 0x0224},
200+ {0x0227, 0x0226},
201+ {0x0229, 0x0228},
202+ {0x022B, 0x022A},
203+ {0x022D, 0x022C},
204+ {0x022F, 0x022E},
205+ {0x0231, 0x0230},
206+ {0x0233, 0x0232},
207+ {0x023C, 0x023B},
208+ {0x023F, 0x2C7E},
209+ {0x0240, 0x2C7F},
210+ {0x0242, 0x0241},
211+ {0x0247, 0x0246},
212+ {0x0249, 0x0248},
213+ {0x024B, 0x024A},
214+ {0x024D, 0x024C},
215+ {0x024F, 0x024E},
216+ {0x0250, 0x2C6F},
217+ {0x0251, 0x2C6D},
218+ {0x0252, 0x2C70},
219+ {0x0253, 0x0181},
220+ {0x0254, 0x0186},
221+ {0x0256, 0x0189},
222+ {0x0257, 0x018A},
223+ {0x0259, 0x018F},
224+ {0x025B, 0x0190},
225+ {0x025C, 0xA7AB},
226+ {0x0260, 0x0193},
227+ {0x0261, 0xA7AC},
228+ {0x0263, 0x0194},
229+ {0x0264, 0xA7CB},
230+ {0x0265, 0xA78D},
231+ {0x0266, 0xA7AA},
232+ {0x0268, 0x0197},
233+ {0x0269, 0x0196},
234+ {0x026A, 0xA7AE},
235+ {0x026B, 0x2C62},
236+ {0x026C, 0xA7AD},
237+ {0x026F, 0x019C},
238+ {0x0271, 0x2C6E},
239+ {0x0272, 0x019D},
240+ {0x0275, 0x019F},
241+ {0x027D, 0x2C64},
242+ {0x0280, 0x01A6},
243+ {0x0282, 0xA7C5},
244+ {0x0283, 0x01A9},
245+ {0x0287, 0xA7B1},
246+ {0x0288, 0x01AE},
247+ {0x0289, 0x0244},
248+ {0x028A, 0x01B1},
249+ {0x028B, 0x01B2},
250+ {0x028C, 0x0245},
251+ {0x0292, 0x01B7},
252+ {0x029D, 0xA7B2},
253+ {0x029E, 0xA7B0},
254+ {0x0345, 0x0399},
255+ {0x0371, 0x0370},
256+ {0x0373, 0x0372},
257+ {0x0377, 0x0376},
258+ {0x037B, 0x03FD},
259+ {0x037C, 0x03FE},
260+ {0x037D, 0x03FF},
261+ {0x03AC, 0x0386},
262+ {0x03AD, 0x0388},
263+ {0x03AE, 0x0389},
264+ {0x03AF, 0x038A},
265+ {0x03B1, 0x0391},
266+ {0x03B2, 0x0392},
267+ {0x03B3, 0x0393},
268+ {0x03B4, 0x0394},
269+ {0x03B5, 0x0395},
270+ {0x03B6, 0x0396},
271+ {0x03B7, 0x0397},
272+ {0x03B8, 0x0398},
273+ {0x03B9, 0x0399},
274+ {0x03BA, 0x039A},
275+ {0x03BB, 0x039B},
276+ {0x03BC, 0x039C},
277+ {0x03BD, 0x039D},
278+ {0x03BE, 0x039E},
279+ {0x03BF, 0x039F},
280+ {0x03C0, 0x03A0},
281+ {0x03C1, 0x03A1},
282+ {0x03C2, 0x03A3},
283+ {0x03C3, 0x03A3},
284+ {0x03C4, 0x03A4},
285+ {0x03C5, 0x03A5},
286+ {0x03C6, 0x03A6},
287+ {0x03C7, 0x03A7},
288+ {0x03C8, 0x03A8},
289+ {0x03C9, 0x03A9},
290+ {0x03CA, 0x03AA},
291+ {0x03CB, 0x03AB},
292+ {0x03CC, 0x038C},
293+ {0x03CD, 0x038E},
294+ {0x03CE, 0x038F},
295+ {0x03D0, 0x0392},
296+ {0x03D1, 0x0398},
297+ {0x03D5, 0x03A6},
298+ {0x03D6, 0x03A0},
299+ {0x03D7, 0x03CF},
300+ {0x03D9, 0x03D8},
301+ {0x03DB, 0x03DA},
302+ {0x03DD, 0x03DC},
303+ {0x03DF, 0x03DE},
304+ {0x03E1, 0x03E0},
305+ {0x03E3, 0x03E2},
306+ {0x03E5, 0x03E4},
307+ {0x03E7, 0x03E6},
308+ {0x03E9, 0x03E8},
309+ {0x03EB, 0x03EA},
310+ {0x03ED, 0x03EC},
311+ {0x03EF, 0x03EE},
312+ {0x03F0, 0x039A},
313+ {0x03F1, 0x03A1},
314+ {0x03F2, 0x03F9},
315+ {0x03F3, 0x037F},
316+ {0x03F5, 0x0395},
317+ {0x03F8, 0x03F7},
318+ {0x03FB, 0x03FA},
319+ {0x0430, 0x0410},
320+ {0x0431, 0x0411},
321+ {0x0432, 0x0412},
322+ {0x0433, 0x0413},
323+ {0x0434, 0x0414},
324+ {0x0435, 0x0415},
325+ {0x0436, 0x0416},
326+ {0x0437, 0x0417},
327+ {0x0438, 0x0418},
328+ {0x0439, 0x0419},
329+ {0x043A, 0x041A},
330+ {0x043B, 0x041B},
331+ {0x043C, 0x041C},
332+ {0x043D, 0x041D},
333+ {0x043E, 0x041E},
334+ {0x043F, 0x041F},
335+ {0x0440, 0x0420},
336+ {0x0441, 0x0421},
337+ {0x0442, 0x0422},
338+ {0x0443, 0x0423},
339+ {0x0444, 0x0424},
340+ {0x0445, 0x0425},
341+ {0x0446, 0x0426},
342+ {0x0447, 0x0427},
343+ {0x0448, 0x0428},
344+ {0x0449, 0x0429},
345+ {0x044A, 0x042A},
346+ {0x044B, 0x042B},
347+ {0x044C, 0x042C},
348+ {0x044D, 0x042D},
349+ {0x044E, 0x042E},
350+ {0x044F, 0x042F},
351+ {0x0450, 0x0400},
352+ {0x0451, 0x0401},
353+ {0x0452, 0x0402},
354+ {0x0453, 0x0403},
355+ {0x0454, 0x0404},
356+ {0x0455, 0x0405},
357+ {0x0456, 0x0406},
358+ {0x0457, 0x0407},
359+ {0x0458, 0x0408},
360+ {0x0459, 0x0409},
361+ {0x045A, 0x040A},
362+ {0x045B, 0x040B},
363+ {0x045C, 0x040C},
364+ {0x045D, 0x040D},
365+ {0x045E, 0x040E},
366+ {0x045F, 0x040F},
367+ {0x0461, 0x0460},
368+ {0x0463, 0x0462},
369+ {0x0465, 0x0464},
370+ {0x0467, 0x0466},
371+ {0x0469, 0x0468},
372+ {0x046B, 0x046A},
373+ {0x046D, 0x046C},
374+ {0x046F, 0x046E},
375+ {0x0471, 0x0470},
376+ {0x0473, 0x0472},
377+ {0x0475, 0x0474},
378+ {0x0477, 0x0476},
379+ {0x0479, 0x0478},
380+ {0x047B, 0x047A},
381+ {0x047D, 0x047C},
382+ {0x047F, 0x047E},
383+ {0x0481, 0x0480},
384+ {0x048B, 0x048A},
385+ {0x048D, 0x048C},
386+ {0x048F, 0x048E},
387+ {0x0491, 0x0490},
388+ {0x0493, 0x0492},
389+ {0x0495, 0x0494},
390+ {0x0497, 0x0496},
391+ {0x0499, 0x0498},
392+ {0x049B, 0x049A},
393+ {0x049D, 0x049C},
394+ {0x049F, 0x049E},
395+ {0x04A1, 0x04A0},
396+ {0x04A3, 0x04A2},
397+ {0x04A5, 0x04A4},
398+ {0x04A7, 0x04A6},
399+ {0x04A9, 0x04A8},
400+ {0x04AB, 0x04AA},
401+ {0x04AD, 0x04AC},
402+ {0x04AF, 0x04AE},
403+ {0x04B1, 0x04B0},
404+ {0x04B3, 0x04B2},
405+ {0x04B5, 0x04B4},
406+ {0x04B7, 0x04B6},
407+ {0x04B9, 0x04B8},
408+ {0x04BB, 0x04BA},
409+ {0x04BD, 0x04BC},
410+ {0x04BF, 0x04BE},
411+ {0x04C2, 0x04C1},
412+ {0x04C4, 0x04C3},
413+ {0x04C6, 0x04C5},
414+ {0x04C8, 0x04C7},
415+ {0x04CA, 0x04C9},
416+ {0x04CC, 0x04CB},
417+ {0x04CE, 0x04CD},
418+ {0x04CF, 0x04C0},
419+ {0x04D1, 0x04D0},
420+ {0x04D3, 0x04D2},
421+ {0x04D5, 0x04D4},
422+ {0x04D7, 0x04D6},
423+ {0x04D9, 0x04D8},
424+ {0x04DB, 0x04DA},
425+ {0x04DD, 0x04DC},
426+ {0x04DF, 0x04DE},
427+ {0x04E1, 0x04E0},
428+ {0x04E3, 0x04E2},
429+ {0x04E5, 0x04E4},
430+ {0x04E7, 0x04E6},
431+ {0x04E9, 0x04E8},
432+ {0x04EB, 0x04EA},
433+ {0x04ED, 0x04EC},
434+ {0x04EF, 0x04EE},
435+ {0x04F1, 0x04F0},
436+ {0x04F3, 0x04F2},
437+ {0x04F5, 0x04F4},
438+ {0x04F7, 0x04F6},
439+ {0x04F9, 0x04F8},
440+ {0x04FB, 0x04FA},
441+ {0x04FD, 0x04FC},
442+ {0x04FF, 0x04FE},
443+ {0x0501, 0x0500},
444+ {0x0503, 0x0502},
445+ {0x0505, 0x0504},
446+ {0x0507, 0x0506},
447+ {0x0509, 0x0508},
448+ {0x050B, 0x050A},
449+ {0x050D, 0x050C},
450+ {0x050F, 0x050E},
451+ {0x0511, 0x0510},
452+ {0x0513, 0x0512},
453+ {0x0515, 0x0514},
454+ {0x0517, 0x0516},
455+ {0x0519, 0x0518},
456+ {0x051B, 0x051A},
457+ {0x051D, 0x051C},
458+ {0x051F, 0x051E},
459+ {0x0521, 0x0520},
460+ {0x0523, 0x0522},
461+ {0x0525, 0x0524},
462+ {0x0527, 0x0526},
463+ {0x0529, 0x0528},
464+ {0x052B, 0x052A},
465+ {0x052D, 0x052C},
466+ {0x052F, 0x052E},
467+ {0x0561, 0x0531},
468+ {0x0562, 0x0532},
469+ {0x0563, 0x0533},
470+ {0x0564, 0x0534},
471+ {0x0565, 0x0535},
472+ {0x0566, 0x0536},
473+ {0x0567, 0x0537},
474+ {0x0568, 0x0538},
475+ {0x0569, 0x0539},
476+ {0x056A, 0x053A},
477+ {0x056B, 0x053B},
478+ {0x056C, 0x053C},
479+ {0x056D, 0x053D},
480+ {0x056E, 0x053E},
481+ {0x056F, 0x053F},
482+ {0x0570, 0x0540},
483+ {0x0571, 0x0541},
484+ {0x0572, 0x0542},
485+ {0x0573, 0x0543},
486+ {0x0574, 0x0544},
487+ {0x0575, 0x0545},
488+ {0x0576, 0x0546},
489+ {0x0577, 0x0547},
490+ {0x0578, 0x0548},
491+ {0x0579, 0x0549},
492+ {0x057A, 0x054A},
493+ {0x057B, 0x054B},
494+ {0x057C, 0x054C},
495+ {0x057D, 0x054D},
496+ {0x057E, 0x054E},
497+ {0x057F, 0x054F},
498+ {0x0580, 0x0550},
499+ {0x0581, 0x0551},
500+ {0x0582, 0x0552},
501+ {0x0583, 0x0553},
502+ {0x0584, 0x0554},
503+ {0x0585, 0x0555},
504+ {0x0586, 0x0556},
505+ {0x10D0, 0x1C90},
506+ {0x10D1, 0x1C91},
507+ {0x10D2, 0x1C92},
508+ {0x10D3, 0x1C93},
509+ {0x10D4, 0x1C94},
510+ {0x10D5, 0x1C95},
511+ {0x10D6, 0x1C96},
512+ {0x10D7, 0x1C97},
513+ {0x10D8, 0x1C98},
514+ {0x10D9, 0x1C99},
515+ {0x10DA, 0x1C9A},
516+ {0x10DB, 0x1C9B},
517+ {0x10DC, 0x1C9C},
518+ {0x10DD, 0x1C9D},
519+ {0x10DE, 0x1C9E},
520+ {0x10DF, 0x1C9F},
521+ {0x10E0, 0x1CA0},
522+ {0x10E1, 0x1CA1},
523+ {0x10E2, 0x1CA2},
524+ {0x10E3, 0x1CA3},
525+ {0x10E4, 0x1CA4},
526+ {0x10E5, 0x1CA5},
527+ {0x10E6, 0x1CA6},
528+ {0x10E7, 0x1CA7},
529+ {0x10E8, 0x1CA8},
530+ {0x10E9, 0x1CA9},
531+ {0x10EA, 0x1CAA},
532+ {0x10EB, 0x1CAB},
533+ {0x10EC, 0x1CAC},
534+ {0x10ED, 0x1CAD},
535+ {0x10EE, 0x1CAE},
536+ {0x10EF, 0x1CAF},
537+ {0x10F0, 0x1CB0},
538+ {0x10F1, 0x1CB1},
539+ {0x10F2, 0x1CB2},
540+ {0x10F3, 0x1CB3},
541+ {0x10F4, 0x1CB4},
542+ {0x10F5, 0x1CB5},
543+ {0x10F6, 0x1CB6},
544+ {0x10F7, 0x1CB7},
545+ {0x10F8, 0x1CB8},
546+ {0x10F9, 0x1CB9},
547+ {0x10FA, 0x1CBA},
548+ {0x10FD, 0x1CBD},
549+ {0x10FE, 0x1CBE},
550+ {0x10FF, 0x1CBF},
551+ {0x13F8, 0x13F0},
552+ {0x13F9, 0x13F1},
553+ {0x13FA, 0x13F2},
554+ {0x13FB, 0x13F3},
555+ {0x13FC, 0x13F4},
556+ {0x13FD, 0x13F5},
557+ {0x1C80, 0x0412},
558+ {0x1C81, 0x0414},
559+ {0x1C82, 0x041E},
560+ {0x1C83, 0x0421},
561+ {0x1C84, 0x0422},
562+ {0x1C85, 0x0422},
563+ {0x1C86, 0x042A},
564+ {0x1C87, 0x0462},
565+ {0x1C88, 0xA64A},
566+ {0x1C8A, 0x1C89},
567+ {0x1D79, 0xA77D},
568+ {0x1D7D, 0x2C63},
569+ {0x1D8E, 0xA7C6},
570+ {0x1E01, 0x1E00},
571+ {0x1E03, 0x1E02},
572+ {0x1E05, 0x1E04},
573+ {0x1E07, 0x1E06},
574+ {0x1E09, 0x1E08},
575+ {0x1E0B, 0x1E0A},
576+ {0x1E0D, 0x1E0C},
577+ {0x1E0F, 0x1E0E},
578+ {0x1E11, 0x1E10},
579+ {0x1E13, 0x1E12},
580+ {0x1E15, 0x1E14},
581+ {0x1E17, 0x1E16},
582+ {0x1E19, 0x1E18},
583+ {0x1E1B, 0x1E1A},
584+ {0x1E1D, 0x1E1C},
585+ {0x1E1F, 0x1E1E},
586+ {0x1E21, 0x1E20},
587+ {0x1E23, 0x1E22},
588+ {0x1E25, 0x1E24},
589+ {0x1E27, 0x1E26},
590+ {0x1E29, 0x1E28},
591+ {0x1E2B, 0x1E2A},
592+ {0x1E2D, 0x1E2C},
593+ {0x1E2F, 0x1E2E},
594+ {0x1E31, 0x1E30},
595+ {0x1E33, 0x1E32},
596+ {0x1E35, 0x1E34},
597+ {0x1E37, 0x1E36},
598+ {0x1E39, 0x1E38},
599+ {0x1E3B, 0x1E3A},
600+ {0x1E3D, 0x1E3C},
601+ {0x1E3F, 0x1E3E},
602+ {0x1E41, 0x1E40},
603+ {0x1E43, 0x1E42},
604+ {0x1E45, 0x1E44},
605+ {0x1E47, 0x1E46},
606+ {0x1E49, 0x1E48},
607+ {0x1E4B, 0x1E4A},
608+ {0x1E4D, 0x1E4C},
609+ {0x1E4F, 0x1E4E},
610+ {0x1E51, 0x1E50},
611+ {0x1E53, 0x1E52},
612+ {0x1E55, 0x1E54},
613+ {0x1E57, 0x1E56},
614+ {0x1E59, 0x1E58},
615+ {0x1E5B, 0x1E5A},
616+ {0x1E5D, 0x1E5C},
617+ {0x1E5F, 0x1E5E},
618+ {0x1E61, 0x1E60},
619+ {0x1E63, 0x1E62},
620+ {0x1E65, 0x1E64},
621+ {0x1E67, 0x1E66},
622+ {0x1E69, 0x1E68},
623+ {0x1E6B, 0x1E6A},
624+ {0x1E6D, 0x1E6C},
625+ {0x1E6F, 0x1E6E},
626+ {0x1E71, 0x1E70},
627+ {0x1E73, 0x1E72},
628+ {0x1E75, 0x1E74},
629+ {0x1E77, 0x1E76},
630+ {0x1E79, 0x1E78},
631+ {0x1E7B, 0x1E7A},
632+ {0x1E7D, 0x1E7C},
633+ {0x1E7F, 0x1E7E},
634+ {0x1E81, 0x1E80},
635+ {0x1E83, 0x1E82},
636+ {0x1E85, 0x1E84},
637+ {0x1E87, 0x1E86},
638+ {0x1E89, 0x1E88},
639+ {0x1E8B, 0x1E8A},
640+ {0x1E8D, 0x1E8C},
641+ {0x1E8F, 0x1E8E},
642+ {0x1E91, 0x1E90},
643+ {0x1E93, 0x1E92},
644+ {0x1E95, 0x1E94},
645+ {0x1E9B, 0x1E60},
646+ {0x1EA1, 0x1EA0},
647+ {0x1EA3, 0x1EA2},
648+ {0x1EA5, 0x1EA4},
649+ {0x1EA7, 0x1EA6},
650+ {0x1EA9, 0x1EA8},
651+ {0x1EAB, 0x1EAA},
652+ {0x1EAD, 0x1EAC},
653+ {0x1EAF, 0x1EAE},
654+ {0x1EB1, 0x1EB0},
655+ {0x1EB3, 0x1EB2},
656+ {0x1EB5, 0x1EB4},
657+ {0x1EB7, 0x1EB6},
658+ {0x1EB9, 0x1EB8},
659+ {0x1EBB, 0x1EBA},
660+ {0x1EBD, 0x1EBC},
661+ {0x1EBF, 0x1EBE},
662+ {0x1EC1, 0x1EC0},
663+ {0x1EC3, 0x1EC2},
664+ {0x1EC5, 0x1EC4},
665+ {0x1EC7, 0x1EC6},
666+ {0x1EC9, 0x1EC8},
667+ {0x1ECB, 0x1ECA},
668+ {0x1ECD, 0x1ECC},
669+ {0x1ECF, 0x1ECE},
670+ {0x1ED1, 0x1ED0},
671+ {0x1ED3, 0x1ED2},
672+ {0x1ED5, 0x1ED4},
673+ {0x1ED7, 0x1ED6},
674+ {0x1ED9, 0x1ED8},
675+ {0x1EDB, 0x1EDA},
676+ {0x1EDD, 0x1EDC},
677+ {0x1EDF, 0x1EDE},
678+ {0x1EE1, 0x1EE0},
679+ {0x1EE3, 0x1EE2},
680+ {0x1EE5, 0x1EE4},
681+ {0x1EE7, 0x1EE6},
682+ {0x1EE9, 0x1EE8},
683+ {0x1EEB, 0x1EEA},
684+ {0x1EED, 0x1EEC},
685+ {0x1EEF, 0x1EEE},
686+ {0x1EF1, 0x1EF0},
687+ {0x1EF3, 0x1EF2},
688+ {0x1EF5, 0x1EF4},
689+ {0x1EF7, 0x1EF6},
690+ {0x1EF9, 0x1EF8},
691+ {0x1EFB, 0x1EFA},
692+ {0x1EFD, 0x1EFC},
693+ {0x1EFF, 0x1EFE},
694+ {0x1F00, 0x1F08},
695+ {0x1F01, 0x1F09},
696+ {0x1F02, 0x1F0A},
697+ {0x1F03, 0x1F0B},
698+ {0x1F04, 0x1F0C},
699+ {0x1F05, 0x1F0D},
700+ {0x1F06, 0x1F0E},
701+ {0x1F07, 0x1F0F},
702+ {0x1F10, 0x1F18},
703+ {0x1F11, 0x1F19},
704+ {0x1F12, 0x1F1A},
705+ {0x1F13, 0x1F1B},
706+ {0x1F14, 0x1F1C},
707+ {0x1F15, 0x1F1D},
708+ {0x1F20, 0x1F28},
709+ {0x1F21, 0x1F29},
710+ {0x1F22, 0x1F2A},
711+ {0x1F23, 0x1F2B},
712+ {0x1F24, 0x1F2C},
713+ {0x1F25, 0x1F2D},
714+ {0x1F26, 0x1F2E},
715+ {0x1F27, 0x1F2F},
716+ {0x1F30, 0x1F38},
717+ {0x1F31, 0x1F39},
718+ {0x1F32, 0x1F3A},
719+ {0x1F33, 0x1F3B},
720+ {0x1F34, 0x1F3C},
721+ {0x1F35, 0x1F3D},
722+ {0x1F36, 0x1F3E},
723+ {0x1F37, 0x1F3F},
724+ {0x1F40, 0x1F48},
725+ {0x1F41, 0x1F49},
726+ {0x1F42, 0x1F4A},
727+ {0x1F43, 0x1F4B},
728+ {0x1F44, 0x1F4C},
729+ {0x1F45, 0x1F4D},
730+ {0x1F51, 0x1F59},
731+ {0x1F53, 0x1F5B},
732+ {0x1F55, 0x1F5D},
733+ {0x1F57, 0x1F5F},
734+ {0x1F60, 0x1F68},
735+ {0x1F61, 0x1F69},
736+ {0x1F62, 0x1F6A},
737+ {0x1F63, 0x1F6B},
738+ {0x1F64, 0x1F6C},
739+ {0x1F65, 0x1F6D},
740+ {0x1F66, 0x1F6E},
741+ {0x1F67, 0x1F6F},
742+ {0x1F70, 0x1FBA},
743+ {0x1F71, 0x1FBB},
744+ {0x1F72, 0x1FC8},
745+ {0x1F73, 0x1FC9},
746+ {0x1F74, 0x1FCA},
747+ {0x1F75, 0x1FCB},
748+ {0x1F76, 0x1FDA},
749+ {0x1F77, 0x1FDB},
750+ {0x1F78, 0x1FF8},
751+ {0x1F79, 0x1FF9},
752+ {0x1F7A, 0x1FEA},
753+ {0x1F7B, 0x1FEB},
754+ {0x1F7C, 0x1FFA},
755+ {0x1F7D, 0x1FFB},
756+ {0x1F80, 0x1F88},
757+ {0x1F81, 0x1F89},
758+ {0x1F82, 0x1F8A},
759+ {0x1F83, 0x1F8B},
760+ {0x1F84, 0x1F8C},
761+ {0x1F85, 0x1F8D},
762+ {0x1F86, 0x1F8E},
763+ {0x1F87, 0x1F8F},
764+ {0x1F90, 0x1F98},
765+ {0x1F91, 0x1F99},
766+ {0x1F92, 0x1F9A},
767+ {0x1F93, 0x1F9B},
768+ {0x1F94, 0x1F9C},
769+ {0x1F95, 0x1F9D},
770+ {0x1F96, 0x1F9E},
771+ {0x1F97, 0x1F9F},
772+ {0x1FA0, 0x1FA8},
773+ {0x1FA1, 0x1FA9},
774+ {0x1FA2, 0x1FAA},
775+ {0x1FA3, 0x1FAB},
776+ {0x1FA4, 0x1FAC},
777+ {0x1FA5, 0x1FAD},
778+ {0x1FA6, 0x1FAE},
779+ {0x1FA7, 0x1FAF},
780+ {0x1FB0, 0x1FB8},
781+ {0x1FB1, 0x1FB9},
782+ {0x1FB3, 0x1FBC},
783+ {0x1FBE, 0x0399},
784+ {0x1FC3, 0x1FCC},
785+ {0x1FD0, 0x1FD8},
786+ {0x1FD1, 0x1FD9},
787+ {0x1FE0, 0x1FE8},
788+ {0x1FE1, 0x1FE9},
789+ {0x1FE5, 0x1FEC},
790+ {0x1FF3, 0x1FFC},
791+ {0x214E, 0x2132},
792+ {0x2170, 0x2160},
793+ {0x2171, 0x2161},
794+ {0x2172, 0x2162},
795+ {0x2173, 0x2163},
796+ {0x2174, 0x2164},
797+ {0x2175, 0x2165},
798+ {0x2176, 0x2166},
799+ {0x2177, 0x2167},
800+ {0x2178, 0x2168},
801+ {0x2179, 0x2169},
802+ {0x217A, 0x216A},
803+ {0x217B, 0x216B},
804+ {0x217C, 0x216C},
805+ {0x217D, 0x216D},
806+ {0x217E, 0x216E},
807+ {0x217F, 0x216F},
808+ {0x2184, 0x2183},
809+ {0x24D0, 0x24B6},
810+ {0x24D1, 0x24B7},
811+ {0x24D2, 0x24B8},
812+ {0x24D3, 0x24B9},
813+ {0x24D4, 0x24BA},
814+ {0x24D5, 0x24BB},
815+ {0x24D6, 0x24BC},
816+ {0x24D7, 0x24BD},
817+ {0x24D8, 0x24BE},
818+ {0x24D9, 0x24BF},
819+ {0x24DA, 0x24C0},
820+ {0x24DB, 0x24C1},
821+ {0x24DC, 0x24C2},
822+ {0x24DD, 0x24C3},
823+ {0x24DE, 0x24C4},
824+ {0x24DF, 0x24C5},
825+ {0x24E0, 0x24C6},
826+ {0x24E1, 0x24C7},
827+ {0x24E2, 0x24C8},
828+ {0x24E3, 0x24C9},
829+ {0x24E4, 0x24CA},
830+ {0x24E5, 0x24CB},
831+ {0x24E6, 0x24CC},
832+ {0x24E7, 0x24CD},
833+ {0x24E8, 0x24CE},
834+ {0x24E9, 0x24CF},
835+ {0x2C30, 0x2C00},
836+ {0x2C31, 0x2C01},
837+ {0x2C32, 0x2C02},
838+ {0x2C33, 0x2C03},
839+ {0x2C34, 0x2C04},
840+ {0x2C35, 0x2C05},
841+ {0x2C36, 0x2C06},
842+ {0x2C37, 0x2C07},
843+ {0x2C38, 0x2C08},
844+ {0x2C39, 0x2C09},
845+ {0x2C3A, 0x2C0A},
846+ {0x2C3B, 0x2C0B},
847+ {0x2C3C, 0x2C0C},
848+ {0x2C3D, 0x2C0D},
849+ {0x2C3E, 0x2C0E},
850+ {0x2C3F, 0x2C0F},
851+ {0x2C40, 0x2C10},
852+ {0x2C41, 0x2C11},
853+ {0x2C42, 0x2C12},
854+ {0x2C43, 0x2C13},
855+ {0x2C44, 0x2C14},
856+ {0x2C45, 0x2C15},
857+ {0x2C46, 0x2C16},
858+ {0x2C47, 0x2C17},
859+ {0x2C48, 0x2C18},
860+ {0x2C49, 0x2C19},
861+ {0x2C4A, 0x2C1A},
862+ {0x2C4B, 0x2C1B},
863+ {0x2C4C, 0x2C1C},
864+ {0x2C4D, 0x2C1D},
865+ {0x2C4E, 0x2C1E},
866+ {0x2C4F, 0x2C1F},
867+ {0x2C50, 0x2C20},
868+ {0x2C51, 0x2C21},
869+ {0x2C52, 0x2C22},
870+ {0x2C53, 0x2C23},
871+ {0x2C54, 0x2C24},
872+ {0x2C55, 0x2C25},
873+ {0x2C56, 0x2C26},
874+ {0x2C57, 0x2C27},
875+ {0x2C58, 0x2C28},
876+ {0x2C59, 0x2C29},
877+ {0x2C5A, 0x2C2A},
878+ {0x2C5B, 0x2C2B},
879+ {0x2C5C, 0x2C2C},
880+ {0x2C5D, 0x2C2D},
881+ {0x2C5E, 0x2C2E},
882+ {0x2C5F, 0x2C2F},
883+ {0x2C61, 0x2C60},
884+ {0x2C65, 0x023A},
885+ {0x2C66, 0x023E},
886+ {0x2C68, 0x2C67},
887+ {0x2C6A, 0x2C69},
888+ {0x2C6C, 0x2C6B},
889+ {0x2C73, 0x2C72},
890+ {0x2C76, 0x2C75},
891+ {0x2C81, 0x2C80},
892+ {0x2C83, 0x2C82},
893+ {0x2C85, 0x2C84},
894+ {0x2C87, 0x2C86},
895+ {0x2C89, 0x2C88},
896+ {0x2C8B, 0x2C8A},
897+ {0x2C8D, 0x2C8C},
898+ {0x2C8F, 0x2C8E},
899+ {0x2C91, 0x2C90},
900+ {0x2C93, 0x2C92},
901+ {0x2C95, 0x2C94},
902+ {0x2C97, 0x2C96},
903+ {0x2C99, 0x2C98},
904+ {0x2C9B, 0x2C9A},
905+ {0x2C9D, 0x2C9C},
906+ {0x2C9F, 0x2C9E},
907+ {0x2CA1, 0x2CA0},
908+ {0x2CA3, 0x2CA2},
909+ {0x2CA5, 0x2CA4},
910+ {0x2CA7, 0x2CA6},
911+ {0x2CA9, 0x2CA8},
912+ {0x2CAB, 0x2CAA},
913+ {0x2CAD, 0x2CAC},
914+ {0x2CAF, 0x2CAE},
915+ {0x2CB1, 0x2CB0},
916+ {0x2CB3, 0x2CB2},
917+ {0x2CB5, 0x2CB4},
918+ {0x2CB7, 0x2CB6},
919+ {0x2CB9, 0x2CB8},
920+ {0x2CBB, 0x2CBA},
921+ {0x2CBD, 0x2CBC},
922+ {0x2CBF, 0x2CBE},
923+ {0x2CC1, 0x2CC0},
924+ {0x2CC3, 0x2CC2},
925+ {0x2CC5, 0x2CC4},
926+ {0x2CC7, 0x2CC6},
927+ {0x2CC9, 0x2CC8},
928+ {0x2CCB, 0x2CCA},
929+ {0x2CCD, 0x2CCC},
930+ {0x2CCF, 0x2CCE},
931+ {0x2CD1, 0x2CD0},
932+ {0x2CD3, 0x2CD2},
933+ {0x2CD5, 0x2CD4},
934+ {0x2CD7, 0x2CD6},
935+ {0x2CD9, 0x2CD8},
936+ {0x2CDB, 0x2CDA},
937+ {0x2CDD, 0x2CDC},
938+ {0x2CDF, 0x2CDE},
939+ {0x2CE1, 0x2CE0},
940+ {0x2CE3, 0x2CE2},
941+ {0x2CEC, 0x2CEB},
942+ {0x2CEE, 0x2CED},
943+ {0x2CF3, 0x2CF2},
944+ {0x2D00, 0x10A0},
945+ {0x2D01, 0x10A1},
946+ {0x2D02, 0x10A2},
947+ {0x2D03, 0x10A3},
948+ {0x2D04, 0x10A4},
949+ {0x2D05, 0x10A5},
950+ {0x2D06, 0x10A6},
951+ {0x2D07, 0x10A7},
952+ {0x2D08, 0x10A8},
953+ {0x2D09, 0x10A9},
954+ {0x2D0A, 0x10AA},
955+ {0x2D0B, 0x10AB},
956+ {0x2D0C, 0x10AC},
957+ {0x2D0D, 0x10AD},
958+ {0x2D0E, 0x10AE},
959+ {0x2D0F, 0x10AF},
960+ {0x2D10, 0x10B0},
961+ {0x2D11, 0x10B1},
962+ {0x2D12, 0x10B2},
963+ {0x2D13, 0x10B3},
964+ {0x2D14, 0x10B4},
965+ {0x2D15, 0x10B5},
966+ {0x2D16, 0x10B6},
967+ {0x2D17, 0x10B7},
968+ {0x2D18, 0x10B8},
969+ {0x2D19, 0x10B9},
970+ {0x2D1A, 0x10BA},
971+ {0x2D1B, 0x10BB},
972+ {0x2D1C, 0x10BC},
973+ {0x2D1D, 0x10BD},
974+ {0x2D1E, 0x10BE},
975+ {0x2D1F, 0x10BF},
976+ {0x2D20, 0x10C0},
977+ {0x2D21, 0x10C1},
978+ {0x2D22, 0x10C2},
979+ {0x2D23, 0x10C3},
980+ {0x2D24, 0x10C4},
981+ {0x2D25, 0x10C5},
982+ {0x2D27, 0x10C7},
983+ {0x2D2D, 0x10CD},
984+ {0xA641, 0xA640},
985+ {0xA643, 0xA642},
986+ {0xA645, 0xA644},
987+ {0xA647, 0xA646},
988+ {0xA649, 0xA648},
989+ {0xA64B, 0xA64A},
990+ {0xA64D, 0xA64C},
991+ {0xA64F, 0xA64E},
992+ {0xA651, 0xA650},
993+ {0xA653, 0xA652},
994+ {0xA655, 0xA654},
995+ {0xA657, 0xA656},
996+ {0xA659, 0xA658},
997+ {0xA65B, 0xA65A},
998+ {0xA65D, 0xA65C},
999+ {0xA65F, 0xA65E},
1000+ {0xA661, 0xA660},
1001+ {0xA663, 0xA662},
1002+ {0xA665, 0xA664},
1003+ {0xA667, 0xA666},
1004+ {0xA669, 0xA668},
1005+ {0xA66B, 0xA66A},
1006+ {0xA66D, 0xA66C},
1007+ {0xA681, 0xA680},
1008+ {0xA683, 0xA682},
1009+ {0xA685, 0xA684},
1010+ {0xA687, 0xA686},
1011+ {0xA689, 0xA688},
1012+ {0xA68B, 0xA68A},
1013+ {0xA68D, 0xA68C},
1014+ {0xA68F, 0xA68E},
1015+ {0xA691, 0xA690},
1016+ {0xA693, 0xA692},
1017+ {0xA695, 0xA694},
1018+ {0xA697, 0xA696},
1019+ {0xA699, 0xA698},
1020+ {0xA69B, 0xA69A},
1021+ {0xA723, 0xA722},
1022+ {0xA725, 0xA724},
1023+ {0xA727, 0xA726},
1024+ {0xA729, 0xA728},
1025+ {0xA72B, 0xA72A},
1026+ {0xA72D, 0xA72C},
1027+ {0xA72F, 0xA72E},
1028+ {0xA733, 0xA732},
1029+ {0xA735, 0xA734},
1030+ {0xA737, 0xA736},
1031+ {0xA739, 0xA738},
1032+ {0xA73B, 0xA73A},
1033+ {0xA73D, 0xA73C},
1034+ {0xA73F, 0xA73E},
1035+ {0xA741, 0xA740},
1036+ {0xA743, 0xA742},
1037+ {0xA745, 0xA744},
1038+ {0xA747, 0xA746},
1039+ {0xA749, 0xA748},
1040+ {0xA74B, 0xA74A},
1041+ {0xA74D, 0xA74C},
1042+ {0xA74F, 0xA74E},
1043+ {0xA751, 0xA750},
1044+ {0xA753, 0xA752},
1045+ {0xA755, 0xA754},
1046+ {0xA757, 0xA756},
1047+ {0xA759, 0xA758},
1048+ {0xA75B, 0xA75A},
1049+ {0xA75D, 0xA75C},
1050+ {0xA75F, 0xA75E},
1051+ {0xA761, 0xA760},
1052+ {0xA763, 0xA762},
1053+ {0xA765, 0xA764},
1054+ {0xA767, 0xA766},
1055+ {0xA769, 0xA768},
1056+ {0xA76B, 0xA76A},
1057+ {0xA76D, 0xA76C},
1058+ {0xA76F, 0xA76E},
1059+ {0xA77A, 0xA779},
1060+ {0xA77C, 0xA77B},
1061+ {0xA77F, 0xA77E},
1062+ {0xA781, 0xA780},
1063+ {0xA783, 0xA782},
1064+ {0xA785, 0xA784},
1065+ {0xA787, 0xA786},
1066+ {0xA78C, 0xA78B},
1067+ {0xA791, 0xA790},
1068+ {0xA793, 0xA792},
1069+ {0xA794, 0xA7C4},
1070+ {0xA797, 0xA796},
1071+ {0xA799, 0xA798},
1072+ {0xA79B, 0xA79A},
1073+ {0xA79D, 0xA79C},
1074+ {0xA79F, 0xA79E},
1075+ {0xA7A1, 0xA7A0},
1076+ {0xA7A3, 0xA7A2},
1077+ {0xA7A5, 0xA7A4},
1078+ {0xA7A7, 0xA7A6},
1079+ {0xA7A9, 0xA7A8},
1080+ {0xA7B5, 0xA7B4},
1081+ {0xA7B7, 0xA7B6},
1082+ {0xA7B9, 0xA7B8},
1083+ {0xA7BB, 0xA7BA},
1084+ {0xA7BD, 0xA7BC},
1085+ {0xA7BF, 0xA7BE},
1086+ {0xA7C1, 0xA7C0},
1087+ {0xA7C3, 0xA7C2},
1088+ {0xA7C8, 0xA7C7},
1089+ {0xA7CA, 0xA7C9},
1090+ {0xA7CD, 0xA7CC},
1091+ {0xA7CF, 0xA7CE},
1092+ {0xA7D1, 0xA7D0},
1093+ {0xA7D3, 0xA7D2},
1094+ {0xA7D5, 0xA7D4},
1095+ {0xA7D7, 0xA7D6},
1096+ {0xA7D9, 0xA7D8},
1097+ {0xA7DB, 0xA7DA},
1098+ {0xA7F6, 0xA7F5},
1099+ {0xAB53, 0xA7B3},
1100+ {0xAB70, 0x13A0},
1101+ {0xAB71, 0x13A1},
1102+ {0xAB72, 0x13A2},
1103+ {0xAB73, 0x13A3},
1104+ {0xAB74, 0x13A4},
1105+ {0xAB75, 0x13A5},
1106+ {0xAB76, 0x13A6},
1107+ {0xAB77, 0x13A7},
1108+ {0xAB78, 0x13A8},
1109+ {0xAB79, 0x13A9},
1110+ {0xAB7A, 0x13AA},
1111+ {0xAB7B, 0x13AB},
1112+ {0xAB7C, 0x13AC},
1113+ {0xAB7D, 0x13AD},
1114+ {0xAB7E, 0x13AE},
1115+ {0xAB7F, 0x13AF},
1116+ {0xAB80, 0x13B0},
1117+ {0xAB81, 0x13B1},
1118+ {0xAB82, 0x13B2},
1119+ {0xAB83, 0x13B3},
1120+ {0xAB84, 0x13B4},
1121+ {0xAB85, 0x13B5},
1122+ {0xAB86, 0x13B6},
1123+ {0xAB87, 0x13B7},
1124+ {0xAB88, 0x13B8},
1125+ {0xAB89, 0x13B9},
1126+ {0xAB8A, 0x13BA},
1127+ {0xAB8B, 0x13BB},
1128+ {0xAB8C, 0x13BC},
1129+ {0xAB8D, 0x13BD},
1130+ {0xAB8E, 0x13BE},
1131+ {0xAB8F, 0x13BF},
1132+ {0xAB90, 0x13C0},
1133+ {0xAB91, 0x13C1},
1134+ {0xAB92, 0x13C2},
1135+ {0xAB93, 0x13C3},
1136+ {0xAB94, 0x13C4},
1137+ {0xAB95, 0x13C5},
1138+ {0xAB96, 0x13C6},
1139+ {0xAB97, 0x13C7},
1140+ {0xAB98, 0x13C8},
1141+ {0xAB99, 0x13C9},
1142+ {0xAB9A, 0x13CA},
1143+ {0xAB9B, 0x13CB},
1144+ {0xAB9C, 0x13CC},
1145+ {0xAB9D, 0x13CD},
1146+ {0xAB9E, 0x13CE},
1147+ {0xAB9F, 0x13CF},
1148+ {0xABA0, 0x13D0},
1149+ {0xABA1, 0x13D1},
1150+ {0xABA2, 0x13D2},
1151+ {0xABA3, 0x13D3},
1152+ {0xABA4, 0x13D4},
1153+ {0xABA5, 0x13D5},
1154+ {0xABA6, 0x13D6},
1155+ {0xABA7, 0x13D7},
1156+ {0xABA8, 0x13D8},
1157+ {0xABA9, 0x13D9},
1158+ {0xABAA, 0x13DA},
1159+ {0xABAB, 0x13DB},
1160+ {0xABAC, 0x13DC},
1161+ {0xABAD, 0x13DD},
1162+ {0xABAE, 0x13DE},
1163+ {0xABAF, 0x13DF},
1164+ {0xABB0, 0x13E0},
1165+ {0xABB1, 0x13E1},
1166+ {0xABB2, 0x13E2},
1167+ {0xABB3, 0x13E3},
1168+ {0xABB4, 0x13E4},
1169+ {0xABB5, 0x13E5},
1170+ {0xABB6, 0x13E6},
1171+ {0xABB7, 0x13E7},
1172+ {0xABB8, 0x13E8},
1173+ {0xABB9, 0x13E9},
1174+ {0xABBA, 0x13EA},
1175+ {0xABBB, 0x13EB},
1176+ {0xABBC, 0x13EC},
1177+ {0xABBD, 0x13ED},
1178+ {0xABBE, 0x13EE},
1179+ {0xABBF, 0x13EF},
1180+ {0xFF41, 0xFF21},
1181+ {0xFF42, 0xFF22},
1182+ {0xFF43, 0xFF23},
1183+ {0xFF44, 0xFF24},
1184+ {0xFF45, 0xFF25},
1185+ {0xFF46, 0xFF26},
1186+ {0xFF47, 0xFF27},
1187+ {0xFF48, 0xFF28},
1188+ {0xFF49, 0xFF29},
1189+ {0xFF4A, 0xFF2A},
1190+ {0xFF4B, 0xFF2B},
1191+ {0xFF4C, 0xFF2C},
1192+ {0xFF4D, 0xFF2D},
1193+ {0xFF4E, 0xFF2E},
1194+ {0xFF4F, 0xFF2F},
1195+ {0xFF50, 0xFF30},
1196+ {0xFF51, 0xFF31},
1197+ {0xFF52, 0xFF32},
1198+ {0xFF53, 0xFF33},
1199+ {0xFF54, 0xFF34},
1200+ {0xFF55, 0xFF35},
1201+ {0xFF56, 0xFF36},
1202+ {0xFF57, 0xFF37},
1203+ {0xFF58, 0xFF38},
1204+ {0xFF59, 0xFF39},
1205+ {0xFF5A, 0xFF3A},
1206+ {0x10428, 0x10400},
1207+ {0x10429, 0x10401},
1208+ {0x1042A, 0x10402},
1209+ {0x1042B, 0x10403},
1210+ {0x1042C, 0x10404},
1211+ {0x1042D, 0x10405},
1212+ {0x1042E, 0x10406},
1213+ {0x1042F, 0x10407},
1214+ {0x10430, 0x10408},
1215+ {0x10431, 0x10409},
1216+ {0x10432, 0x1040A},
1217+ {0x10433, 0x1040B},
1218+ {0x10434, 0x1040C},
1219+ {0x10435, 0x1040D},
1220+ {0x10436, 0x1040E},
1221+ {0x10437, 0x1040F},
1222+ {0x10438, 0x10410},
1223+ {0x10439, 0x10411},
1224+ {0x1043A, 0x10412},
1225+ {0x1043B, 0x10413},
1226+ {0x1043C, 0x10414},
1227+ {0x1043D, 0x10415},
1228+ {0x1043E, 0x10416},
1229+ {0x1043F, 0x10417},
1230+ {0x10440, 0x10418},
1231+ {0x10441, 0x10419},
1232+ {0x10442, 0x1041A},
1233+ {0x10443, 0x1041B},
1234+ {0x10444, 0x1041C},
1235+ {0x10445, 0x1041D},
1236+ {0x10446, 0x1041E},
1237+ {0x10447, 0x1041F},
1238+ {0x10448, 0x10420},
1239+ {0x10449, 0x10421},
1240+ {0x1044A, 0x10422},
1241+ {0x1044B, 0x10423},
1242+ {0x1044C, 0x10424},
1243+ {0x1044D, 0x10425},
1244+ {0x1044E, 0x10426},
1245+ {0x1044F, 0x10427},
1246+ {0x104D8, 0x104B0},
1247+ {0x104D9, 0x104B1},
1248+ {0x104DA, 0x104B2},
1249+ {0x104DB, 0x104B3},
1250+ {0x104DC, 0x104B4},
1251+ {0x104DD, 0x104B5},
1252+ {0x104DE, 0x104B6},
1253+ {0x104DF, 0x104B7},
1254+ {0x104E0, 0x104B8},
1255+ {0x104E1, 0x104B9},
1256+ {0x104E2, 0x104BA},
1257+ {0x104E3, 0x104BB},
1258+ {0x104E4, 0x104BC},
1259+ {0x104E5, 0x104BD},
1260+ {0x104E6, 0x104BE},
1261+ {0x104E7, 0x104BF},
1262+ {0x104E8, 0x104C0},
1263+ {0x104E9, 0x104C1},
1264+ {0x104EA, 0x104C2},
1265+ {0x104EB, 0x104C3},
1266+ {0x104EC, 0x104C4},
1267+ {0x104ED, 0x104C5},
1268+ {0x104EE, 0x104C6},
1269+ {0x104EF, 0x104C7},
1270+ {0x104F0, 0x104C8},
1271+ {0x104F1, 0x104C9},
1272+ {0x104F2, 0x104CA},
1273+ {0x104F3, 0x104CB},
1274+ {0x104F4, 0x104CC},
1275+ {0x104F5, 0x104CD},
1276+ {0x104F6, 0x104CE},
1277+ {0x104F7, 0x104CF},
1278+ {0x104F8, 0x104D0},
1279+ {0x104F9, 0x104D1},
1280+ {0x104FA, 0x104D2},
1281+ {0x104FB, 0x104D3},
1282+ {0x10597, 0x10570},
1283+ {0x10598, 0x10571},
1284+ {0x10599, 0x10572},
1285+ {0x1059A, 0x10573},
1286+ {0x1059B, 0x10574},
1287+ {0x1059C, 0x10575},
1288+ {0x1059D, 0x10576},
1289+ {0x1059E, 0x10577},
1290+ {0x1059F, 0x10578},
1291+ {0x105A0, 0x10579},
1292+ {0x105A1, 0x1057A},
1293+ {0x105A3, 0x1057C},
1294+ {0x105A4, 0x1057D},
1295+ {0x105A5, 0x1057E},
1296+ {0x105A6, 0x1057F},
1297+ {0x105A7, 0x10580},
1298+ {0x105A8, 0x10581},
1299+ {0x105A9, 0x10582},
1300+ {0x105AA, 0x10583},
1301+ {0x105AB, 0x10584},
1302+ {0x105AC, 0x10585},
1303+ {0x105AD, 0x10586},
1304+ {0x105AE, 0x10587},
1305+ {0x105AF, 0x10588},
1306+ {0x105B0, 0x10589},
1307+ {0x105B1, 0x1058A},
1308+ {0x105B3, 0x1058C},
1309+ {0x105B4, 0x1058D},
1310+ {0x105B5, 0x1058E},
1311+ {0x105B6, 0x1058F},
1312+ {0x105B7, 0x10590},
1313+ {0x105B8, 0x10591},
1314+ {0x105B9, 0x10592},
1315+ {0x105BB, 0x10594},
1316+ {0x105BC, 0x10595},
1317+ {0x10CC0, 0x10C80},
1318+ {0x10CC1, 0x10C81},
1319+ {0x10CC2, 0x10C82},
1320+ {0x10CC3, 0x10C83},
1321+ {0x10CC4, 0x10C84},
1322+ {0x10CC5, 0x10C85},
1323+ {0x10CC6, 0x10C86},
1324+ {0x10CC7, 0x10C87},
1325+ {0x10CC8, 0x10C88},
1326+ {0x10CC9, 0x10C89},
1327+ {0x10CCA, 0x10C8A},
1328+ {0x10CCB, 0x10C8B},
1329+ {0x10CCC, 0x10C8C},
1330+ {0x10CCD, 0x10C8D},
1331+ {0x10CCE, 0x10C8E},
1332+ {0x10CCF, 0x10C8F},
1333+ {0x10CD0, 0x10C90},
1334+ {0x10CD1, 0x10C91},
1335+ {0x10CD2, 0x10C92},
1336+ {0x10CD3, 0x10C93},
1337+ {0x10CD4, 0x10C94},
1338+ {0x10CD5, 0x10C95},
1339+ {0x10CD6, 0x10C96},
1340+ {0x10CD7, 0x10C97},
1341+ {0x10CD8, 0x10C98},
1342+ {0x10CD9, 0x10C99},
1343+ {0x10CDA, 0x10C9A},
1344+ {0x10CDB, 0x10C9B},
1345+ {0x10CDC, 0x10C9C},
1346+ {0x10CDD, 0x10C9D},
1347+ {0x10CDE, 0x10C9E},
1348+ {0x10CDF, 0x10C9F},
1349+ {0x10CE0, 0x10CA0},
1350+ {0x10CE1, 0x10CA1},
1351+ {0x10CE2, 0x10CA2},
1352+ {0x10CE3, 0x10CA3},
1353+ {0x10CE4, 0x10CA4},
1354+ {0x10CE5, 0x10CA5},
1355+ {0x10CE6, 0x10CA6},
1356+ {0x10CE7, 0x10CA7},
1357+ {0x10CE8, 0x10CA8},
1358+ {0x10CE9, 0x10CA9},
1359+ {0x10CEA, 0x10CAA},
1360+ {0x10CEB, 0x10CAB},
1361+ {0x10CEC, 0x10CAC},
1362+ {0x10CED, 0x10CAD},
1363+ {0x10CEE, 0x10CAE},
1364+ {0x10CEF, 0x10CAF},
1365+ {0x10CF0, 0x10CB0},
1366+ {0x10CF1, 0x10CB1},
1367+ {0x10CF2, 0x10CB2},
1368+ {0x118C0, 0x118A0},
1369+ {0x118C1, 0x118A1},
1370+ {0x118C2, 0x118A2},
1371+ {0x118C3, 0x118A3},
1372+ {0x118C4, 0x118A4},
1373+ {0x118C5, 0x118A5},
1374+ {0x118C6, 0x118A6},
1375+ {0x118C7, 0x118A7},
1376+ {0x118C8, 0x118A8},
1377+ {0x118C9, 0x118A9},
1378+ {0x118CA, 0x118AA},
1379+ {0x118CB, 0x118AB},
1380+ {0x118CC, 0x118AC},
1381+ {0x118CD, 0x118AD},
1382+ {0x118CE, 0x118AE},
1383+ {0x118CF, 0x118AF},
1384+ {0x118D0, 0x118B0},
1385+ {0x118D1, 0x118B1},
1386+ {0x118D2, 0x118B2},
1387+ {0x118D3, 0x118B3},
1388+ {0x118D4, 0x118B4},
1389+ {0x118D5, 0x118B5},
1390+ {0x118D6, 0x118B6},
1391+ {0x118D7, 0x118B7},
1392+ {0x118D8, 0x118B8},
1393+ {0x118D9, 0x118B9},
1394+ {0x118DA, 0x118BA},
1395+ {0x118DB, 0x118BB},
1396+ {0x118DC, 0x118BC},
1397+ {0x118DD, 0x118BD},
1398+ {0x118DE, 0x118BE},
1399+ {0x118DF, 0x118BF},
1400+ {0x16E60, 0x16E40},
1401+ {0x16E61, 0x16E41},
1402+ {0x16E62, 0x16E42},
1403+ {0x16E63, 0x16E43},
1404+ {0x16E64, 0x16E44},
1405+ {0x16E65, 0x16E45},
1406+ {0x16E66, 0x16E46},
1407+ {0x16E67, 0x16E47},
1408+ {0x16E68, 0x16E48},
1409+ {0x16E69, 0x16E49},
1410+ {0x16E6A, 0x16E4A},
1411+ {0x16E6B, 0x16E4B},
1412+ {0x16E6C, 0x16E4C},
1413+ {0x16E6D, 0x16E4D},
1414+ {0x16E6E, 0x16E4E},
1415+ {0x16E6F, 0x16E4F},
1416+ {0x16E70, 0x16E50},
1417+ {0x16E71, 0x16E51},
1418+ {0x16E72, 0x16E52},
1419+ {0x16E73, 0x16E53},
1420+ {0x16E74, 0x16E54},
1421+ {0x16E75, 0x16E55},
1422+ {0x16E76, 0x16E56},
1423+ {0x16E77, 0x16E57},
1424+ {0x16E78, 0x16E58},
1425+ {0x16E79, 0x16E59},
1426+ {0x16E7A, 0x16E5A},
1427+ {0x16E7B, 0x16E5B},
1428+ {0x16E7C, 0x16E5C},
1429+ {0x16E7D, 0x16E5D},
1430+ {0x16E7E, 0x16E5E},
1431+ {0x16E7F, 0x16E5F},
1432+ {0x1E922, 0x1E900},
1433+ {0x1E923, 0x1E901},
1434+ {0x1E924, 0x1E902},
1435+ {0x1E925, 0x1E903},
1436+ {0x1E926, 0x1E904},
1437+ {0x1E927, 0x1E905},
1438+ {0x1E928, 0x1E906},
1439+ {0x1E929, 0x1E907},
1440+ {0x1E92A, 0x1E908},
1441+ {0x1E92B, 0x1E909},
1442+ {0x1E92C, 0x1E90A},
1443+ {0x1E92D, 0x1E90B},
1444+ {0x1E92E, 0x1E90C},
1445+ {0x1E92F, 0x1E90D},
1446+ {0x1E930, 0x1E90E},
1447+ {0x1E931, 0x1E90F},
1448+ {0x1E932, 0x1E910},
1449+ {0x1E933, 0x1E911},
1450+ {0x1E934, 0x1E912},
1451+ {0x1E935, 0x1E913},
1452+ {0x1E936, 0x1E914},
1453+ {0x1E937, 0x1E915},
1454+ {0x1E938, 0x1E916},
1455+ {0x1E939, 0x1E917},
1456+ {0x1E93A, 0x1E918},
1457+ {0x1E93B, 0x1E919},
1458+ {0x1E93C, 0x1E91A},
1459+ {0x1E93D, 0x1E91B},
1460+ {0x1E93E, 0x1E91C},
1461+ {0x1E93F, 0x1E91D},
1462+ {0x1E940, 0x1E91E},
1463+ {0x1E941, 0x1E91F},
1464+ {0x1E942, 0x1E920},
1465+ {0x1E943, 0x1E921},
1466+}
A expr/fromjson_runner_test.go +68 -0
@@ -0,0 +1,68 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "encoding/json"
7+ "os"
8+ "strings"
9+ "testing"
10+)
11+
12+type fromJSONCase struct {
13+ In string `json:"in"`
14+ OK bool `json:"ok"`
15+ Out string `json:"out"`
16+}
17+
18+func loadFromJSONCases(tb testing.TB) []fromJSONCase {
19+ tb.Helper()
20+ data, err := os.ReadFile("testdata/fromjson_cases.json")
21+ if err != nil {
22+ tb.Fatal(err)
23+ }
24+ var cs []fromJSONCase
25+ if err := json.Unmarshal(data, &cs); err != nil {
26+ tb.Fatal(err)
27+ }
28+ return cs
29+}
30+
31+// unportedJSON reports whether the input uses something of Json.NET that is not
32+// ported: comments and constructors. The runner accepts them, this package
33+// fails on them (see PORTING.md).
34+func unportedJSON(s string) bool {
35+ return strings.Contains(s, "/*") || strings.Contains(s, "//") || strings.Contains(s, "new ") || strings.Contains(s, "/")
36+}
37+
38+// TestFromJSONMatchesRunner replays testdata/fromjson_cases.json, the answers
39+// of the runner's fromJSON (gen/runner/fromjson_cases.mjs) on hand-picked and
40+// mutated JSON-like texts: it must accept exactly the same texts and give the
41+// same toJSON, except where the text could hold a comment or constructor.
42+func TestFromJSONMatchesRunner(t *testing.T) {
43+ cases := loadFromJSONCases(t)
44+ bad, skipped := 0, 0
45+ for _, c := range cases {
46+ v, err := FromJSON([]byte(c.In))
47+ if err == nil && strings.TrimFunc(c.In, isNetSpace) == "" {
48+ err = os.ErrInvalid // fromJSON() rejects blank input before reading
49+ }
50+ gotOK := err == nil
51+ if gotOK != c.OK || (gotOK && ToJSON(v) != c.Out) {
52+ if unportedJSON(c.In) {
53+ skipped++
54+ continue
55+ }
56+ bad++
57+ if bad <= 25 {
58+ t.Errorf("FromJSON(%q): ok=%v out=%q (%v); runner ok=%v out=%q", c.In, gotOK, func() string {
59+ if v != nil {
60+ return ToJSON(v)
61+ }
62+ return ""
63+ }(), err, c.OK, c.Out)
64+ }
65+ }
66+ }
67+ t.Logf("%d cases, %d with comments, slashes or constructors skipped, %d mismatches", len(cases), skipped, bad)
68+}
M expr/funcs.go +251 -96
@@ -4,58 +4,88 @@ package expr
4 4
5 import (5 import (
6 "errors"6 "errors"
7- "math"
8 "strconv"7 "strconv"
9 "strings"8 "strings"
9+ "unicode"
10 )10 )
11 11
12 // builtinFunc implements a built-in function. It runs inside the evaluator so12 // builtinFunc implements a built-in function. It runs inside the evaluator so
13-// that it can charge the output budget and watch the context.13+// that it can charge the output budget and watch the context, and it receives
14-type builtinFunc func(e *evaluator, args []Value) (Value, error)14+// the call, not the evaluated arguments: like the runner's functions
15+// (Sdk/Functions/*.cs) it evaluates only the arguments it needs, in its own
16+// order, so an argument that is never reached cannot fail.
17+type builtinFunc func(e *evaluator, c *FunctionCall) (Value, error)
15 18
16 // builtinImpls are the implementations of the built-in functions, keyed by19 // builtinImpls are the implementations of the built-in functions, keyed by
17-// lower-cased name. Arity is validated by the parser, so each may assume its20+// upper-cased name (see foldKey). Arity is validated by the parser, so each
18-// documented argument count.21+// may assume its documented argument count.
19-var builtinImpls = map[string]builtinFunc{22+var builtinImpls map[string]builtinFunc
20- "case": funcCase,23+
21- "contains": funcContains,24+// The map is filled in init: the implementations call back into the evaluator,
22- "endswith": funcEndsWith,25+// which would make a package-level initializer cyclic.
23- "format": funcFormat,26+func init() {
24- "fromjson": funcFromJSON,27+ builtinImpls = map[string]builtinFunc{
25- "join": funcJoin,28+ "CASE": funcCase,
26- "startswith": funcStartsWith,29+ "CONTAINS": funcContains,
27- "tojson": funcToJSON,30+ "ENDSWITH": funcEndsWith,
31+ "FORMAT": funcFormat,
32+ "FROMJSON": funcFromJSON,
33+ "JOIN": funcJoin,
34+ "STARTSWITH": funcStartsWith,
35+ "TOJSON": funcToJSON,
36+ }
28 }37 }
29 38
30-// funcCase is case(pred1, val1, pred2, val2, ..., default).39+// funcCase is case(pred1, val1, pred2, val2, ..., default): the predicates are
31-//40+// evaluated in order up to the first true one, then only its value (Case.cs).
32-// The JavaScript reference ships it behind an experimental feature flag41+// A predicate that is not a boolean is an error. The JavaScript reference
33-// (allowCaseFunction) that only affects editor completion; the parser always42+// ships case behind an experimental feature flag that only affects editor
34-// accepts it, and so does this package.43+// completion; the parser always accepts it, and so does the runner.
35-func funcCase(e *evaluator, args []Value) (Value, error) {44+func funcCase(e *evaluator, c *FunctionCall) (Value, error) {
36- for i := 0; i < len(args)-1; i += 2 {45+ n := len(c.Args)
37- pred, ok := normalize(args[i]).(Boolean)46+ if n%2 == 0 {
47+ return nil, evalErrorf("case requires an odd number of arguments")
48+ }
49+ for i := 0; i < n-1; i += 2 {
50+ p, err := e.eval(c.Args[i])
51+ if err != nil {
52+ return nil, err
53+ }
54+ pred, ok := normalize(p).(Boolean)
38 if !ok {55 if !ok {
39 return nil, evalErrorf("case predicate must evaluate to a boolean value")56 return nil, evalErrorf("case predicate must evaluate to a boolean value")
40 }57 }
41 if pred {58 if pred {
42- return args[i+1], nil59+ return e.eval(c.Args[i+1])
43 }60 }
44 }61 }
45- return args[len(args)-1], nil62+ return e.eval(c.Args[n-1])
46 }63 }
47 64
48-// funcContains is contains(search, item): substring test, case-insensitive65+// funcContains is contains(search, item) (Contains.cs): when search is a
49-// via toLowerCase, when both are primitive; membership by == in an array;66+// primitive and so is item, a case-insensitive (OrdinalIgnoreCase) substring
50-// false otherwise.67+// test on their string forms; when search is a non-empty array, == against each
51-func funcContains(e *evaluator, args []Value) (Value, error) {68+// element; false otherwise. item is only evaluated when it can matter.
52- left, right := normalize(args[0]), normalize(args[1])69+func funcContains(e *evaluator, c *FunctionCall) (Value, error) {
70+ l, err := e.eval(c.Args[0])
71+ if err != nil {
72+ return nil, err
73+ }
74+ left := normalize(l)
53 switch {75 switch {
54 case isPrimitive(left):76 case isPrimitive(left):
55- if isPrimitive(right) {77+ r, err := e.eval(c.Args[1])
56- return Boolean(jsIncludes(jsToLower(CoerceString(left)), jsToLower(CoerceString(right)))), nil78+ if err != nil {
79+ return nil, err
80+ }
81+ if right := normalize(r); isPrimitive(right) {
82+ return Boolean(jsIncludes(ordinalFold(CoerceString(left)), ordinalFold(CoerceString(right)))), nil
83+ }
84+ case left.Kind() == KindArray && len(left.(*Array).items) > 0:
85+ right, err := e.eval(c.Args[1])
86+ if err != nil {
87+ return nil, err
57 }88 }
58- case left.Kind() == KindArray:
59 for _, v := range left.(*Array).items {89 for _, v := range left.(*Array).items {
60 if err := e.tick(); err != nil {90 if err := e.tick(); err != nil {
61 return nil, err91 return nil, err
@@ -68,40 +98,57 @@ func funcContains(e *evaluator, args []Value) (Value, error) {
68 return Boolean(false), nil98 return Boolean(false), nil
69 }99 }
70 100
71-func funcStartsWith(e *evaluator, args []Value) (Value, error) {101+func funcStartsWith(e *evaluator, c *FunctionCall) (Value, error) {
72- ls, rs, ok := primitivePair(args)102+ ls, rs, ok, err := e.primitivePair(c)
73- return Boolean(ok && jsHasPrefix(toUpperSpecial(ls), toUpperSpecial(rs))), nil103+ return Boolean(ok && jsHasPrefix(ordinalFold(ls), ordinalFold(rs))), err
74 }104 }
75 105
76-func funcEndsWith(e *evaluator, args []Value) (Value, error) {106+func funcEndsWith(e *evaluator, c *FunctionCall) (Value, error) {
77- ls, rs, ok := primitivePair(args)107+ ls, rs, ok, err := e.primitivePair(c)
78- return Boolean(ok && jsHasSuffix(toUpperSpecial(ls), toUpperSpecial(rs))), nil108+ return Boolean(ok && jsHasSuffix(ordinalFold(ls), ordinalFold(rs))), err
79 }109 }
80 110
81-// primitivePair returns the string forms of the two arguments, and false111+// primitivePair evaluates the two arguments, the second only if the first is
82-// when either is an array or a dictionary.112+// primitive, and returns their string forms; ok is false when either is an
83-func primitivePair(args []Value) (string, string, bool) {113+// array or a dictionary.
84- if !isPrimitive(args[0]) || !isPrimitive(args[1]) {114+func (e *evaluator) primitivePair(c *FunctionCall) (ls, rs string, ok bool, err error) {
85- return "", "", false115+ l, err := e.eval(c.Args[0])
116+ if err != nil || !isPrimitive(l) {
117+ return "", "", false, err
86 }118 }
87- return CoerceString(args[0]), CoerceString(args[1]), true119+ r, err := e.eval(c.Args[1])
120+ if err != nil || !isPrimitive(r) {
121+ return "", "", false, err
122+ }
123+ return CoerceString(l), CoerceString(r), true, nil
88 }124 }
89 125
90-// funcJoin is join(array, separator). A primitive first argument is just126+// funcJoin is join(array, separator) (Join.cs). A primitive first argument is
91-// converted to a string, ignoring the separator; a dictionary gives "". A127+// just converted to a string, ignoring the separator; a dictionary or an empty
92-// separator that is not primitive means ",".128+// array gives "". The separator is evaluated only when there are at least two
93-func funcJoin(e *evaluator, args []Value) (Value, error) {129+// items to separate, and a separator that is not primitive means ",".
94- first := normalize(args[0])130+func funcJoin(e *evaluator, c *FunctionCall) (Value, error) {
131+ f, err := e.eval(c.Args[0])
132+ if err != nil {
133+ return nil, err
134+ }
135+ first := normalize(f)
95 if isPrimitive(first) {136 if isPrimitive(first) {
96 return String(CoerceString(first)), nil137 return String(CoerceString(first)), nil
97 }138 }
98 arr, ok := first.(*Array)139 arr, ok := first.(*Array)
99- if !ok {140+ if !ok || len(arr.items) == 0 {
100 return String(""), nil141 return String(""), nil
101 }142 }
102 sep := ","143 sep := ","
103- if len(args) > 1 && isPrimitive(args[1]) {144+ if len(arr.items) > 1 && len(c.Args) > 1 {
104- sep = CoerceString(args[1])145+ sv, err := e.eval(c.Args[1])
146+ if err != nil {
147+ return nil, err
148+ }
149+ if isPrimitive(sv) {
150+ sep = CoerceString(sv)
151+ }
105 }152 }
106 var out strings.Builder153 var out strings.Builder
107 for i, it := range arr.items {154 for i, it := range arr.items {
@@ -121,17 +168,21 @@ func funcJoin(e *evaluator, args []Value) (Value, error) {
121 }168 }
122 out.WriteString(part)169 out.WriteString(part)
123 }170 }
124- return String(normalizeWTF8(out.String())), nil171+ return String(out.String()), nil
125 }172 }
126 173
127 // funcToJSON is toJSON(value). The output counts against the budget and is174 // funcToJSON is toJSON(value). The output counts against the budget and is
128 // cut off as soon as it exceeds what is left.175 // cut off as soon as it exceeds what is left.
129-func funcToJSON(e *evaluator, args []Value) (Value, error) {176+func funcToJSON(e *evaluator, c *FunctionCall) (Value, error) {
177+ v, err := e.eval(c.Args[0])
178+ if err != nil {
179+ return nil, err
180+ }
130 max := -1181 max := -1
131 if e.limit >= 0 {182 if e.limit >= 0 {
132 max = e.limit - e.used183 max = e.limit - e.used
133 }184 }
134- out, err := EncodeJSON(args[0], max)185+ out, err := EncodeJSON(v, max)
135 switch {186 switch {
136 case errors.Is(err, errJSONTooBig):187 case errors.Is(err, errJSONTooBig):
137 return nil, e.budgetError()188 return nil, e.budgetError()
@@ -145,11 +196,15 @@ func funcToJSON(e *evaluator, args []Value) (Value, error) {
145 }196 }
146 197
147 // funcFromJSON is fromJSON(value). Blank input is "empty input"; anything198 // funcFromJSON is fromJSON(value). Blank input is "empty input"; anything
148-// that is not valid JSON is "Error parsing JSON when evaluating fromJson"199+// that is not readable by Json.NET is "Error parsing JSON when evaluating
149-// wrapping the syntax error.200+// fromJson" wrapping the reader's error.
150-func funcFromJSON(e *evaluator, args []Value) (Value, error) {201+func funcFromJSON(e *evaluator, c *FunctionCall) (Value, error) {
151- in := CoerceString(args[0])202+ a, err := e.eval(c.Args[0])
152- if jsTrim(in) == "" {203+ if err != nil {
204+ return nil, err
205+ }
206+ in := CoerceString(a)
207+ if strings.TrimFunc(in, isNetSpace) == "" {
153 return nil, evalErrorf("empty input")208 return nil, evalErrorf("empty input")
154 }209 }
155 v, err := FromJSON([]byte(in))210 v, err := FromJSON([]byte(in))
@@ -159,13 +214,39 @@ func funcFromJSON(e *evaluator, args []Value) (Value, error) {
159 return v, nil214 return v, nil
160 }215 }
161 216
162-// funcFormat is format(fmt, args...): "{N}" inserts argument N, "{{" and "}}"217+// formatPiece is a segment of the result of format(): literal text, or the
163-// are literal braces. Anything between the digits of "{N" and the closing218+// index of an argument (evaluated when the result is assembled) with the
164-// brace is ignored ("{0:x}" is "{0}"). Every other use of a brace is an219+// format specifiers written after the colon.
165-// error, as is a reference to a missing argument.220+type formatPiece struct {
166-func funcFormat(e *evaluator, args []Value) (Value, error) {221+ text string
167- fs := CoerceString(args[0])222+ arg int
168- var out strings.Builder223+ specs string
224+ isArg bool
225+}
226+
227+// funcFormat is format(fmt, args...) (Format.cs): "{N}" inserts argument N,
228+// "{{" and "}}" are literal braces. The format string is scanned completely
229+// first (so a syntax error or a reference to a missing argument is reported
230+// before any argument is evaluated), then the arguments are evaluated, once
231+// each, in the order they appear. N is at most 255 (a byte). Format specifiers
232+// ("{0:x}") are not supported for any value: they are an error.
233+func funcFormat(e *evaluator, c *FunctionCall) (Value, error) {
234+ f, err := e.eval(c.Args[0])
235+ if err != nil {
236+ return nil, err
237+ }
238+ fs := CoerceString(f)
239+ var pieces []formatPiece
240+ text := func(t string) error {
241+ if t == "" {
242+ return nil
243+ }
244+ if err := e.charge(utf16Len(t)); err != nil {
245+ return err
246+ }
247+ pieces = append(pieces, formatPiece{text: t})
248+ return nil
249+ }
169 index := 0250 index := 0
170 for index < len(fs) {251 for index < len(fs) {
171 lbrace := indexByteFrom(fs, '{', index)252 lbrace := indexByteFrom(fs, '{', index)
@@ -174,27 +255,26 @@ func funcFormat(e *evaluator, args []Value) (Value, error) {
174 case lbrace >= 0 && (rbrace < 0 || rbrace > lbrace):255 case lbrace >= 0 && (rbrace < 0 || rbrace > lbrace):
175 // Escaped left brace.256 // Escaped left brace.
176 if charAt(fs, lbrace+1) == '{' {257 if charAt(fs, lbrace+1) == '{' {
177- if err := e.charge(lbrace + 1 - index); err != nil {258+ if err := text(fs[index : lbrace+1]); err != nil {
178 return nil, err259 return nil, err
179 }260 }
180- out.WriteString(fs[index : lbrace+1])
181 index = lbrace + 2261 index = lbrace + 2
182 continue262 continue
183 }263 }
184 // Left brace, number, optional format specifiers, right brace.264 // Left brace, number, optional format specifiers, right brace.
185 if rbrace > lbrace+1 {265 if rbrace > lbrace+1 {
186- if argIndex, ok := readArgIndex(fs, lbrace+1); ok {266+ if argIndex, end, ok := readArgIndex(fs, lbrace+1); ok {
187- if 1+argIndex > float64(len(args)-1) {267+ if specs, closing, ok := readFormatSpecifiers(fs, end+1); ok {
188- return nil, evalErrorf("The following format string references more arguments than were supplied: %s", fs)268+ if argIndex > len(c.Args)-2 {
269+ return nil, evalErrorf("The following format string references more arguments than were supplied: %s", fs)
270+ }
271+ if err := text(fs[index:lbrace]); err != nil {
272+ return nil, err
273+ }
274+ pieces = append(pieces, formatPiece{arg: argIndex, specs: specs, isArg: true})
275+ index = closing + 1
276+ continue
189 }277 }
190- arg := CoerceString(args[1+int(argIndex)])
191- if err := e.charge(lbrace - index + len(arg)); err != nil {
192- return nil, err
193- }
194- out.WriteString(fs[index:lbrace])
195- out.WriteString(arg)
196- index = rbrace + 1
197- continue
198 }278 }
199 }279 }
200 return nil, evalErrorf("The following format string is invalid: %s", fs)280 return nil, evalErrorf("The following format string is invalid: %s", fs)
@@ -203,20 +283,50 @@ func funcFormat(e *evaluator, args []Value) (Value, error) {
203 if charAt(fs, rbrace+1) != '}' {283 if charAt(fs, rbrace+1) != '}' {
204 return nil, evalErrorf("The following format string is invalid: %s", fs)284 return nil, evalErrorf("The following format string is invalid: %s", fs)
205 }285 }
206- if err := e.charge(rbrace + 1 - index); err != nil {286+ if err := text(fs[index : rbrace+1]); err != nil {
207 return nil, err287 return nil, err
208 }288 }
209- out.WriteString(fs[index : rbrace+1])
210 index = rbrace + 2289 index = rbrace + 2
211 default:290 default:
212- if err := e.charge(len(fs) - index); err != nil {291+ if err := text(fs[index:]); err != nil {
213 return nil, err292 return nil, err
214 }293 }
215- out.WriteString(fs[index:])
216 index = len(fs)294 index = len(fs)
217 }295 }
218 }296 }
219- return String(normalizeWTF8(out.String())), nil297+ // Assemble: arguments are evaluated lazily, once each, in the order of
298+ // their first appearance.
299+ type argValue struct {
300+ str string
301+ kind Kind
302+ done bool
303+ }
304+ cache := make([]argValue, len(c.Args)-1)
305+ var out strings.Builder
306+ for _, p := range pieces {
307+ if !p.isArg {
308+ out.WriteString(p.text)
309+ continue
310+ }
311+ av := &cache[p.arg]
312+ if !av.done {
313+ v, err := e.eval(c.Args[1+p.arg])
314+ if err != nil {
315+ return nil, err
316+ }
317+ *av = argValue{str: CoerceString(v), kind: normalize(v).Kind(), done: true}
318+ }
319+ if p.specs != "" {
320+ return nil, evalErrorf("The format specifiers '%s' are not valid for objects of type '%s'", p.specs, av.kind)
321+ }
322+ if av.str != "" {
323+ if err := e.charge(utf16Len(av.str)); err != nil {
324+ return nil, err
325+ }
326+ }
327+ out.WriteString(av.str)
328+ }
329+ return String(out.String()), nil
220 }330 }
221 331
222 func indexByteFrom(s string, c byte, from int) int {332 func indexByteFrom(s string, c byte, from int) int {
@@ -238,20 +348,65 @@ func charAt(s string, i int) byte {
238 return 0348 return 0
239 }349 }
240 350
241-// readArgIndex reads the decimal digits at s[start:]. The index is a float351+// readArgIndex reads the digits at s[start:] as the index of a format
242-// because parseInt in the reference yields Infinity for absurdly long digit352+// argument: a byte, so at most 255 (Byte.TryParse). It returns the index of the
243-// runs, which then fails the argument-count check.353+// last digit. Digits are Unicode decimal digits for the scan but only ASCII
244-func readArgIndex(s string, start int) (float64, bool) {354+// ones parse, so any other digit makes the format string invalid.
355+func readArgIndex(s string, start int) (value, end int, ok bool) {
245 n := 0356 n := 0
246- for charAt(s, start+n) >= '0' && charAt(s, start+n) <= '9' {357+ for i := start; i < len(s); {
247- n++358+ r, w := decodeRune(s[i:])
359+ if !unicode.IsDigit(r) {
360+ break
361+ }
362+ i += w
363+ n = i - start
248 }364 }
249 if n == 0 {365 if n == 0 {
250- return 0, false366+ return 0, 0, false
367+ }
368+ v, err := strconv.ParseUint(s[start:start+n], 10, 8)
369+ if err != nil || !isASCII(s[start:start+n]) {
370+ return 0, 0, false
371+ }
372+ return int(v), start + n - 1, true
373+}
374+
375+// readFormatSpecifiers reads what follows the digits of "{N": either the
376+// closing brace, or ":" and specifiers up to a closing brace, where "}}" is a
377+// literal brace inside the specifiers. It returns the specifiers and the index
378+// of the closing brace.
379+func readFormatSpecifiers(s string, start int) (specs string, closing int, ok bool) {
380+ switch charAt(s, start) {
381+ case '}':
382+ return "", start, true
383+ case ':':
384+ default:
385+ return "", 0, false
251 }386 }
252- f, _ := strconv.ParseFloat(s[start:start+n], 64)387+ var b strings.Builder
253- if math.IsNaN(f) {388+ for i := start + 1; i < len(s); {
254- return 0, false389+ if s[i] != '}' {
390+ b.WriteByte(s[i])
391+ i++
392+ } else if charAt(s, i+1) == '}' {
393+ b.WriteByte('}')
394+ i += 2
395+ } else {
396+ return b.String(), i, true
397+ }
398+ }
399+ return "", 0, false
400+}
401+
402+// utf16Len is the length of s in UTF-16 code units, the length of a .NET string.
403+func utf16Len(s string) int {
404+ n := 0
405+ for _, r := range s {
406+ n++
407+ if r >= 0x10000 {
408+ n++
409+ }
255 }410 }
256- return f, true411+ return n
257 }412 }
M expr/fuzz_test.go +22 -3
@@ -4,6 +4,7 @@ package expr
4 4
5 import (5 import (
6 "context"6 "context"
7+ "regexp"
7 "strings"8 "strings"
8 "testing"9 "testing"
9 "unicode/utf8"10 "unicode/utf8"
@@ -81,7 +82,7 @@ func FuzzParse(f *testing.F) {
81 }82 }
82 return83 return
83 }84 }
84- if node == nil && strings.TrimFunc(s, func(r rune) bool { return r == ' ' || r == '\t' || r == '\r' || r == '\n' }) != "" {85+ if node == nil && strings.TrimFunc(s, isNetSpace) != "" {
85 t.Fatalf("empty tree for non-blank input %q", s)86 t.Fatalf("empty tree for non-blank input %q", s)
86 }87 }
87 if d := nodeDepth(node); d > 2*MaxParserDepth+2 {88 if d := nodeDepth(node); d > 2*MaxParserDepth+2 {
@@ -157,7 +158,7 @@ func FuzzEvaluate(f *testing.F) {
157 if !utf8.ValidString(j) {158 if !utf8.ValidString(j) {
158 t.Fatalf("toJSON produced invalid UTF-8: %q", j)159 t.Fatalf("toJSON produced invalid UTF-8: %q", j)
159 }160 }
160- if _, err := FromJSON([]byte(j)); err != nil {161+ if _, err := FromJSON([]byte(j)); err != nil && !hasNonFinite(j) {
161 t.Fatalf("toJSON output does not parse: %v\n%s", err, j)162 t.Fatalf("toJSON output does not parse: %v\n%s", err, j)
162 }163 }
163 for k, c := range ctx.Contexts {164 for k, c := range ctx.Contexts {
@@ -174,6 +175,11 @@ func FuzzFromJSON(f *testing.F) {
174 for _, s := range []string{`null`, `{"a":[1,2,{"b":null}]}`, `"\ud83d\ude00"`, `{"1":1,"a":2,"01":3}`, `[1e999]`, `-0`, `"\ud800"`, `{"a":1,"A":2}`, `[`, `{"a"`, "\"\x01\""} {175 for _, s := range []string{`null`, `{"a":[1,2,{"b":null}]}`, `"\ud83d\ude00"`, `{"1":1,"a":2,"01":3}`, `[1e999]`, `-0`, `"\ud800"`, `{"a":1,"A":2}`, `[`, `{"a"`, "\"\x01\""} {
175 f.Add(s)176 f.Add(s)
176 }177 }
178+ // The corpus recorded from the runner: every input it was asked about
179+ // (leading zeros, slashes, commas, literals, mutations of those).
180+ for _, c := range loadFromJSONCases(f) {
181+ f.Add(c.In)
182+ }
177 f.Fuzz(func(t *testing.T, s string) {183 f.Fuzz(func(t *testing.T, s string) {
178 v, err := FromJSON([]byte(s))184 v, err := FromJSON([]byte(s))
179 if err != nil {185 if err != nil {
@@ -181,11 +187,24 @@ func FuzzFromJSON(f *testing.F) {
181 }187 }
182 j := ToJSON(v)188 j := ToJSON(v)
183 v2, err := FromJSON([]byte(j))189 v2, err := FromJSON([]byte(j))
190+ if err != nil && hasNonFinite(j) {
191+ return
192+ }
184 if err != nil {193 if err != nil {
185 t.Fatalf("round trip failed: %v\n%s", err, j)194 t.Fatalf("round trip failed: %v\n%s", err, j)
186 }195 }
187- if j2 := ToJSON(v2); j2 != j {196+ // A negative zero is written "-0" and read back as the integer 0.
197+ if j2 := ToJSON(v2); j2 != j && negZeroLine.ReplaceAllString(j, "${1}0${2}") != j2 {
188 t.Fatalf("round trip changed output:\n%s\n%s", j, j2)198 t.Fatalf("round trip changed output:\n%s\n%s", j, j2)
189 }199 }
190 })200 })
191 }201 }
202+
203+// hasNonFinite reports whether toJSON output holds NaN or an infinity, which
204+// the runner writes as bare words (not JSON).
205+func hasNonFinite(j string) bool {
206+ return strings.Contains(j, "NaN") || strings.Contains(j, "Infinity")
207+}
208+
209+// negZeroLine matches a written negative zero: "-0" alone on its value.
210+var negZeroLine = regexp.MustCompile(`(^|[\s\[:,])-0($|[\s,\]}])`)
A expr/gen/fold.go +61 -0
@@ -0,0 +1,61 @@
1+// SPDX-License-Identifier: MIT
2+
3+//go:build ignore
4+
5+// Command fold reads the "XXXX YYYY" lines printed by `runner.dll --fold`
6+// (see gen/runner) on stdin and writes fold_tables.go: the case folding of
7+// .NET's StringComparison.OrdinalIgnoreCase, code point by code point.
8+package main
9+
10+import (
11+ "bufio"
12+ "flag"
13+ "fmt"
14+ "go/format"
15+ "os"
16+ "strconv"
17+ "strings"
18+)
19+
20+func main() {
21+ out := flag.String("o", "", "output file (default stdout)")
22+ flag.Parse()
23+ var b strings.Builder
24+ b.WriteString("// SPDX-License-Identifier: MIT\n\n// Code generated by gen/fold.go from `runner.dll --fold` (gen/runner); DO NOT EDIT.\n\npackage expr\n\n")
25+ b.WriteString("// ordinalUpper maps a code point to the one that .NET's OrdinalIgnoreCase comparison treats\n// it as, sorted by the first element. Code points that are not listed stand for themselves.\n")
26+ b.WriteString("var ordinalUpper = [...][2]rune{\n")
27+ sc := bufio.NewScanner(os.Stdin)
28+ n := 0
29+ for sc.Scan() {
30+ f := strings.Fields(sc.Text())
31+ if len(f) != 2 {
32+ continue
33+ }
34+ from, err1 := strconv.ParseInt(f[0], 16, 32)
35+ to, err2 := strconv.ParseInt(f[1], 16, 32)
36+ if err1 != nil || err2 != nil {
37+ fmt.Fprintln(os.Stderr, "bad line:", sc.Text())
38+ os.Exit(1)
39+ }
40+ fmt.Fprintf(&b, "\t{0x%04X, 0x%04X},\n", from, to)
41+ n++
42+ }
43+ b.WriteString("}\n")
44+ src, err := format.Source([]byte(b.String()))
45+ if err != nil {
46+ fmt.Fprintln(os.Stderr, err)
47+ os.Exit(1)
48+ }
49+ if n == 0 {
50+ fmt.Fprintln(os.Stderr, "no input")
51+ os.Exit(1)
52+ }
53+ if *out == "" {
54+ os.Stdout.Write(src)
55+ return
56+ }
57+ if err := os.WriteFile(*out, src, 0o644); err != nil {
58+ fmt.Fprintln(os.Stderr, err)
59+ os.Exit(1)
60+ }
61+}
A expr/gen/runner-new/.gitignore +2 -0
@@ -0,0 +1,2 @@
1+bin/
2+obj/
A expr/gen/runner-new/Program.cs +132 -0
@@ -0,0 +1,132 @@
1+// SPDX-License-Identifier: MIT
2+
3+using System;
4+using System.Collections.Generic;
5+using System.IO;
6+using System.Linq;
7+using System.Text;
8+using GitHub.Actions.Expressions;
9+using GitHub.Actions.Expressions.Data;
10+using GitHub.Actions.Expressions.Sdk;
11+using GitHub.Actions.Expressions.Sdk.Functions;
12+using Newtonsoft.Json;
13+using Newtonsoft.Json.Linq;
14+
15+// Differential harness: runs expressions through the runner's own
16+// Expressions2 code (compiled from the runner repository) and prints one JSON
17+// line per case.
18+//
19+// Input (argv[0]): JSON {"contexts": {name: json}, "cases": [{"e": expr, "ctx": {name: json} | null}]}
20+// Output: JSON lines {"k": kind, "s": string form, "j": toJson, "err": {"t": "parse"|"eval", "m": message}}
21+
22+internal sealed class CtxNode : NamedValue
23+{
24+ protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
25+ {
26+ resultMemory = null;
27+ return (context.State as DictionaryExpressionData)[Name];
28+ }
29+}
30+
31+internal sealed class Success : Function { protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m) { m = null; return true; } }
32+internal sealed class Always : Function { protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m) { m = null; return true; } }
33+internal sealed class HashFiles : Function
34+{
35+ protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m)
36+ {
37+ m = null;
38+ var s = "hash:";
39+ for (var i = 0; i < Parameters.Count; i++)
40+ {
41+ if (i > 0) s += "|";
42+ s += Parameters[i].Evaluate(c).ConvertToString();
43+ }
44+ return s;
45+ }
46+}
47+
48+internal static class P
49+{
50+ static DictionaryExpressionData Load(JObject ctx)
51+ {
52+ var d = new DictionaryExpressionData();
53+ foreach (var p in ctx.Properties())
54+ {
55+ var token = p.Value;
56+ d[p.Name] = token.ToExpressionData();
57+ }
58+ return d;
59+ }
60+
61+ static int Main(string[] args)
62+ {
63+ JObject root; using (var jr0 = new JsonTextReader(new StringReader(File.ReadAllText(args[0]))) { DateParseHandling = DateParseHandling.None }) root = JObject.Load(jr0);
64+ var defaultCtx = (JObject)root["contexts"];
65+ var funcs = new IFunctionInfo[]
66+ {
67+ new FunctionInfo<Success>("success", 0, 0),
68+ new FunctionInfo<Always>("always", 0, 0),
69+ new FunctionInfo<HashFiles>("hashFiles", 1, 255),
70+ };
71+ var output = new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false));
72+ var defaultData = Load(defaultCtx);
73+ foreach (var c in (JArray)root["cases"])
74+ {
75+ var e = (string)c["e"];
76+ var custom = c["ctx"] as JObject;
77+ var data = custom != null ? Load(custom) : defaultData;
78+ var names = (custom ?? defaultCtx).Properties().Select(p => p.Name);
79+ var res = new JObject();
80+ IExpressionNode tree = null;
81+ try
82+ {
83+ tree = new ExpressionParser().CreateTree(e, null, names.Select(n => (INamedValueInfo)new NamedValueInfo<CtxNode>(n)).ToList(), funcs);
84+ }
85+ catch (Exception ex)
86+ {
87+ res["err"] = new JObject { ["t"] = "parse", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
88+ }
89+ if (res["err"] == null)
90+ {
91+ if (tree == null)
92+ {
93+ res["empty"] = true;
94+ }
95+ else
96+ {
97+ try
98+ {
99+ var r = tree.Evaluate(null, null, data, null);
100+ res["k"] = r.Kind.ToString();
101+ res["s"] = r.ConvertToString();
102+ // toJson of the result
103+ var tj = new ToJson();
104+ tj.AddParameter(new Literal(r.Value));
105+ try
106+ {
107+ var jr = ((IExpressionNode)tj).Evaluate(null, null, data, null);
108+ res["j"] = jr.ConvertToString();
109+ }
110+ catch (Exception ex2)
111+ {
112+ res["jerr"] = ex2.Message;
113+ }
114+ }
115+ catch (Exception ex)
116+ {
117+ res["err"] = new JObject { ["t"] = "eval", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
118+ }
119+ }
120+ }
121+ // ASCII-escaped, so that a lone surrogate (which UTF-8 cannot carry) survives.
122+ var sw = new StringWriter();
123+ using (var jw = new JsonTextWriter(sw) { StringEscapeHandling = StringEscapeHandling.EscapeNonAscii, Formatting = Formatting.None })
124+ {
125+ res.WriteTo(jw);
126+ }
127+ output.WriteLine(sw.ToString());
128+ }
129+ output.Flush();
130+ return 0;
131+ }
132+}
A expr/gen/runner-new/engines.mjs +73 -0
@@ -0,0 +1,73 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Runs the oracle corpus, the fromJSON corpus, the number corpus and the string
4+// comparison corpus through both expression engines of actions/runner and lists
5+// where they differ:
6+// legacy src/Sdk/DTExpressions2 (driver ../runner)
7+// newer src/Sdk/Expressions (driver ./runner-new)
8+//
9+// dotnet build -c Release expr/gen/runner -p:RunnerSrc=$RUNNER_CLONE/src/Sdk
10+// dotnet build -c Release expr/gen/runner-new -p:RunnerSrc=$RUNNER_CLONE/src/Sdk
11+// RUNNER_CLONE=<clone> node expr/gen/runner-new/engines.mjs
12+//
13+// The exception message of a fromJSON failure is not compared (the engines word
14+// it differently); everything else must be identical. Exit code 1 if anything
15+// differs beyond the known difference: negative zero (the newer engine prints 0).
16+
17+import { execFileSync } from "node:child_process";
18+import { readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
19+import { tmpdir } from "node:os";
20+import { join, dirname } from "node:path";
21+import { fileURLToPath } from "node:url";
22+import { gunzipSync } from "node:zlib";
23+import { checkEnvironment } from "../runner/env.mjs";
24+
25+const here = dirname(fileURLToPath(import.meta.url));
26+const testdata = join(here, "../../testdata");
27+checkEnvironment(process.argv[2]);
28+
29+const dec = h => (h === "-" ? "" : h.split(".").map(u => String.fromCharCode(parseInt(u, 16))).join(""));
30+const lit = s => "'" + s.replaceAll("'", "''") + "'";
31+const hasLone = s => /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.test(s);
32+
33+const oracle = JSON.parse(gunzipSync(readFileSync(join(testdata, "oracle.json.gz"))));
34+const corpora = { oracle: { contexts: oracle.contexts, cases: oracle.cases.map(c => c.e) } };
35+const exprs = [];
36+for (const c of JSON.parse(readFileSync(join(testdata, "fromjson_cases.json"), "utf8"))) exprs.push(`toJSON(fromJSON(${lit(c.in)}))`);
37+for (const l of readFileSync(join(testdata, "number_cases.txt"), "utf8").trim().split("\n")) {
38+ const s = dec(l.split(" ")[0]);
39+ if (hasLone(s)) continue;
40+ for (const op of ["== 0", "== 1", "> 255", "< 0"]) exprs.push(`${lit(s)} ${op}`);
41+}
42+for (const l of readFileSync(join(testdata, "ordinal_cases.txt"), "utf8").trim().split("\n")) {
43+ const [a, b] = l.split(" ");
44+ const A = dec(a), B = dec(b);
45+ if (hasLone(A + B)) continue;
46+ exprs.push(`${lit(A)} == ${lit(B)}`, `${lit(A)} < ${lit(B)}`, `contains(${lit(A)}, ${lit(B)})`, `startsWith(${lit(A)}, ${lit(B)})`, `endsWith(${lit(A)}, ${lit(B)})`);
47+}
48+corpora.strings = { contexts: {}, cases: exprs };
49+
50+const run = (dll, input) => execFileSync("dotnet", [dll, input], { maxBuffer: 1 << 30 }).toString("utf8").split("\n").filter(Boolean).map(l => JSON.parse(l));
51+const sig = r => (r.err ? JSON.stringify(["err", r.err.t, r.err.x === "JsonReaderException" ? r.err.x : r.err.m]) : JSON.stringify(r));
52+
53+const tmp = mkdtempSync(join(tmpdir(), "engines-"));
54+let unexpected = 0, total = 0;
55+for (const [name, c] of Object.entries(corpora)) {
56+ const input = join(tmp, name + ".json");
57+ writeFileSync(input, JSON.stringify({ contexts: c.contexts, cases: c.cases.map(e => ({ e })) }));
58+ const a = run(join(here, "../runner/bin/Release/net9.0/runner.dll"), input);
59+ const b = run(join(here, "bin/Release/net9.0/runner-new.dll"), input);
60+ let diffs = 0;
61+ a.forEach((r, i) => {
62+ total++;
63+ if (sig(r) === sig(b[i])) return;
64+ diffs++;
65+ const known = /^(toJSON\(-0\)|-0|toJSON\(fromJSON\('-\.0'\)\))$/.test(c.cases[i]);
66+ if (!known) unexpected++;
67+ console.log(`${known ? "known " : "DIFF "} ${name}: ${JSON.stringify(c.cases[i].slice(0, 80))}\n legacy ${sig(r).slice(0, 100)}\n newer ${sig(b[i]).slice(0, 100)}`);
68+ });
69+ console.log(`${name}: ${a.length} expressions, ${diffs} differences`);
70+}
71+rmSync(tmp, { recursive: true });
72+console.log(`${total} expressions, ${unexpected} unexpected differences`);
73+process.exit(unexpected ? 1 : 0);
A expr/gen/runner-new/runner-new.csproj +24 -0
@@ -0,0 +1,24 @@
1+<Project Sdk="Microsoft.NET.Sdk">
2+ <!-- Same driver as ../runner, built on the newer engine of actions/runner,
3+ src/Sdk/Expressions (namespace GitHub.Actions.Expressions), the one that
4+ WorkflowTemplateEvaluator runs next to the legacy engine to compare.
5+ Used by engines.mjs to find out where the two engines differ. -->
6+ <PropertyGroup>
7+ <OutputType>Exe</OutputType>
8+ <TargetFramework>net9.0</TargetFramework>
9+ <Nullable>disable</Nullable>
10+ <LangVersion>11.0</LangVersion>
11+ <NoWarn>NU1701;CS8632;CS0108;CS0114;CS1591;CS0618;SYSLIB0050</NoWarn>
12+ <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
13+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
14+ </PropertyGroup>
15+ <Target Name="RequireRunnerSrc" BeforeTargets="BeforeBuild;CoreCompile">
16+ <Error Condition="'$(RunnerSrc)' == '' Or !Exists('$(RunnerSrc)/Expressions')" Text="Set RunnerSrc to the src/Sdk directory of a clone of actions/runner: dotnet build -c Release expr/gen/runner-new -p:RunnerSrc=&lt;clone&gt;/src/Sdk" />
17+ </Target>
18+ <ItemGroup>
19+ <Compile Include="$(RunnerSrc)/Expressions/**/*.cs" />
20+ <Compile Include="Program.cs" />
21+ <EmbeddedResource Include="$(RunnerSrc)/Expressions/Resources/ExpressionResources.resx" LogicalName="GitHub.Actions.Expressions.ExpressionResources.resources" />
22+ <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
23+ </ItemGroup>
24+</Project>
A expr/gen/runner/.gitignore +2 -0
@@ -0,0 +1,2 @@
1+bin/
2+obj/
A expr/gen/runner/Program.cs +216 -0
@@ -0,0 +1,216 @@
1+// SPDX-License-Identifier: MIT
2+
3+using System;
4+using System.Collections.Generic;
5+using System.IO;
6+using System.Linq;
7+using System.Text;
8+using GitHub.DistributedTask.Expressions2;
9+using GitHub.DistributedTask.Expressions2.Sdk;
10+using GitHub.DistributedTask.Expressions2.Sdk.Functions;
11+using GitHub.DistributedTask.Pipelines.ContextData;
12+using Newtonsoft.Json;
13+using Newtonsoft.Json.Linq;
14+
15+// Differential harness: runs expressions through the runner's own
16+// Expressions2 code (compiled from the runner repository) and prints one JSON
17+// line per case.
18+//
19+// Input (argv[0]): JSON {"contexts": {name: json}, "cases": [{"e": expr, "ctx": {name: json} | null}]}
20+// Output: JSON lines {"k": kind, "s": string form, "j": toJson, "err": {"t": "parse"|"eval", "m": message}}
21+
22+internal sealed class CtxNode : NamedValue
23+{
24+ protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory)
25+ {
26+ resultMemory = null;
27+ return (context.State as DictionaryContextData)[Name];
28+ }
29+}
30+
31+internal sealed class Success : Function { protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m) { m = null; return true; } }
32+internal sealed class Always : Function { protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m) { m = null; return true; } }
33+internal sealed class HashFiles : Function
34+{
35+ protected override Object EvaluateCore(EvaluationContext c, out ResultMemory m)
36+ {
37+ m = null;
38+ var s = "hash:";
39+ for (var i = 0; i < Parameters.Count; i++)
40+ {
41+ if (i > 0) s += "|";
42+ s += Parameters[i].Evaluate(c).ConvertToString();
43+ }
44+ return s;
45+ }
46+}
47+
48+internal static class P
49+{
50+ static DictionaryContextData Load(JObject ctx)
51+ {
52+ var d = new DictionaryContextData();
53+ foreach (var p in ctx.Properties())
54+ {
55+ var token = p.Value;
56+ d[p.Name] = token.ToPipelineContextData();
57+ }
58+ return d;
59+ }
60+
61+ static int Main(string[] args)
62+ {
63+ if (args[0] == "--fold")
64+ {
65+ // Debug aid: the case folding of StringComparison.OrdinalIgnoreCase. Prints "XXXX YYYY" (hexadecimal
66+ // code points) for every code point c whose class representative m(c) differs from c, after checking
67+ // that string.Equals(a, b, OrdinalIgnoreCase) holds exactly when m(a) == m(b) over all the affected
68+ // code points. m(c) is ToUpperInvariant(c) when OrdinalIgnoreCase agrees that they are equal.
69+ var map = new SortedDictionary<int, int>();
70+ for (var c = 0; c <= 0x10FFFF; c++)
71+ {
72+ if (c >= 0xD800 && c <= 0xDFFF) continue;
73+ var str = char.ConvertFromUtf32(c);
74+ var up = str.ToUpperInvariant();
75+ if (up != str && char.ConvertToUtf32(up, 0) is var u && up.Length == char.ConvertFromUtf32(u).Length
76+ && string.Equals(str, up, StringComparison.OrdinalIgnoreCase))
77+ {
78+ map[c] = u;
79+ }
80+ }
81+ var members = new SortedSet<int>(map.Keys);
82+ foreach (var v in map.Values) members.Add(v);
83+ var list = new List<int>(members);
84+ Func<int, int> m = c => map.TryGetValue(c, out var x) ? x : c;
85+ var bad = 0;
86+ foreach (var a in list)
87+ {
88+ foreach (var b in list)
89+ {
90+ var eq = string.Equals(char.ConvertFromUtf32(a), char.ConvertFromUtf32(b), StringComparison.OrdinalIgnoreCase);
91+ if (eq != (m(a) == m(b))) { bad++; if (bad < 10) Console.Error.WriteLine($"inconsistent: {a:X} {b:X}"); }
92+ }
93+ }
94+ // Nothing outside the table may be equal to anything but itself.
95+ for (var c = 0; c <= 0x10FFFF; c++)
96+ {
97+ if (c >= 0xD800 && c <= 0xDFFF) continue;
98+ if (members.Contains(c)) continue;
99+ var str = char.ConvertFromUtf32(c);
100+ foreach (var d in new[] { c ^ 1, c + 1, c - 1 })
101+ {
102+ if (d < 0 || d > 0x10FFFF || (d >= 0xD800 && d <= 0xDFFF)) continue;
103+ if (string.Equals(str, char.ConvertFromUtf32(d), StringComparison.OrdinalIgnoreCase)) { bad++; if (bad < 10) Console.Error.WriteLine($"outside: {c:X} {d:X}"); }
104+ }
105+ }
106+ Console.Error.WriteLine($"{map.Count} mappings, {bad} inconsistencies");
107+ foreach (var kv in map) Console.WriteLine($"{kv.Key:X} {kv.Value:X}");
108+ return bad == 0 ? 0 : 1;
109+ }
110+ if (args[0] == "--cmp")
111+ {
112+ // Debug aid: each input line is two space-separated strings written as dot-separated hexadecimal UTF-16 code
113+ // units; prints Equals, Compare sign, Contains, StartsWith and EndsWith, all OrdinalIgnoreCase.
114+ Func<string, string> parse = h => { var sb = new StringBuilder(); if (h != "-") foreach (var u in h.Split('.')) sb.Append((char)Convert.ToInt32(u, 16)); return sb.ToString(); };
115+ foreach (var line in File.ReadLines(args[1]))
116+ {
117+ var parts = line.Split(' ');
118+ var a = parse(parts[0]);
119+ var b = parse(parts[1]);
120+ Console.WriteLine($"{(string.Equals(a, b, StringComparison.OrdinalIgnoreCase) ? 1 : 0)} {Math.Sign(string.Compare(a, b, StringComparison.OrdinalIgnoreCase))} {(a.IndexOf(b, StringComparison.OrdinalIgnoreCase) >= 0 ? 1 : 0)} {(a.StartsWith(b, StringComparison.OrdinalIgnoreCase) ? 1 : 0)} {(a.EndsWith(b, StringComparison.OrdinalIgnoreCase) ? 1 : 0)}");
121+ }
122+ return 0;
123+ }
124+ if (args[0] == "--num")
125+ {
126+ // Debug aid: each input line is a string as dot-separated hexadecimal UTF-16 code units ("-" for the
127+ // empty string); prints the IEEE 754 bits (hexadecimal) of ExpressionUtility.ParseNumber of it.
128+ foreach (var line in File.ReadLines(args[1]))
129+ {
130+ var sb = new StringBuilder();
131+ if (line.Trim() != "-") foreach (var u in line.Trim().Split('.')) sb.Append((char)Convert.ToInt32(u, 16));
132+ var d = ExpressionUtility.ParseNumber(sb.ToString());
133+ Console.WriteLine(double.IsNaN(d) ? "NaN" : BitConverter.DoubleToInt64Bits(d).ToString("x"));
134+ }
135+ return 0;
136+ }
137+ if (args[0] == "--g15")
138+ {
139+ // Debug aid: one hexadecimal IEEE 754 bit pattern per input line, the G15 form per output line.
140+ foreach (var line in File.ReadLines(args[1]))
141+ {
142+ var d = BitConverter.Int64BitsToDouble(Convert.ToInt64(line.Trim(), 16));
143+ Console.WriteLine(d.ToString("G15", System.Globalization.CultureInfo.InvariantCulture));
144+ }
145+ return 0;
146+ }
147+ JObject root; using (var jr0 = new JsonTextReader(new StringReader(File.ReadAllText(args[0]))) { DateParseHandling = DateParseHandling.None }) root = JObject.Load(jr0);
148+ var defaultCtx = (JObject)root["contexts"];
149+ var funcs = new IFunctionInfo[]
150+ {
151+ new FunctionInfo<Success>("success", 0, 0),
152+ new FunctionInfo<Always>("always", 0, 0),
153+ new FunctionInfo<HashFiles>("hashFiles", 1, 255),
154+ };
155+ var output = new StreamWriter(Console.OpenStandardOutput(), new UTF8Encoding(false));
156+ var defaultData = Load(defaultCtx);
157+ foreach (var c in (JArray)root["cases"])
158+ {
159+ var e = (string)c["e"];
160+ var custom = c["ctx"] as JObject;
161+ var data = custom != null ? Load(custom) : defaultData;
162+ var names = (custom ?? defaultCtx).Properties().Select(p => p.Name);
163+ var res = new JObject();
164+ IExpressionNode tree = null;
165+ try
166+ {
167+ tree = new ExpressionParser().CreateTree(e, null, names.Select(n => (INamedValueInfo)new NamedValueInfo<CtxNode>(n)).ToList(), funcs);
168+ }
169+ catch (Exception ex)
170+ {
171+ res["err"] = new JObject { ["t"] = "parse", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
172+ }
173+ if (res["err"] == null)
174+ {
175+ if (tree == null)
176+ {
177+ res["empty"] = true;
178+ }
179+ else
180+ {
181+ try
182+ {
183+ var r = tree.Evaluate(null, null, data, null);
184+ res["k"] = r.Kind.ToString();
185+ res["s"] = r.ConvertToString();
186+ // toJson of the result
187+ var tj = new ToJson();
188+ tj.AddParameter(new Literal(r.Value));
189+ try
190+ {
191+ var jr = ((IExpressionNode)tj).Evaluate(null, null, data, null);
192+ res["j"] = jr.ConvertToString();
193+ }
194+ catch (Exception ex2)
195+ {
196+ res["jerr"] = ex2.Message;
197+ }
198+ }
199+ catch (Exception ex)
200+ {
201+ res["err"] = new JObject { ["t"] = "eval", ["m"] = ex.Message, ["x"] = ex.GetType().Name };
202+ }
203+ }
204+ }
205+ // ASCII-escaped, so that a lone surrogate (which UTF-8 cannot carry) survives.
206+ var sw = new StringWriter();
207+ using (var jw = new JsonTextWriter(sw) { StringEscapeHandling = StringEscapeHandling.EscapeNonAscii, Formatting = Formatting.None })
208+ {
209+ res.WriteTo(jw);
210+ }
211+ output.WriteLine(sw.ToString());
212+ }
213+ output.Flush();
214+ return 0;
215+ }
216+}
A expr/gen/runner/README.md +36 -0
@@ -0,0 +1,36 @@
1+# Running the runner's expression engine
2+
3+`runner.csproj` compiles `src/Sdk/DTExpressions2` (the Expressions2 engine the
4+runner uses on github.com), its `ContextData` types and `DTLogging` from a clone
5+of https://github.com/actions/runner, replacing the few assemblies they need by
6+the stubs in `Stubs.cs`. `Program.cs` reads
7+`{"contexts": {...}, "cases": [{"e": "<expression>"}]}` and prints one JSON line
8+per case: `{"k": kind, "s": string form, "j": toJSON of the result}`, or
9+`{"err": {"t": "parse"|"eval", "m": message}}`.
10+
11+Requirements (checked, not assumed):
12+
13+- a clone of https://github.com/actions/runner at the commit in
14+ `runner-commit.txt` (`5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798`), clean in
15+ `src/Sdk`. `RunnerSrc` has no default: the build fails without it.
16+- the .NET SDK (the project targets `net9.0`) and a **9.0.x runtime**
17+ (`dotnet --list-runtimes`): number formatting and case folding come from the
18+ runtime, and `env.mjs` refuses to run on another line. No `global.json` pins
19+ the SDK because any SDK that can build `net9.0` works (the author's machine
20+ has SDK 10 with the 9.0 runtime); the runtime is what matters and is asserted.
21+
22+Build and run:
23+
24+ dotnet build -c Release expr/gen/runner -p:RunnerSrc=$RUNNER_CLONE/src/Sdk
25+ RUNNER_CLONE=<clone> node expr/gen/runner/answers.mjs | gzip -9 -n > expr/testdata/runner.json.gz
26+
27+The other scripts (`goldens.mjs`, `ordinal_cases.mjs`, `number_cases.mjs`,
28+`fromjson_cases.mjs`) use the same environment (`env.mjs`); `runner.dll
29+input.json` can also be run by hand. `TestRunnerCommit` checks that the
30+recorded data, `runner-commit.txt` and `PORTING.md` name the same commit.
31+
32+It is how the C# column of `expr/PORTING.md` was established: the 34 765
33+expressions of `testdata/oracle.json.gz` were run through it and compared with
34+this package. Requires the .NET SDK; nothing in `go test` depends on it.
35+The stubs are not the runner's code: `JsonUtility.ToString` is
36+`JsonConvert.SerializeObject` and the error resources are copied texts.
A expr/gen/runner/Stubs.cs +28 -0
@@ -0,0 +1,28 @@
1+// SPDX-License-Identifier: MIT
2+
3+using System;
4+namespace GitHub.Services.Common {
5+ public static class ArgumentUtility {
6+ public static void CheckForNull(Object v, String name) { if (v == null) throw new ArgumentNullException(name); }
7+ public static void CheckStringForNullOrEmpty(String v, String name, bool trim = false) { if (String.IsNullOrEmpty(v)) throw new ArgumentException(name); }
8+ }
9+}
10+namespace GitHub.Services.WebApi.Internal {
11+ [AttributeUsage(AttributeTargets.All)] public sealed class ClientIgnoreAttribute : Attribute {}
12+}
13+namespace GitHub.Services.WebApi {
14+ public abstract class VssSecureJsonConverter : Newtonsoft.Json.JsonConverter { public override void WriteJson(Newtonsoft.Json.JsonWriter w, object v, Newtonsoft.Json.JsonSerializer s) {} }
15+ public static class JsonUtility {
16+ // Same as the runner's JsonUtility.ToString for a string: default JsonTextWriter settings.
17+ public static String ToString(Object o) { return Newtonsoft.Json.JsonConvert.SerializeObject(o); }
18+ }
19+}
20+namespace GitHub.DistributedTask.Expressions {
21+ internal static class ExpressionResources {
22+ public static String ExceededAllowedMemory(object e) { return "The maximum allowed memory size was exceeded while evaluating the following expression: " + e; }
23+ public static String InvalidFormatArgIndex(object f) { return "The following format string references more arguments than were supplied: " + f; }
24+ public static String InvalidFormatSpecifiers(object s, object k) { return "The format specifiers '" + s + "' are not valid for objects of type '" + k + "'"; }
25+ public static String InvalidFormatString(object f) { return "The following format string is invalid: " + f; }
26+ }
27+}
28+namespace GitHub.Actions.Expressions.Data { internal class Dummy {} }
A expr/gen/runner/answers.mjs +31 -0
@@ -0,0 +1,31 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Generates testdata/runner.json.gz: what the C# runner's expression engine
4+// (actions/runner, src/Sdk/DTExpressions2) answers on the expressions of
5+// testdata/oracle.json.gz. Needs the .NET SDK and a clone of actions/runner:
6+//
7+// dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
8+// RUNNER_CLONE=<runner clone> node expr/gen/runner/answers.mjs | gzip -9 -n > expr/testdata/runner.json.gz
9+
10+import { readFileSync, writeFileSync, mkdtempSync } from "node:fs";
11+import { tmpdir } from "node:os";
12+import { join } from "node:path";
13+import { runDriver, here, checkEnvironment } from "./env.mjs";
14+import { gunzipSync } from "node:zlib";
15+
16+const env = checkEnvironment(process.argv[2]);
17+
18+const oracle = JSON.parse(gunzipSync(readFileSync(join(here, "../../testdata/oracle.json.gz"))));
19+const dir = mkdtempSync(join(tmpdir(), "runner-answers-"));
20+const input = join(dir, "in.json");
21+writeFileSync(input, JSON.stringify({ contexts: oracle.contexts, cases: oracle.cases.map(c => ({ e: c.e })) }));
22+const out = runDriver([input]).split("\n").filter(Boolean);
23+if (out.length !== oracle.cases.length) throw new Error(`got ${out.length} answers for ${oracle.cases.length} cases`);
24+const cases = out.map((l, i) => {
25+ const r = JSON.parse(l);
26+ return { e: oracle.cases[i].e, ...r };
27+});
28+process.stdout.write(JSON.stringify({
29+ reference: { source: "actions/runner src/Sdk/DTExpressions2 (Expressions2)", commit: env.commit, runtime: env.runtime },
30+ cases,
31+}));
A expr/gen/runner/env.mjs +45 -0
@@ -0,0 +1,45 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Shared by the scripts of this directory: finds the runner driver, and checks
4+// that the clone it was built from is the one the recorded data is about and
5+// that the .NET runtime is the 9.0 line (number formatting and case folding come
6+// from the runtime, not from the SDK).
7+//
8+// RUNNER_CLONE=/path/to/actions/runner node expr/gen/runner/<script>.mjs
9+
10+import { execFileSync } from "node:child_process";
11+import { readFileSync } from "node:fs";
12+import { join, dirname } from "node:path";
13+import { fileURLToPath } from "node:url";
14+
15+export const here = dirname(fileURLToPath(import.meta.url));
16+
17+/** The commit of actions/runner the data in testdata/ is about (runner-commit.txt). */
18+export const runnerCommit = readFileSync(join(here, "runner-commit.txt"), "utf8").trim();
19+
20+/** The driver built by `dotnet build -c Release expr/gen/runner -p:RunnerSrc=<clone>/src/Sdk`. */
21+export const driver = join(here, "bin/Release/net9.0/runner.dll");
22+
23+/**
24+ * Checks the environment and returns { clone, commit, runtime }. The clone is
25+ * $RUNNER_CLONE (or the first argument); its HEAD must be runner-commit.txt and
26+ * its tree clean in src/Sdk, and `dotnet --list-runtimes` must list a
27+ * Microsoft.NETCore.App 9.0.x.
28+ */
29+export function checkEnvironment(arg) {
30+ const clone = process.env.RUNNER_CLONE || arg;
31+ if (!clone) throw new Error("set RUNNER_CLONE to a clone of https://github.com/actions/runner");
32+ const git = (...a) => execFileSync("git", ["-C", clone, ...a]).toString().trim();
33+ const commit = git("rev-parse", "HEAD");
34+ if (commit !== runnerCommit) throw new Error(`the clone is at ${commit}, the data is about ${runnerCommit} (runner-commit.txt)`);
35+ if (git("status", "--porcelain", "--", "src/Sdk") !== "") throw new Error("the clone has local changes in src/Sdk");
36+ const rt = execFileSync("dotnet", ["--list-runtimes"]).toString().split("\n").filter(l => /^Microsoft\.NETCore\.App 9\.0\.\d+ /.test(l));
37+ if (rt.length === 0) throw new Error("no Microsoft.NETCore.App 9.0.x runtime installed (dotnet --list-runtimes)");
38+ return { clone, commit, runtime: rt[rt.length - 1].replace(/ \[.*$/, "") };
39+}
40+
41+/** Runs the driver with args; the environment is checked first. */
42+export function runDriver(args, opts = {}) {
43+ checkEnvironment();
44+ return execFileSync("dotnet", [driver, ...args], { maxBuffer: 1 << 30, ...opts }).toString("utf8");
45+}
A expr/gen/runner/fromjson_cases.mjs +56 -0
@@ -0,0 +1,56 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Writes testdata/fromjson_cases.json: for a corpus of JSON-ish inputs (hand
4+// picked and seeded random mutations of them), whether the runner's
5+// fromJSON accepts the text and what toJSON of the result is. The Go tests
6+// replay it (TestFromJSONMatchesRunner) and use the inputs as fuzz seeds.
7+//
8+// node expr/gen/runner/fromjson_cases.mjs > expr/testdata/fromjson_cases.json
9+//
10+// Needs the built driver, see README.md.
11+
12+import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
13+import { tmpdir } from "node:os";
14+import { join } from "node:path";
15+import { runDriver, here, checkEnvironment } from "./env.mjs";
16+
17+const seeds = [
18+ "null", "true", "false", "NaN", "Infinity", "-Infinity", "undefined", "0", "-0", "010", "08", "0x1F", "1.5e3", ".5", "5.", "12345678901234567890",
19+ '"a"', "'a'", '"\\u00e9\\ud83d\\ude00"', '"\\ud800"', "[]", "{}", "[1,2,3]", "[1,]", "[,1]", "[1,,2]", '{"a":1}', '{"a":1,"A":2}', "{a:1}", "{'a':'b'}",
20+ '{"a":[1,{"b":null}]}', "[[[1]]]", "[null,true,false]", "1 2", "[] x", ",", ",1", "/*c*/1", "1/*c*/", "[1/*c*/,2]", "null/x", "new Date(1)",
21+ "[010,08,-010]", '{"a":010}', "1e5", "1E+5", "-1.5e-3", "0.1e1", "0e0", "00", "-00", "0.0", "00.5",
22+];
23+const alphabet = [...'"\'{}[],:/*-+.0123456789xXeE tfnuNaIy\\\n\tabc_$'];
24+let state = 20260101;
25+const rnd = n => { state = (state * 1103515245 + 12345) & 0x7fffffff; return state % n; };
26+const set = new Set(seeds);
27+for (let i = 0; i < 4000; i++) {
28+ const base = seeds[rnd(seeds.length)];
29+ let s = base;
30+ for (let k = 0, n = 1 + rnd(3); k < n; k++) {
31+ const pos = rnd(s.length + 1);
32+ switch (rnd(3)) {
33+ case 0: s = s.slice(0, pos) + alphabet[rnd(alphabet.length)] + s.slice(pos); break;
34+ case 1: s = s.slice(0, pos) + s.slice(pos + 1); break;
35+ default: s = s.slice(0, pos) + alphabet[rnd(alphabet.length)] + s.slice(pos + 1);
36+ }
37+ }
38+ set.add(s);
39+}
40+const inputs = [...set];
41+const lit = s => "'" + s.replaceAll("'", "''") + "'";
42+const cases = inputs.map(s => ({ e: `toJSON(fromJSON(${lit(s)}))` }));
43+const tmp = mkdtempSync(join(tmpdir(), "fj-"));
44+writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
45+const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
46+rmSync(tmp, { recursive: true });
47+if (out.length !== inputs.length) throw new Error("answer count");
48+const res = inputs.map((s, i) => {
49+ const r = JSON.parse(out[i]);
50+ if (r.err) {
51+ // An error while parsing the expression (e.g. the text broke the literal) is not about fromJSON.
52+ return r.err.t === "eval" ? { in: s, ok: false } : null;
53+ }
54+ return { in: s, ok: true, out: r.s };
55+}).filter(Boolean);
56+process.stdout.write(JSON.stringify(res));
A expr/gen/runner/goldens.mjs +27 -0
@@ -0,0 +1,27 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// For each testdata/tojson/NAME.json, runs toJSON(fromJSON(<file>)) through the
4+// C# runner's engine and writes testdata/tojson/NAME.runner when the answer is
5+// not the JavaScript one (NAME.golden). golden_test.go prefers NAME.runner.
6+//
7+// dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
8+// node expr/gen/runner/goldens.mjs
9+
10+import { readFileSync, readdirSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
11+import { tmpdir } from "node:os";
12+import { join } from "node:path";
13+import { runDriver, here, checkEnvironment } from "./env.mjs";
14+
15+const dir = join(here, "../../testdata/tojson");
16+const names = readdirSync(dir).filter(f => f.endsWith(".json")).map(f => f.slice(0, -5)).sort();
17+const cases = names.map(n => ({ e: `toJSON(fromJSON('${readFileSync(join(dir, n + ".json"), "utf8").replaceAll("'", "''")}'))` }));
18+const tmp = mkdtempSync(join(tmpdir(), "goldens-"));
19+writeFileSync(join(tmp, "in.json"), JSON.stringify({ contexts: {}, cases }));
20+const out = runDriver([join(tmp, "in.json")]).split("\n").filter(Boolean);
21+rmSync(tmp, { recursive: true });
22+names.forEach((n, i) => {
23+ const r = JSON.parse(out[i]);
24+ const golden = readFileSync(join(dir, n + ".golden"), "utf8");
25+ if (r.err) { console.error(`${n}: runner error: ${r.err.m}`); return; }
26+ if (r.s !== golden) { writeFileSync(join(dir, n + ".runner"), r.s); console.log(`${n}: differs`); }
27+});
A expr/gen/runner/number_cases.mjs +40 -0
@@ -0,0 +1,40 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Writes testdata/number_cases.txt: ExpressionUtility.ParseNumber of the
4+// runner (string to number, used for number literals and for comparisons of
5+// strings with numbers) on a corpus of strings, as `--num` prints it.
6+// number_test.go replays it.
7+//
8+// dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
9+// node expr/gen/runner/number_cases.mjs > expr/testdata/number_cases.txt
10+
11+import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
12+import { tmpdir } from "node:os";
13+import { join } from "node:path";
14+import { runDriver, here, checkEnvironment } from "./env.mjs";
15+
16+const set = new Set();
17+const add = s => set.add(s);
18+const base = ["", " ", "0", "00", "-0", "+0", "1", "-1", "+1", "12", "007", "1.5", "-1.5", "1.", ".5", "-.5", "+.5", ".", "-.", "+.", "..", "1..2", "1.2.3",
19+ "1e3", "1E3", "1e+3", "1e-3", "1e", "1e+", "1e-", "e3", "E3", ".e3", "1.e3", ".5e3", "1e3.5", "1e03", "1e0003", "1e308", "1e309", "-1e309", "1e-324", "1e-400", "4.9e-324", "2.5e-324", "1.7976931348623157e308", "1.7976931348623159e308",
20+ "0.1", "0.30000000000000004", "123456789012345678901234567890", "9007199254740993", "-9007199254740993", "0.000001", "1_000", "1,000", "1 000", "١٢٣", "123",
21+ "Infinity", "-Infinity", "+Infinity", "infinity", "INFINITY", "Infinit", "Infinityx", "inf", "Inf", "-inf", "+inf", "∞", "-∞", "\u2212Infinity", "\u2212" + "1",
22+ "NaN", "nan", "NAN", "-NaN", "+NaN", "NaNx", "N", "null", "true", "false", "abc", "x", "-", "+", "+-1", "-+1", "--1", "++1", "- 1", "+ 1", "1 -", "1-", "1+", "1 2",
23+ "0x", "0x0", "0x1", "0x10", "0xff", "0xFF", "0XFF", "0xfF", "0x7fffffff", "0x80000000", "0xffffffff", "0x100000000", "0xFFFFFFFFF", "0x00000001", "0x0000000000000001", "0x000000000000000000001", "-0x1", "+0x1", "0x-1", "0x+1", "0x1.0", "0x1p3", "0xg", "0x 1", "0 x1", "0x1 ",
24+ "0o", "0o0", "0o7", "0o8", "0o17", "0O17", "0o777", "0o17777777777", "0o20000000000", "0o37777777777", "0o40000000000", "0o77777777777", "0o000000000001", "0o0000000000000000000001", "-0o7", "0o-7", "0o1.5", "0o 7",
25+ "0b", "0b0", "0b1", "0b101", "0B101", "0b2", "0b1e3",
26+ "\t1", "1\t", "\n1\n", "\r1", "\u000b1", "\u000c1", "\u00a01", "1\u00a0", "\u20281", "\u20291", "\u00851", "1\u0085", "\u30001", "\u20031\u2003", "\ufeff1", "1\ufeff", "\u200b1", "\u180e1", "\u1680" + "1",
27+ "1\u0000", "\u00001", "1 ", " 1", " 1 ", "+ ", " -1.5e2 "];
28+base.forEach(add);
29+// systematically: signs x forms
30+for (const sg of ["", "+", "-"]) for (const f of ["0", "1", "10", "1.", ".1", "1.1", "1e1", "1e+1", "1e-1", ".1e1", "1.e1", "Infinity", "infinity", "NaN", "nan", "0x1", "0xA", "0o1", "0b1"]) add(sg + f);
31+for (let i = 0; i < 40; i++) { add("0x" + "f".repeat(i)); add("0o" + "7".repeat(i)); add("0x" + "0".repeat(i) + "1"); add("0o" + "0".repeat(i) + "1"); add("1" + "0".repeat(i)); add("0." + "0".repeat(i) + "1"); }
32+for (const n of [255, 256, 2147483647, 2147483648, 4294967295, 4294967296]) { add("0x" + n.toString(16)); add("0o" + n.toString(8)); add(String(n)); }
33+const hex = s => s.length ? [...s].flatMap(c => { const n = c.codePointAt(0); return n > 0xffff ? [0xd800 + ((n - 0x10000) >> 10), 0xdc00 + ((n - 0x10000) & 0x3ff)] : [n]; }).map(u => u.toString(16).toUpperCase()).join(".") : "-";
34+const all = [...set];
35+const tmp = mkdtempSync(join(tmpdir(), "num-"));
36+writeFileSync(join(tmp, "in.txt"), all.map(hex).join("\n") + "\n");
37+const out = runDriver(["--num", join(tmp, "in.txt")]).split("\n").filter(Boolean);
38+rmSync(tmp, { recursive: true });
39+if (out.length !== all.length) throw new Error("answer count");
40+process.stdout.write(all.map((s, i) => `${hex(s)} ${out[i]}`).join("\n") + "\n");
A expr/gen/runner/ordinal_cases.mjs +39 -0
@@ -0,0 +1,39 @@
1+// SPDX-License-Identifier: MIT
2+//
3+// Writes testdata/ordinal_cases.txt: for pairs of strings chosen around the
4+// differences between .NET's OrdinalIgnoreCase and JavaScript's case mapping,
5+// what the runner's `--cmp` mode answers (equality, ordering, IndexOf,
6+// StartsWith, EndsWith). ordinal_test.go replays it.
7+//
8+// dotnet build -c Release expr/gen/runner -p:RunnerSrc=<runner>/src/Sdk
9+// node expr/gen/runner/ordinal_cases.mjs > expr/testdata/ordinal_cases.txt
10+
11+import { writeFileSync, mkdtempSync, rmSync } from "node:fs";
12+import { tmpdir } from "node:os";
13+import { join } from "node:path";
14+import { runDriver, here, checkEnvironment } from "./env.mjs";
15+
16+const words = [
17+ "", "a", "A", "ab", "AB", "abc", "ABC", "Hello", "hELLO", "hello world", "true", "TRUE", "null",
18+ "ß", "SS", "ss", "ẞ", "ʼn", "ʼN", "ffi", "FFI", "ffi", "ı", "I", "i", "İ", "ſ", "s", "S",
19+ "\u212a", "k", "K", "\u212b", "å", "Å", "σ", "ς", "Σ", "ΑΣ", "ας", "ασ", "DŽ", "Dž", "dž", "Lj", "lj",
20+ "é", "É", "e\u0301", "ÿ", "Ÿ", "µ", "μ", "Μ", "ǰ", "J\u030c", "ΐ", "ῼ", "ᾳ", "ᾼ",
21+ "\u00e9", "\u00c9", "Dža", "DŽa", "Ж", "ж", "ა", "Ა", "ꭰ", "Ꭰ", "ᏸ", "Ᏸ", "ⓐ", "Ⓐ", "a", "A",
22+ "\ue000", "\uffff", "\ud83d\ude00", "\ud801\udc00", "\ud801\udc28", "\u{10428}", "\ud800\udc00", "ab\ud83d\ude00", "AB\ud83d\ude00",
23+ "x\u2028y", "x y", "x\u00a0y", "0", "00", "1e3", "-", "_", "a-b", "a_b", "[]", "'",
24+];
25+const hex = s => s.length ? [...s].flatMap(c => { const n = c.codePointAt(0); return n > 0xffff ? [0xd800 + ((n - 0x10000) >> 10), 0xdc00 + ((n - 0x10000) & 0x3ff)] : [n]; }).map(u => u.toString(16).toUpperCase()).join(".") : "-";
26+const lines = [];
27+for (const a of words) for (const b of words) lines.push(`${hex(a)} ${hex(b)}`);
28+const tmp = mkdtempSync(join(tmpdir(), "ordinal-"));
29+writeFileSync(join(tmp, "in.txt"), lines.join("\n") + "\n");
30+const out = runDriver(["--cmp", join(tmp, "in.txt")]).split("\n").filter(Boolean);
31+rmSync(tmp, { recursive: true });
32+if (out.length !== lines.length) throw new Error("answer count");
33+// Keep every pair that is interesting (some predicate true) and a sample of the rest, to bound the file.
34+const res = [];
35+lines.forEach((l, i) => {
36+ const interesting = out[i] !== "0 0 0 0 0" && out[i] !== "0 -1 0 0 0" && out[i] !== "0 1 0 0 0";
37+ if (interesting || i % 7 === 0) res.push(`${l} ${out[i]}`);
38+});
39+process.stdout.write(res.join("\n") + "\n");
A expr/gen/runner/runner-commit.txt +1 -0
@@ -0,0 +1,1 @@
1+5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798
A expr/gen/runner/runner.csproj +26 -0
@@ -0,0 +1,26 @@
1+<Project Sdk="Microsoft.NET.Sdk">
2+ <!-- Builds the runner's own Expressions2 sources (actions/runner, MIT) with
3+ small stubs for the assemblies they depend on, and a driver that
4+ evaluates expressions with them. See README.md. -->
5+ <PropertyGroup>
6+ <OutputType>Exe</OutputType>
7+ <TargetFramework>net9.0</TargetFramework>
8+ <Nullable>disable</Nullable>
9+ <LangVersion>11.0</LangVersion>
10+ <NoWarn>NU1701;CS8632;CS0108;CS0114;CS1591;CS0618;SYSLIB0050</NoWarn>
11+ <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
12+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
13+ </PropertyGroup>
14+ <Target Name="RequireRunnerSrc" BeforeTargets="BeforeBuild;CoreCompile">
15+ <Error Condition="'$(RunnerSrc)' == '' Or !Exists('$(RunnerSrc)/DTExpressions2')" Text="Set RunnerSrc to the src/Sdk directory of a clone of actions/runner at the commit in runner-commit.txt: dotnet build -c Release expr/gen/runner -p:RunnerSrc=&lt;clone&gt;/src/Sdk" />
16+ </Target>
17+
18+ <ItemGroup>
19+ <Compile Include="$(RunnerSrc)/DTExpressions2/**/*.cs" />
20+ <Compile Include="$(RunnerSrc)/DTLogging/**/*.cs" />
21+ <Compile Include="$(RunnerSrc)/DTPipelines/Pipelines/ContextData/*.cs" Exclude="$(RunnerSrc)/DTPipelines/Pipelines/ContextData/PipelineContextDataExtensions.cs;$(RunnerSrc)/DTPipelines/Pipelines/ContextData/TemplateMemoryExtensions.cs;$(RunnerSrc)/DTPipelines/Pipelines/ContextData/TemplateTokenExtensions.cs" />
22+ <Compile Include="Stubs.cs" />
23+ <Compile Include="Program.cs" />
24+ <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
25+ </ItemGroup>
26+</Project>
M expr/golden_test.go +13 -1
@@ -24,7 +24,14 @@ func TestToJSONGolden(t *testing.T) {
24 if err != nil {24 if err != nil {
25 t.Fatal(err)25 t.Fatal(err)
26 }26 }
27- want, err := os.ReadFile(strings.TrimSuffix(in, ".json") + ".golden")27+ // NAME.golden is the answer of the JavaScript reference. Where
28+ // the C# runner answers differently, NAME.runner holds its
29+ // answer and is what this package follows (see PORTING.md).
30+ want, err := os.ReadFile(strings.TrimSuffix(in, ".json") + ".runner")
31+ fromRunner := err == nil
32+ if os.IsNotExist(err) {
33+ want, err = os.ReadFile(strings.TrimSuffix(in, ".json") + ".golden")
34+ }
28 if err != nil {35 if err != nil {
29 t.Fatal(err)36 t.Fatal(err)
30 }37 }
@@ -35,6 +42,11 @@ func TestToJSONGolden(t *testing.T) {
35 if got := ToJSON(v); got != string(want) {42 if got := ToJSON(v); got != string(want) {
36 t.Errorf("toJSON mismatch\n got: %q\nwant: %q", got, want)43 t.Errorf("toJSON mismatch\n got: %q\nwant: %q", got, want)
37 }44 }
45+ if fromRunner {
46+ // The runner's output is not JSON in general: numbers have 15
47+ // significant digits and NaN and Infinity are bare words.
48+ return
49+ }
38 // The golden output is itself JSON that must round-trip.50 // The golden output is itself JSON that must round-trip.
39 v2, err := FromJSON(want)51 v2, err := FromJSON(want)
40 if err != nil {52 if err != nil {
A expr/jsnumber_test.go +58 -0
@@ -0,0 +1,58 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "math"
7+ "strconv"
8+ "strings"
9+)
10+
11+// The helpers below reproduce what the JavaScript reference prints for a
12+// number. The package itself follows the C# runner (numberToCoercedString);
13+// the oracle tests need the JavaScript form to read the recorded tokens.
14+
15+// jsNumberToString is Number.prototype.toString: the shortest decimal that
16+// round-trips, in positional notation for exponents from -7 to 20 and in
17+// exponent notation ("1e+21", "1.5e-7") beyond.
18+func jsNumberToString(x float64) string {
19+ switch {
20+ case math.IsNaN(x):
21+ return "NaN"
22+ case x == 0:
23+ return "0"
24+ case math.IsInf(x, 1):
25+ return "Infinity"
26+ case math.IsInf(x, -1):
27+ return "-Infinity"
28+ }
29+ sign := ""
30+ if x < 0 {
31+ sign = "-"
32+ x = -x
33+ }
34+ s := strconv.FormatFloat(x, 'e', -1, 64) // d.ddde±xx
35+ mant, expStr, _ := strings.Cut(s, "e")
36+ exp, _ := strconv.Atoi(expStr)
37+ digits := strings.Replace(mant, ".", "", 1)
38+ k := len(digits)
39+ n := exp + 1
40+ switch {
41+ case k <= n && n <= 21:
42+ return sign + digits + strings.Repeat("0", n-k)
43+ case 0 < n && n <= 21:
44+ return sign + digits[:n] + "." + digits[n:]
45+ case -6 < n && n <= 0:
46+ return sign + "0." + strings.Repeat("0", -n) + digits
47+ }
48+ e := n - 1
49+ esign := "+"
50+ if e < 0 {
51+ esign = "-"
52+ e = -e
53+ }
54+ if k == 1 {
55+ return sign + digits + "e" + esign + strconv.Itoa(e)
56+ }
57+ return sign + digits[:1] + "." + digits[1:] + "e" + esign + strconv.Itoa(e)
58+}
M expr/json.go +245 -165
@@ -6,9 +6,10 @@ import (
6 "errors"6 "errors"
7 "fmt"7 "fmt"
8 "math"8 "math"
9- "sort"9+ "math/big"
10 "strconv"10 "strconv"
11 "strings"11 "strings"
12+ "unicode"
12 "unicode/utf8"13 "unicode/utf8"
13 )14 )
14 15
@@ -21,30 +22,37 @@ const MaxJSONDepth = 1000
21 22
22 // FromJSON parses JSON text into a [Value], like the fromJSON function.23 // FromJSON parses JSON text into a [Value], like the fromJSON function.
23 //24 //
24-// The grammar is strict RFC 8259 JSON as JSON.parse accepts it. Numbers are25+// The reader is Json.NET's, as in the runner (Sdk/Functions/FromJson.cs:
25-// float64 (1e999 is +Inf). Objects become [*Dictionary] values: keys that26+// JToken.ReadFrom on a JsonTextReader, then ToPipelineContextData), so it
26-// are array indices ("0", "42") come first in ascending order, then the rest27+// accepts more than RFC 8259:
27-// in document order, as JavaScript enumerates object keys; a repeated key28+//
28-// keeps its first position and its last value; keys that differ only by case29+// - only the first value is read, anything after it is ignored;
29-// collapse to the first.30+// - strings may use single quotes and may contain raw control characters,
31+// object names may be unquoted (letters, digits, _ and $);
32+// - a trailing comma is accepted, an empty array element is an empty string;
33+// - NaN, Infinity and -Infinity are numbers, numbers may be written .5, 5.,
34+// 1.e2, 01 or 0x1F (hexadecimal, no sign), and undefined is an empty
35+// string;
36+// - nesting is limited to 64 levels (JsonReader.MaxDepth).
37+//
38+// Not ported (they fail here): comments and constructors (new Date(1)).
39+// Numbers are float64 (1e999 is +Inf). Objects become [*Dictionary] values in
40+// document order; a repeated key, compared case-insensitively, keeps the
41+// spelling and position of its first occurrence and the value of its last.
42+// An unpaired surrogate escape reads as U+FFFD.
30 func FromJSON(data []byte) (Value, error) {43 func FromJSON(data []byte) (Value, error) {
31 s := string(data)44 s := string(data)
32 if !utf8.ValidString(s) {45 if !utf8.ValidString(s) {
33- s = string([]rune(s)) // invalid bytes become U+FFFD, as in a JS string46+ s = string([]rune(s)) // invalid bytes become U+FFFD
34 }47 }
35 p := &jsonParser{s: s}48 p := &jsonParser{s: s}
36 p.skipSpace()49 p.skipSpace()
37- v, err := p.value(0)50+ return p.value(0)
38- if err != nil {
39- return nil, err
40- }
41- p.skipSpace()
42- if p.i < len(p.s) {
43- return nil, p.unexpected()
44- }
45- return v, nil
46 }51 }
47 52
53+// jsonReaderMaxDepth is Json.NET's JsonReader.MaxDepth default.
54+const jsonReaderMaxDepth = 64
55+
48 type jsonParser struct {56 type jsonParser struct {
49 s string57 s string
50 i int58 i int
@@ -55,102 +63,193 @@ func (p *jsonParser) unexpected() error {
55 return errors.New("unexpected end of JSON input")63 return errors.New("unexpected end of JSON input")
56 }64 }
57 r, _ := utf8.DecodeRuneInString(p.s[p.i:])65 r, _ := utf8.DecodeRuneInString(p.s[p.i:])
58- return fmt.Errorf("unexpected token %q in JSON at position %d", r, p.i)66+ return fmt.Errorf("unexpected character %q in JSON at position %d", r, p.i)
59 }67 }
60 68
61 func (p *jsonParser) skipSpace() {69 func (p *jsonParser) skipSpace() {
62 for p.i < len(p.s) {70 for p.i < len(p.s) {
63- switch p.s[p.i] {71+ r, w := utf8.DecodeRuneInString(p.s[p.i:])
64- case ' ', '\t', '\n', '\r':72+ if !isNetSpace(r) {
65- p.i++
66- default:
67 return73 return
68 }74 }
75+ p.i += w
69 }76 }
70 }77 }
71 78
72-func (p *jsonParser) literal(word string, v Value) (Value, error) {79+// isJSONSeparator reports whether c ends a literal or a number.
73- if !strings.HasPrefix(p.s[p.i:], word) {80+func isJSONSeparator(r rune) bool {
74- // Advance to the first mismatching byte for the error position.81+ switch r {
75- for j := 0; j < len(word) && p.i < len(p.s) && p.s[p.i] == word[j]; j++ {82+ case ',', '}', ']', ')', '/':
76- p.i++83+ return true
84+ }
85+ return isNetSpace(r)
86+}
87+
88+// token returns the run of characters up to the next separator.
89+func (p *jsonParser) token() string {
90+ start := p.i
91+ for p.i < len(p.s) {
92+ r, w := utf8.DecodeRuneInString(p.s[p.i:])
93+ if isJSONSeparator(r) {
94+ break
77 }95 }
78- return nil, p.unexpected()96+ p.i += w
79 }97 }
80- p.i += len(word)98+ return p.s[start:p.i]
81- return v, nil
82 }99 }
83 100
84 func (p *jsonParser) value(depth int) (Value, error) {101 func (p *jsonParser) value(depth int) (Value, error) {
85- if depth > MaxJSONDepth {
86- return nil, errors.New("maximum JSON nesting depth exceeded")
87- }
88 if p.i >= len(p.s) {102 if p.i >= len(p.s) {
89 return nil, p.unexpected()103 return nil, p.unexpected()
90 }104 }
91 switch c := p.s[p.i]; {105 switch c := p.s[p.i]; {
92 case c == '{':106 case c == '{':
93- return p.object(depth)107+ return p.object(depth + 1)
94 case c == '[':108 case c == '[':
95- return p.array(depth)109+ return p.array(depth + 1)
96- case c == '"':110+ case c == '"' || c == '\'':
97 s, err := p.str()111 s, err := p.str()
98 if err != nil {112 if err != nil {
99 return nil, err113 return nil, err
100 }114 }
101 return String(s), nil115 return String(s), nil
102- case c == 't':116+ case c == '-' || c == '.' || c >= '0' && c <= '9':
103- return p.literal("true", Boolean(true))
104- case c == 'f':
105- return p.literal("false", Boolean(false))
106- case c == 'n':
107- return p.literal("null", Null{})
108- case c == '-' || c >= '0' && c <= '9':
109 return p.number()117 return p.number()
110 }118 }
111- return nil, p.unexpected()119+ if c := p.s[p.i]; c == ',' && depth == 0 {
120+ // A comma where the document should start is an undefined value,
121+ // which Json.NET reads as an empty string; whatever follows is ignored
122+ // like any text after the first value.
123+ return String(""), nil
124+ }
125+ start := p.i
126+ var v Value
127+ switch word := p.token(); word {
128+ case "true":
129+ v = Boolean(true)
130+ case "false":
131+ v = Boolean(false)
132+ case "null":
133+ v = Null{}
134+ case "undefined":
135+ v = String("")
136+ case "NaN":
137+ v = Number(nan)
138+ case "Infinity":
139+ v = Number(posInf)
140+ default:
141+ p.i = start
142+ return nil, p.unexpected()
143+ }
144+ if err := p.afterLiteral(); err != nil {
145+ return nil, err
146+ }
147+ return v, nil
112 }148 }
113 149
150+// afterLiteral checks the character that follows a word literal (true, null,
151+// NaN...): Json.NET only accepts a "/" there when it starts a comment, and
152+// comments are not ported, so any "/" is an error here. Anything else is left
153+// for the caller (white space, a comma, a closing bracket, or trailing text).
154+func (p *jsonParser) afterLiteral() error {
155+ if p.i >= len(p.s) || p.s[p.i] != '/' {
156+ return nil
157+ }
158+ if p.i+1 < len(p.s) && (p.s[p.i+1] == '/' || p.s[p.i+1] == '*') {
159+ return fmt.Errorf("comments are not supported (position %d in JSON)", p.i)
160+ }
161+ return p.unexpected()
162+}
163+
164+// number reads a number token: decimal with optional sign, fraction and
165+// exponent (a digit is required in the mantissa), -Infinity, or an unsigned
166+// 0x hexadecimal integer.
114 func (p *jsonParser) number() (Value, error) {167 func (p *jsonParser) number() (Value, error) {
115 start := p.i168 start := p.i
116- if p.s[p.i] == '-' {169+ tok := p.token()
117- p.i++170+ if tok == "-Infinity" {
171+ if err := p.afterLiteral(); err != nil {
172+ return nil, err
173+ }
174+ return Number(math.Inf(-1)), nil
118 }175 }
119- digits := func() int {176+ if len(tok) > 2 && tok[0] == '0' && (tok[1] == 'x' || tok[1] == 'X') {
120- n := 0177+ if len(tok) <= 18 {
121- for p.i < len(p.s) && p.s[p.i] >= '0' && p.s[p.i] <= '9' {178+ if n, err := strconv.ParseUint(tok[2:], 16, 64); err == nil && !strings.ContainsAny(tok[2:], "+-_") {
122- p.i++179+ return Number(float64(int64(n))), nil
123- n++180+ }
124 }181 }
125- return n182+ p.i = start
183+ return nil, fmt.Errorf("input string %q is not a valid number", tok)
126 }184 }
127- switch {185+ // A non-negative number with a leading zero is octal when it has only
128- case p.i < len(p.s) && p.s[p.i] == '0':186+ // digits (010 is 8, 08 is an error) and an error unless the zero is
129- p.i++187+ // followed by ".", "e" or "E" (01.5, 012e1 and 00.5 fail). A negative
130- case digits() == 0:188+ // number is read as decimal whatever its zeros (-010 is -10).
131- return nil, p.unexpected()189+ if len(tok) > 1 && tok[0] == '0' && tok[1] != '.' && tok[1] != 'e' && tok[1] != 'E' {
190+ v, ok := parseOctal64(tok[1:])
191+ if !ok {
192+ p.i = start
193+ return nil, fmt.Errorf("input string %q is not a valid number", tok)
194+ }
195+ return Number(v), nil
132 }196 }
133- if p.i < len(p.s) && p.s[p.i] == '.' {197+ body := strings.TrimPrefix(tok, "-")
134- p.i++198+ if !isDecimalLiteral(body) {
135- if digits() == 0 {199+ p.i = start
136- return nil, p.unexpected()200+ return nil, fmt.Errorf("input string %q is not a valid number", tok)
201+ }
202+ if isBigInteger(body) {
203+ // Json.NET reads an integer that does not fit in 64 bits as a
204+ // BigInteger, and (Double)BigInteger truncates toward zero to 53 bits
205+ // instead of rounding to nearest.
206+ if _, err := strconv.ParseInt(tok, 10, 64); err != nil {
207+ n, _ := new(big.Int).SetString(tok, 10)
208+ f, _ := new(big.Float).SetPrec(53).SetMode(big.ToZero).SetInt(n).Float64()
209+ return Number(f), nil
137 }210 }
138 }211 }
139- if p.i < len(p.s) && (p.s[p.i] == 'e' || p.s[p.i] == 'E') {212+ f, _ := strconv.ParseFloat(tok, 64) // range errors give ±Inf
140- p.i++213+ if f == 0 && isBigInteger(body) {
141- if p.i < len(p.s) && (p.s[p.i] == '+' || p.s[p.i] == '-') {214+ f = 0 // an integer token is read as a long: "-0" and "-00" are plain 0
142- p.i++215+ }
216+ return Number(f), nil
217+}
218+
219+// parseOctal64 reads octal digits as Convert.ToInt64(s, 8) does: leading
220+// zeros are free, at most 64 significant bits, and a value of 2^63 or more
221+// wraps to a negative number (1777777777777777777777 is -1).
222+func parseOctal64(digits string) (float64, bool) {
223+ if digits == "" {
224+ return 0, false
225+ }
226+ var v uint64
227+ for i := 0; i < len(digits); i++ {
228+ c := digits[i]
229+ if c < '0' || c > '7' {
230+ return 0, false
143 }231 }
144- if digits() == 0 {232+ if v>>61 != 0 {
145- return nil, p.unexpected()233+ return 0, false // the next shift would leave 64 bits
146 }234 }
235+ v = v<<3 | uint64(c-'0')
147 }236 }
148- f, _ := strconv.ParseFloat(p.s[start:p.i], 64) // range errors give ±Inf237+ return float64(int64(v)), true
149- return Number(f), nil238+}
239+
240+// isBigInteger reports whether s is only digits.
241+func isBigInteger(s string) bool {
242+ for i := 0; i < len(s); i++ {
243+ if s[i] < '0' || s[i] > '9' {
244+ return false
245+ }
246+ }
247+ return s != ""
150 }248 }
151 249
152 // str reads a string literal; p.i is on the opening quote.250 // str reads a string literal; p.i is on the opening quote.
153 func (p *jsonParser) str() (string, error) {251 func (p *jsonParser) str() (string, error) {
252+ quote := p.s[p.i]
154 p.i++253 p.i++
155 var b strings.Builder254 var b strings.Builder
156 for {255 for {
@@ -158,13 +257,11 @@ func (p *jsonParser) str() (string, error) {
158 return "", errors.New("unterminated string in JSON")257 return "", errors.New("unterminated string in JSON")
159 }258 }
160 c := p.s[p.i]259 c := p.s[p.i]
161- switch {260+ if c == quote {
162- case c == '"':
163 p.i++261 p.i++
164 return b.String(), nil262 return b.String(), nil
165- case c < 0x20:263+ }
166- return "", p.unexpected()264+ if c != '\\' {
167- case c != '\\':
168 b.WriteByte(c)265 b.WriteByte(c)
169 p.i++266 p.i++
170 continue267 continue
@@ -176,7 +273,7 @@ func (p *jsonParser) str() (string, error) {
176 e := p.s[p.i]273 e := p.s[p.i]
177 p.i++274 p.i++
178 switch e {275 switch e {
179- case '"', '\\', '/':276+ case '"', '\'', '\\', '/':
180 b.WriteByte(e)277 b.WriteByte(e)
181 case 'b':278 case 'b':
182 b.WriteByte('\b')279 b.WriteByte('\b')
@@ -206,13 +303,13 @@ func (p *jsonParser) str() (string, error) {
206 p.i = save // not a pair; the second escape is read on its own303 p.i = save // not a pair; the second escape is read on its own
207 }304 }
208 }305 }
209- // A lone surrogate cannot be a Go rune: writeRune keeps it in306+ if r >= 0xD800 && r < 0xE000 {
210- // its three-byte WTF-8 form so that toJSON writes it back as307+ r = utf8.RuneError // an unpaired surrogate
211- // the same \uXXXX escape, like JSON.stringify.308+ }
212- writeRune(&b, r)309+ b.WriteRune(r)
213 default:310 default:
214- p.i--311+ p.i -= 2
215- return "", p.unexpected()312+ return "", fmt.Errorf("bad JSON escape sequence \\%c at position %d", e, p.i)
216 }313 }
217 }314 }
218 }315 }
@@ -243,16 +340,27 @@ func (p *jsonParser) hex4() (rune, error) {
243 }340 }
244 341
245 func (p *jsonParser) array(depth int) (Value, error) {342 func (p *jsonParser) array(depth int) (Value, error) {
343+ if depth > jsonReaderMaxDepth {
344+ return nil, fmt.Errorf("the reader's MaxDepth of %d has been exceeded", jsonReaderMaxDepth)
345+ }
246 p.i++346 p.i++
247 arr := &Array{}347 arr := &Array{}
248- p.skipSpace()
249- if p.i < len(p.s) && p.s[p.i] == ']' {
250- p.i++
251- return arr, nil
252- }
253 for {348 for {
254 p.skipSpace()349 p.skipSpace()
255- v, err := p.value(depth + 1)350+ if p.i >= len(p.s) {
351+ return nil, p.unexpected()
352+ }
353+ switch p.s[p.i] {
354+ case ']':
355+ p.i++
356+ return arr, nil
357+ case ',':
358+ // An empty element is "undefined", which reads as an empty string.
359+ p.i++
360+ arr.items = append(arr.items, String(""))
361+ continue
362+ }
363+ v, err := p.value(depth)
256 if err != nil {364 if err != nil {
257 return nil, err365 return nil, err
258 }366 }
@@ -265,29 +373,50 @@ func (p *jsonParser) array(depth int) (Value, error) {
265 case ',':373 case ',':
266 p.i++374 p.i++
267 case ']':375 case ']':
268- p.i++
269- return arr, nil
270 default:376 default:
271 return nil, p.unexpected()377 return nil, p.unexpected()
272 }378 }
273 }379 }
274 }380 }
275 381
382+// propertyName reads a quoted or unquoted object name.
383+func (p *jsonParser) propertyName() (string, error) {
384+ if p.s[p.i] == '"' || p.s[p.i] == '\'' {
385+ return p.str()
386+ }
387+ start := p.i
388+ for p.i < len(p.s) {
389+ r, w := utf8.DecodeRuneInString(p.s[p.i:])
390+ if r == ':' || isNetSpace(r) {
391+ break
392+ }
393+ if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '_' && r != '$' {
394+ return "", p.unexpected()
395+ }
396+ p.i += w
397+ }
398+ if p.i == start {
399+ return "", p.unexpected()
400+ }
401+ return p.s[start:p.i], nil
402+}
403+
276 func (p *jsonParser) object(depth int) (Value, error) {404 func (p *jsonParser) object(depth int) (Value, error) {
277- p.i++405+ if depth > jsonReaderMaxDepth {
278- var keys []string406+ return nil, fmt.Errorf("the reader's MaxDepth of %d has been exceeded", jsonReaderMaxDepth)
279- vals := map[string]Value{}
280- p.skipSpace()
281- if p.i < len(p.s) && p.s[p.i] == '}' {
282- p.i++
283- return NewDictionary(), nil
284 }407 }
408+ p.i++
409+ d := NewDictionary()
285 for {410 for {
286 p.skipSpace()411 p.skipSpace()
287- if p.i >= len(p.s) || p.s[p.i] != '"' {412+ if p.i >= len(p.s) {
288 return nil, p.unexpected()413 return nil, p.unexpected()
289 }414 }
290- k, err := p.str()415+ if p.s[p.i] == '}' {
416+ p.i++
417+ return d, nil
418+ }
419+ k, err := p.propertyName()
291 if err != nil {420 if err != nil {
292 return nil, err421 return nil, err
293 }422 }
@@ -297,14 +426,11 @@ func (p *jsonParser) object(depth int) (Value, error) {
297 }426 }
298 p.i++427 p.i++
299 p.skipSpace()428 p.skipSpace()
300- v, err := p.value(depth + 1)429+ v, err := p.value(depth)
301 if err != nil {430 if err != nil {
302 return nil, err431 return nil, err
303 }432 }
304- if _, dup := vals[k]; !dup {433+ d.set(k, v)
305- keys = append(keys, k)
306- }
307- vals[k] = v // last value wins, at the first position
308 p.skipSpace()434 p.skipSpace()
309 if p.i >= len(p.s) {435 if p.i >= len(p.s) {
310 return nil, p.unexpected()436 return nil, p.unexpected()
@@ -312,66 +438,21 @@ func (p *jsonParser) object(depth int) (Value, error) {
312 switch p.s[p.i] {438 switch p.s[p.i] {
313 case ',':439 case ',':
314 p.i++440 p.i++
315- continue
316 case '}':441 case '}':
317- p.i++
318 default:442 default:
319 return nil, p.unexpected()443 return nil, p.unexpected()
320 }444 }
321- break
322 }445 }
323- d := NewDictionary()
324- for _, k := range jsKeyOrder(keys) {
325- d.Add(k, vals[k])
326- }
327- return d, nil
328 }446 }
329 447
330-// isArrayIndex reports whether k is the canonical decimal form of an integer448+// ToJSON renders v as the toJSON function does (Sdk/Functions/ToJson.cs): two
331-// in [0, 2^32-2], the keys JavaScript enumerates before all others.449+// space indentation, "key": value pairs in the dictionary's order, "[]" and
332-func isArrayIndex(k string) bool {450+// "{}" for empty containers. A number is written as its string form
333- if k == "" || len(k) > 10 || (len(k) > 1 && k[0] == '0') {451+// (numberToCoercedString: at most 15 significant digits, so NaN and the
334- return false452+// infinities are written as such). Strings are escaped as Json.NET does by
335- }453+// default: quotes, backslashes, \b \f \n \r \t, the other control characters
336- for i := 0; i < len(k); i++ {454+// as \u00xx, and U+0085, U+2028 and U+2029 as \uxxxx; everything else,
337- if k[i] < '0' || k[i] > '9' {455+// including non-ASCII text, is written as is.
338- return false
339- }
340- }
341- n, _ := strconv.ParseUint(k, 10, 64)
342- return n < math.MaxUint32
343-}
344-
345-// jsKeyOrder reorders keys the way Object.keys enumerates a JavaScript
346-// object: array indices ascending, then the others in their given order.
347-func jsKeyOrder(keys []string) []string {
348- var idx, rest []string
349- for _, k := range keys {
350- if isArrayIndex(k) {
351- idx = append(idx, k)
352- } else {
353- rest = append(rest, k)
354- }
355- }
356- if len(idx) == 0 {
357- return keys
358- }
359- sort.Slice(idx, func(i, j int) bool {
360- a, _ := strconv.ParseUint(idx[i], 10, 64)
361- b, _ := strconv.ParseUint(idx[j], 10, 64)
362- return a < b
363- })
364- return append(idx, rest...)
365-}
366-
367-// ToJSON renders v as the toJSON function does: JSON.stringify(v, replacer,
368-// " "), that is two-space indentation, "key": value pairs, "[]" and "{}"
369-// for empty containers, and numbers as JavaScript prints them (NaN and the
370-// infinities as null, -0 as 0, 1e21 as 1e+21). Dictionary keys that are array
371-// indices come first in ascending order, as when GitHub's replacer builds a
372-// JavaScript object. Control characters, quotes and backslashes in strings
373-// are escaped; everything else, including non-ASCII text, is written as is.
374-// Lone surrogates, which FromJSON keeps in WTF-8 form, are written as \uXXXX.
375 //456 //
376 // ToJSON suits the finite values that FromJSON and Evaluate produce. A value457 // ToJSON suits the finite values that FromJSON and Evaluate produce. A value
377 // nested deeper than [MaxJSONDepth], which only a cyclic value built by hand458 // nested deeper than [MaxJSONDepth], which only a cyclic value built by hand
@@ -453,12 +534,12 @@ func (w *jsonWriter) value(v Value, depth int) {
453 w.str("false")534 w.str("false")
454 }535 }
455 case Number:536 case Number:
456- f := float64(x)537+ // The runner writes the number as it converts it to a string
457- if math.IsNaN(f) || math.IsInf(f, 0) {538+ // (ToJson.WriteValue: value.ConvertToString()), so NaN and the
458- w.str("null")539+ // infinities are written as such, and a number has at most 15
459- } else {540+ // significant digits. JSON.stringify gives null and the shortest
460- w.str(jsNumberToString(f))541+ // round-trip form.
461- }542+ w.str(numberToCoercedString(float64(x)))
462 case String:543 case String:
463 w.jsonString(string(x))544 w.jsonString(string(x))
464 case *Array:545 case *Array:
@@ -485,7 +566,7 @@ func (w *jsonWriter) value(v Value, depth int) {
485 w.str("{}")566 w.str("{}")
486 return567 return
487 }568 }
488- keys := jsKeyOrder(x.keys)569+ keys := x.keys
489 w.str("{\n")570 w.str("{\n")
490 for i, k := range keys {571 for i, k := range keys {
491 val, _ := x.Get(k)572 val, _ := x.Get(k)
@@ -534,8 +615,7 @@ func (w *jsonWriter) jsonString(s string) {
534 esc = `\t`615 esc = `\t`
535 case r < 0x20:616 case r < 0x20:
536 esc = `\u00` + string([]byte{hexDigits[r>>4], hexDigits[r&0xF]})617 esc = `\u00` + string([]byte{hexDigits[r>>4], hexDigits[r&0xF]})
537- case r >= 0xD800 && r < 0xE000:618+ case r == 0x85 || r == 0x2028 || r == 0x2029 || r >= 0xD800 && r < 0xE000:
538- // A lone surrogate (WTF-8, see FromJSON).
539 esc = `\u` + string([]byte{hexDigits[r>>12], hexDigits[r>>8&0xF], hexDigits[r>>4&0xF], hexDigits[r&0xF]})619 esc = `\u` + string([]byte{hexDigits[r>>12], hexDigits[r>>8&0xF], hexDigits[r>>4&0xF], hexDigits[r&0xF]})
540 }620 }
541 if esc != "" {621 if esc != "" {
M expr/json_test.go +149 -30
@@ -8,6 +8,9 @@ import (
8 "testing"8 "testing"
9 )9 )
10 10
11+// The accepted and rejected inputs below were established by running
12+// fromJSON through the runner's own code (gen/runner): Json.NET's reader, not
13+// JSON.parse.
11 func TestFromJSONValid(t *testing.T) {14 func TestFromJSONValid(t *testing.T) {
12 tests := []struct {15 tests := []struct {
13 in string16 in string
@@ -17,29 +20,41 @@ func TestFromJSONValid(t *testing.T) {
17 {" true ", "true"},20 {" true ", "true"},
18 {"\t\r\n false \n", "false"},21 {"\t\r\n false \n", "false"},
19 {"0", "0"},22 {"0", "0"},
20- {"-0", "0"},
21 {"-12.5e1", "-125"},23 {"-12.5e1", "-125"},
22 {"1E2", "100"},24 {"1E2", "100"},
23 {"0.1e1", "1"},25 {"0.1e1", "1"},
24 {`"a\u00e9\n"`, `"aé\n"`},26 {`"a\u00e9\n"`, `"aé\n"`},
25 {`"\ud83d\ude00"`, `"😀"`},27 {`"\ud83d\ude00"`, `"😀"`},
26- {`"\ud800"`, `"\ud800"`},
27- {`"\udc00x"`, `"\udc00x"`},
28- {`"\ud800\u0041"`, `"\ud800A"`},
29 {`"\/"`, `"/"`},28 {`"\/"`, `"/"`},
30 {`[]`, `[]`},29 {`[]`, `[]`},
31 {`{}`, `{}`},30 {`{}`, `{}`},
32 {`[ 1 , [ ] , { } ]`, "[\n 1,\n [],\n {}\n]"},31 {`[ 1 , [ ] , { } ]`, "[\n 1,\n [],\n {}\n]"},
33- {`{"a":1,"A":2}`, "{\n \"a\": 2\n}"}, // see below: the last value of an exact duplicate wins, case variants collapse to the first32+ // An unpaired surrogate escape becomes U+FFFD.
33+ {`"\ud800"`, "\"\ufffd\""},
34+ {`"\udc00x"`, "\"\ufffdx\""},
35+ {`"\ud800\u0041"`, "\"\ufffdA\""},
36+ // Repeated keys: the last value, at the first position and spelling;
37+ // keys compare case-insensitively.
38+ {`{"a":1,"A":2}`, "{\n \"a\": 2\n}"},
34 {`{"a":1,"a":2}`, "{\n \"a\": 2\n}"},39 {`{"a":1,"a":2}`, "{\n \"a\": 2\n}"},
35- {`{"b":1,"1":2,"a":3,"0":4}`, "{\n \"0\": 4,\n \"1\": 2,\n \"b\": 1,\n \"a\": 3\n}"},40+ // Document order, array-index keys included.
41+ {`{"b":1,"1":2,"a":3,"0":4}`, "{\n \"b\": 1,\n \"1\": 2,\n \"a\": 3,\n \"0\": 4\n}"},
42+ // Leniency of Json.NET.
43+ {`{"a":1,}`, "{\n \"a\": 1\n}"},
44+ {`{a:1,$b:2,c_d:3}`, "{\n \"a\": 1,\n \"$b\": 2,\n \"c_d\": 3\n}"},
45+ {`{'a':'x\'y'}`, "{\n \"a\": \"x'y\"\n}"},
46+ {`[1,]`, "[\n 1\n]"},
47+ {`[,1]`, "[\n \"\",\n 1\n]"},
48+ {`[1,,2]`, "[\n 1,\n \"\",\n 2\n]"},
49+ {"1.", "1"}, {".5", "0.5"}, {"-.5", "-0.5"}, {"1.e2", "100"},
50+ {"0x10", "16"}, {"0X1f", "31"},
51+ {"NaN", "NaN"}, {"Infinity", "Infinity"}, {"-Infinity", "-Infinity"},
52+ {"undefined", `""`},
53+ {"1 2", "1"}, {"[] []", "[]"}, {"{} x", "{}"}, {`"a""b"`, `"a"`}, {"true false", "true"},
54+ {"\u00a01", "1"}, {"1\u00a0", "1"}, {"\u00851", "1"},
55+ {"\"a\nb\"", `"a\nb"`}, {"\"\x01\"", `"\u0001"`},
36 }56 }
37 for _, tc := range tests {57 for _, tc := range tests {
38- if tc.in == `{"a":1,"A":2}` {
39- // "a" and "A" are distinct JSON keys but the same dictionary key:
40- // the first one wins.
41- tc.want = "{\n \"a\": 1\n}"
42- }
43 v, err := FromJSON([]byte(tc.in))58 v, err := FromJSON([]byte(tc.in))
44 if err != nil {59 if err != nil {
45 t.Errorf("FromJSON(%q): %v", tc.in, err)60 t.Errorf("FromJSON(%q): %v", tc.in, err)
@@ -51,11 +66,74 @@ func TestFromJSONValid(t *testing.T) {
51 }66 }
52 }67 }
53 68
69+// TestFromJSONLeadingZeros: Json.NET reads a non-negative integer with a
70+// leading zero as octal (Convert.ToInt64(s, 8)), a negative one as decimal, and
71+// rejects a leading zero before a fraction or an exponent. Established with the
72+// runner's code.
73+func TestFromJSONLeadingZeros(t *testing.T) {
74+ for _, tc := range []struct {
75+ src string
76+ want string // ToJSON, or "" for an error
77+ }{
78+ {"010", "8"}, {"050", "40"}, {"00", "0"}, {"007", "7"}, {"0777", "511"}, {"00777", "511"},
79+ {"08", ""}, {"09", ""}, {"01.5", ""}, {"012e1", ""}, {"00.5", ""}, {"010.0", ""}, {"00e1", ""}, {"010e", ""},
80+ {"0.5", "0.5"}, {"0e1", "0"}, {"0E1", "0"}, {"0.", "0"}, {"0.e1", "0"}, {"0e", ""},
81+ {"-010", "-10"}, {"-09", "-9"}, {"-00", "0"}, {"-0777", "-777"}, {"-01.5", "-1.5"}, {"-012e1", "-120"},
82+ {"[010]", "[\n 8\n]"}, {"[08]", ""}, {`{"a":010}`, "{\n \"a\": 8\n}"}, {`{"a":08}`, ""}, {`{"a":01.5}`, ""},
83+ // 64-bit wrap of Convert.ToInt64.
84+ {"0777777777777777777777", "9.22337203685478E+18"},
85+ {"01777777777777777777777", "-1"},
86+ {"07777777777777777777777", ""},
87+ {"0000000000000000000012345", "5349"},
88+ {"0000000000000000000012349", ""},
89+ } {
90+ v, err := FromJSON([]byte(tc.src))
91+ switch {
92+ case tc.want == "" && err == nil:
93+ t.Errorf("FromJSON(%q) = %s, want an error", tc.src, ToJSON(v))
94+ case tc.want != "" && err != nil:
95+ t.Errorf("FromJSON(%q): %v", tc.src, err)
96+ case err == nil && ToJSON(v) != tc.want:
97+ t.Errorf("FromJSON(%q) = %s, want %s", tc.src, ToJSON(v), tc.want)
98+ }
99+ }
100+}
101+
102+// TestFromJSONSlashAndComma: a "/" after a literal is an error when it could
103+// start a comment (comments are not ported, so it fails consistently), as it is
104+// for Json.NET after null, true, false, NaN and Infinity and between the values
105+// of an array or object; after a number or string at the top level the text is
106+// ignored. A comma where the document should start is an undefined value.
107+func TestFromJSONSlashAndComma(t *testing.T) {
108+ for _, tc := range []struct {
109+ src string
110+ want string // ToJSON, or "" for an error
111+ }{
112+ {"null/x", ""}, {"null/", ""}, {"true/x", ""}, {"false/x", ""}, {"NaN/x", ""}, {"Infinity/x", ""}, {"-Infinity/x", ""},
113+ {"undefined/x", ""}, {"null//c", ""}, {"null/*c*/", ""}, {"NaN/*c*/", ""}, {"[null/x]", ""}, {"[null/*c*/]", ""}, {`{"a":null/x}`, ""},
114+ {"[1/2]", ""}, {"[1 /2]", ""}, {`{"a":1/2}`, ""}, {"[1/*c*/,2]", ""}, {"[1//c\n,2]", ""},
115+ {"null /x", "null"}, {"1/x", "1"}, {"1/", "1"}, {"1 /", "1"}, {"1.5/x", "1.5"}, {`"a"/x`, `"a"`}, {"[null,1]/x", "[\n null,\n 1\n]"},
116+ {",", `""`}, {",1", `""`}, {",,1", `""`}, {",x", `""`}, {" ,1", `""`}, {`,"a"`, `""`}, {",]", `""`},
117+ {"[,1]", "[\n \"\",\n 1\n]"}, {"[1,,2]", "[\n 1,\n \"\",\n 2\n]"}, {"[,]", "[\n \"\"\n]"},
118+ {"{,}", ""}, {`{"a":,1}`, ""}, {`{"a":1,,"b":2}`, ""},
119+ } {
120+ v, err := FromJSON([]byte(tc.src))
121+ switch {
122+ case tc.want == "" && err == nil:
123+ t.Errorf("FromJSON(%q) = %s, want an error", tc.src, ToJSON(v))
124+ case tc.want != "" && err != nil:
125+ t.Errorf("FromJSON(%q): %v", tc.src, err)
126+ case err == nil && ToJSON(v) != tc.want:
127+ t.Errorf("FromJSON(%q) = %s, want %s", tc.src, ToJSON(v), tc.want)
128+ }
129+ }
130+}
131+
54 func TestFromJSONInvalid(t *testing.T) {132 func TestFromJSONInvalid(t *testing.T) {
55 for _, in := range []string{133 for _, in := range []string{
56- "", " ", "{", "}", "[", "]", `{"a"`, `{"a":`, `{"a":1`, `{"a":1,}`, `{,}`, `{a:1}`, `{'a':1}`, `{"a" 1}`, `[1,]`, `[,1]`, `[1 2]`,134+ "", " ", "{", "}", "[", "]", `{"a"`, `{"a":`, `{"a":1`, `{,}`, `{"a" 1}`, `[1 2]`,
57- "01", "1.", ".5", "+1", "-", "1e", "1e+", "--1", "0x10", "NaN", "Infinity", "-Infinity", "undefined", "nul", "truee", "tru", "True",135+ "+1", "-", "1e", "1e+", "--1", "-0x10", "nul", "truee", "tru", "True", "TRUE", "NULL", "nan", "+Infinity", "1_2",
58- `"abc`, `"a` + "\n" + `b"`, "\"\x01\"", `"\x"`, `"\u12"`, `"\u12g4"`, `"\`, "1 2", "[] []", "{} x", `"a""b"`, "\x00", "\u00a01", "1\u00a0", "\ufeff1",136+ `"abc`, `"\x"`, `"\u12"`, `"\u12g4"`, `"\`, "\x00", "\ufeff1", "@", "new Foo",
59 } {137 } {
60 if v, err := FromJSON([]byte(in)); err == nil {138 if v, err := FromJSON([]byte(in)); err == nil {
61 t.Errorf("FromJSON(%q) = %v, want error", in, ToJSON(v))139 t.Errorf("FromJSON(%q) = %v, want error", in, ToJSON(v))
@@ -76,9 +154,44 @@ func TestFromJSONNumbers(t *testing.T) {
76 t.Errorf("FromJSON(%q) = %v, want %v", in, got, want)154 t.Errorf("FromJSON(%q) = %v, want %v", in, got, want)
77 }155 }
78 }156 }
79- v, _ := FromJSON([]byte("-0"))157+ // Json.NET reads the integer "-0" as 0, but "-0.0" is a float with a sign.
158+ if v, _ := FromJSON([]byte("-0")); math.Signbit(float64(v.(Number))) {
159+ t.Error(`"-0" kept its sign`)
160+ }
161+ v, _ := FromJSON([]byte("-0.0"))
80 if !math.Signbit(float64(v.(Number))) {162 if !math.Signbit(float64(v.(Number))) {
81- t.Error("-0 lost its sign")163+ t.Error("-0.0 lost its sign")
164+ }
165+ if got := ToJSON(v); got != "-0" {
166+ t.Errorf("ToJSON(-0.0) = %q", got)
167+ }
168+}
169+
170+// TestFromJSONBigIntegers: Json.NET gives integers beyond 64 bits as BigInteger
171+// and (Double)BigInteger truncates; established with the runner's code
172+// (fromJSON('N') == N, where the literal N rounds to nearest).
173+func TestFromJSONBigIntegers(t *testing.T) {
174+ for _, tc := range []struct {
175+ src string
176+ want float64
177+ }{
178+ {"9223372036854775807", 9223372036854775807}, // fits a long: nearest
179+ {"-9223372036854775808", -9223372036854775808}, // exact
180+ {"12345678901234567890", 12345678901234567890}, // 64 bits, above long: truncated to the same double here
181+ {"18446744073709551615", 18446744073709549568}, // 2^64-1 truncates to 2^64-2048, not 2^64
182+ {"18446744073709551617", 18446744073709551616}, // 2^64+1 truncates to 2^64
183+ {"-18446744073709551615", -18446744073709549568},
184+ {"12345678901234567890.0", 12345678901234567168}, // a decimal point: an ordinary float
185+ {"1e30", 1e30},
186+ } {
187+ v, err := FromJSON([]byte(tc.src))
188+ if err != nil {
189+ t.Errorf("FromJSON(%q): %v", tc.src, err)
190+ continue
191+ }
192+ if got := float64(v.(Number)); got != tc.want {
193+ t.Errorf("FromJSON(%q) = %v, want %v", tc.src, got, tc.want)
194+ }
82 }195 }
83 }196 }
84 197
@@ -93,13 +206,17 @@ func TestFromJSONInvalidUTF8(t *testing.T) {
93 }206 }
94 207
95 func TestFromJSONDeepNesting(t *testing.T) {208 func TestFromJSONDeepNesting(t *testing.T) {
96- deep := strings.Repeat("[", 900) + strings.Repeat("]", 900)209+ // Json.NET's reader allows 64 levels (JsonReader.MaxDepth).
97- if _, err := FromJSON([]byte(deep)); err != nil {210+ for _, n := range []int{1, 63, 64} {
98- t.Errorf("depth 900: %v", err)211+ if _, err := FromJSON([]byte(strings.Repeat("[", n) + "1" + strings.Repeat("]", n))); err != nil {
212+ t.Errorf("depth %d: %v", n, err)
213+ }
99 }214 }
100- tooDeep := strings.Repeat("[", MaxJSONDepth+10) + strings.Repeat("]", MaxJSONDepth+10)215+ for _, open := range []string{"[", `{"a":`} {
101- if _, err := FromJSON([]byte(tooDeep)); err == nil {216+ close := map[string]string{"[": "]", `{"a":`: "}"}[open]
102- t.Error("absurd depth accepted")217+ if _, err := FromJSON([]byte(strings.Repeat(open, 65) + "1" + strings.Repeat(close, 65))); err == nil {
218+ t.Errorf("depth 65 of %q accepted", open)
219+ }
103 }220 }
104 }221 }
105 222
@@ -132,14 +249,16 @@ func TestToJSONFormatting(t *testing.T) {
132 }{249 }{
133 {nil, "null"},250 {nil, "null"},
134 {Null{}, "null"},251 {Null{}, "null"},
135- {Number(math.NaN()), "null"},252+ // The runner writes numbers with ConvertToString (G15), so NaN and
136- {Number(math.Inf(1)), "null"},253+ // the infinities are written as such.
137- {Number(math.Inf(-1)), "null"},254+ {Number(math.NaN()), "NaN"},
138- {Number(math.Copysign(0, -1)), "0"},255+ {Number(math.Inf(1)), "Infinity"},
139- {Number(1e21), "1e+21"},256+ {Number(math.Inf(-1)), "-Infinity"},
140- {Number(1e-7), "1e-7"},257+ {Number(math.Copysign(0, -1)), "-0"},
258+ {Number(1e21), "1E+21"},
259+ {Number(1e-7), "1E-07"},
141 {Number(123456789), "123456789"},260 {Number(123456789), "123456789"},
142- {String("\u0000\u001f\u007f\u0080\u2028"), `"\u0000\u001f` + "\u007f\u0080\u2028" + `"`},261+ {String("\u0000\u001f\u007f\u0080\u2028"), `"\u0000\u001f` + "\u007f\u0080" + `\u2028"`},
143 {String("\"\\\b\f\n\r\t"), `"\"\\\b\f\n\r\t"`},262 {String("\"\\\b\f\n\r\t"), `"\"\\\b\f\n\r\t"`},
144 {String("</script>&'"), `"</script>&'"`},263 {String("</script>&'"), `"</script>&'"`},
145 {String("\xff"), "\"\ufffd\""},264 {String("\xff"), "\"\ufffd\""},
@@ -228,7 +347,7 @@ func TestCoerceAndTruthy(t *testing.T) {
228 {Boolean(true), "true", true},347 {Boolean(true), "true", true},
229 {Boolean(false), "false", false},348 {Boolean(false), "false", false},
230 {Number(0), "0", false},349 {Number(0), "0", false},
231- {Number(math.Copysign(0, -1)), "0", false},350+ {Number(math.Copysign(0, -1)), "-0", false},
232 {Number(math.NaN()), "NaN", false},351 {Number(math.NaN()), "NaN", false},
233 {Number(2), "2", true},352 {Number(2), "2", true},
234 {Number(math.Inf(-1)), "-Infinity", true},353 {Number(math.Inf(-1)), "-Infinity", true},
M expr/lexer.go +13 -19
@@ -70,19 +70,11 @@ type Token struct {
70 Value Value70 Value Value
71 }71 }
72 72
73-// tokenString is the text an error message shows for a token.73+// Lex splits an expression into tokens the way the JavaScript reference's
74-func tokenString(t Token) string {74+// lexer does, ending with a TokenEOF token. [Parse] does not use it: it
75- switch t.Type {75+// follows the C# runner, whose tokenizer is context sensitive and lazy (see
76- case TokenEOF:76+// parse_runner.go). Lex is kept because the oracle tests replay the reference's
77- return "EOF"77+// tokens with it.
78- case TokenString:
79- return string(t.Value.(String))
80- }
81- return t.Lexeme
82-}
83-
84-// Lex splits an expression into tokens. The result always ends with a
85-// TokenEOF token.
86 func Lex(input string) ([]Token, error) {78 func Lex(input string) ([]Token, error) {
87 if exceedsUTF16Len(input, MaxExpressionLength) {79 if exceedsUTF16Len(input, MaxExpressionLength) {
88 // The reference throws a bare Error("ErrorExceededMaxLength") here,80 // The reference throws a bare Error("ErrorExceededMaxLength") here,
@@ -271,7 +263,7 @@ func (l *lexer) consumeNumber() error {
271 l.next()263 l.next()
272 }264 }
273 lexeme := l.substring(l.start, l.offset)265 lexeme := l.substring(l.start, l.offset)
274- v := jsToNumber(lexeme)266+ v := parseNumber(lexeme)
275 if math.IsNaN(v) {267 if math.IsNaN(v) {
276 return l.unexpected(lexeme)268 return l.unexpected(lexeme)
277 }269 }
@@ -295,11 +287,13 @@ func (l *lexer) consumeString() error {
295 }287 }
296 l.next() // closing quote288 l.next() // closing quote
297 value := l.substring(l.start+1, l.offset-1)289 value := l.substring(l.start+1, l.offset-1)
298- // The reference calls value.replace("''", "'") with a string pattern,290+ // Every doubled quote is one quote. The JavaScript reference calls
299- // which replaces only the FIRST doubled quote: 'a''b''c' is a'b''c.291+ // value.replace("''", "'") with a string pattern, which replaces only the
300- // GitHub's C# lexer unescapes every pair. Followed as written; see292+ // first pair ('a''b''c' is a'b''c there); the C# runner, which is what
301- // PORTING.md.293+ // runs on github.com, appends one quote per pair
302- value = strings.Replace(value, "''", "'", 1)294+ // (Tokens/LexicalAnalyzer.cs ReadStringToken, "Escaped single quote"), and
295+ // this package follows it. See PORTING.md.
296+ value = strings.ReplaceAll(value, "''", "'")
303 l.addToken(TokenString, String(value))297 l.addToken(TokenString, String(value))
304 return nil298 return nil
305 }299 }
M expr/limits_test.go +10 -12
@@ -410,7 +410,7 @@ func TestWTF8Helpers(t *testing.T) {
410 if got := normalizeWTF8(lo + hi); got != lo+hi {410 if got := normalizeWTF8(lo + hi); got != lo+hi {
411 t.Errorf("reversed pair joined: %q", got)411 t.Errorf("reversed pair joined: %q", got)
412 }412 }
413- if foldKey(hi) != hi || foldKey("A"+hi) != "a"+hi {413+ if foldKey(hi) != hi || foldKey("a"+hi) != "A"+hi {
414 t.Error("foldKey")414 t.Error("foldKey")
415 }415 }
416 if compareUTF16(hi, "\ue000") >= 0 || compareUTF16(lo, "\ue000") >= 0 || compareUTF16(hi, "\U00010000") >= 0 {416 if compareUTF16(hi, "\ue000") >= 0 || compareUTF16(lo, "\ue000") >= 0 || compareUTF16(hi, "\U00010000") >= 0 {
@@ -422,17 +422,15 @@ func TestWTF8Helpers(t *testing.T) {
422 }422 }
423 423
424 func TestLoneSurrogateExpressions(t *testing.T) {424 func TestLoneSurrogateExpressions(t *testing.T) {
425- // The cases the review asked for; the oracle covers many more.425+ // Json.NET turns an unpaired surrogate escape into U+FFFD, so the
426+ // runner has no lone surrogates (JavaScript strings do: see PORTING.md).
426 runEvalCases(t, []evalCase{427 runEvalCases(t, []evalCase{
427- {`fromJSON('"\ud800"') == fromJSON('"\udc00"')`, "Boolean:false"},428+ {`fromJSON('"\ud800"') == fromJSON('"\udc00"')`, "Boolean:true"},
428- {`fromJSON('"\ud800"') == fromJSON('"\ud800"')`, "Boolean:true"},429+ {"fromJSON('\"\\ud800\"') == '\ufffd'", "Boolean:true"},
429- {"fromJSON('\"\\ud800\"') == '\ufffd'", "Boolean:false"},430+ {`fromJSON('"\ud800"') < fromJSON('"\udc00"')`, "Boolean:false"},
430- {`fromJSON('"\ud800"') < fromJSON('"\udc00"')`, "Boolean:true"},431+ {`contains(fromJSON('"\ud83d\ude00"'), fromJSON('"\ud83d"'))`, "Boolean:false"},
431- {"fromJSON('\"\\ud800\"') < '\ue000'", "Boolean:true"},432+ {`fromJSON('"\ud83d\ude00"') == '😀'`, "Boolean:true"},
432- {"fromJSON('\"\\ud800\"') < '\U00010000'", "Boolean:true"},433+ {`format('{0}{1}', fromJSON('"\ud83d"'), fromJSON('"\ude00"')) == '😀'`, "Boolean:false"},
433- {`contains(fromJSON('"a\ud800"'), fromJSON('"\ud800"'))`, "Boolean:true"},434+ {`toJSON(fromJSON('"\ud800"'))`, "String:\"\ufffd\""},
434- {`contains(fromJSON('"\ud83d\ude00"'), fromJSON('"\ud83d"'))`, "Boolean:true"},
435- {`format('{0}{1}', fromJSON('"\ud83d"'), fromJSON('"\ude00"')) == '😀'`, "Boolean:true"},
436- {`toJSON(fromJSON('"\ud800"'))`, `String:"\ud800"`},
437 })435 })
438 }436 }
M expr/number.go +140 -99
@@ -4,7 +4,6 @@ package expr
4 4
5 import (5 import (
6 "math"6 "math"
7- "math/big"
8 "strconv"7 "strconv"
9 "strings"8 "strings"
10 )9 )
@@ -14,137 +13,179 @@ var (
14 posInf = math.Inf(1)13 posInf = math.Inf(1)
15 )14 )
16 15
17-// jsNumberToString is Number.prototype.toString: the shortest decimal that16+// numberToCoercedString is the string form of a number: .NET's
18-// round-trips, in positional notation for exponents from -7 to 20 and in17+// Double.ToString("G15", InvariantCulture), which is what the C# runner uses
19-// exponent notation ("1e+21", "1.5e-7") beyond.18+// for every number-to-string conversion (ExpressionConstants.NumberFormat,
20-func jsNumberToString(x float64) string {19+// EvaluationResult.ConvertToString, hence format(), join(), toJSON() and the
20+// comparison with strings). The JavaScript reference computes
21+// (+x.toFixed(15)).toString() instead; they agree on ordinary values and
22+// differ on large, small and very precise ones. See PORTING.md.
23+//
24+// G15 rounds to 15 significant digits (ties to even on the exact binary
25+// value), then prints positionally when the decimal exponent X of the rounded
26+// value satisfies -5 < X < 15, and as d.dddE+XX otherwise, with the trailing
27+// zeros of the mantissa removed and at least two exponent digits. NaN is
28+// "NaN" and the infinities are "Infinity" and "-Infinity". Negative zero is
29+// "-0", as .NET Core prints it and as the legacy engine of the runner
30+// (DTExpressions2/EvaluationResult.ConvertToString, the one that runs today)
31+// leaves it; the newer engine (Expressions/EvaluationResult.cs) maps it to "0".
32+// See PORTING.md.
33+func numberToCoercedString(x float64) string {
21 switch {34 switch {
22 case math.IsNaN(x):35 case math.IsNaN(x):
23 return "NaN"36 return "NaN"
24- case x == 0:
25- return "0"
26 case math.IsInf(x, 1):37 case math.IsInf(x, 1):
27 return "Infinity"38 return "Infinity"
28 case math.IsInf(x, -1):39 case math.IsInf(x, -1):
29 return "-Infinity"40 return "-Infinity"
41+ case x == 0:
42+ if math.Signbit(x) {
43+ return "-0"
44+ }
45+ return "0"
30 }46 }
31 sign := ""47 sign := ""
32 if x < 0 {48 if x < 0 {
33 sign = "-"49 sign = "-"
34 x = -x50 x = -x
35 }51 }
36- s := strconv.FormatFloat(x, 'e', -1, 64) // d.ddde±xx52+ s := strconv.FormatFloat(x, 'e', 14, 64) // d.dddddddddddddde±xx, correctly rounded
37 mant, expStr, _ := strings.Cut(s, "e")53 mant, expStr, _ := strings.Cut(s, "e")
38 exp, _ := strconv.Atoi(expStr)54 exp, _ := strconv.Atoi(expStr)
39- digits := strings.Replace(mant, ".", "", 1)55+ digits := strings.TrimRight(strings.Replace(mant, ".", "", 1), "0")
40- k := len(digits)56+ if digits == "" {
41- n := exp + 157+ digits = "0"
58+ }
42 switch {59 switch {
43- case k <= n && n <= 21:60+ case exp >= 15 || exp <= -5:
44- return sign + digits + strings.Repeat("0", n-k)61+ out := digits[:1]
45- case 0 < n && n <= 21:62+ if len(digits) > 1 {
46- return sign + digits[:n] + "." + digits[n:]63+ out += "." + digits[1:]
47- case -6 < n && n <= 0:64+ }
48- return sign + "0." + strings.Repeat("0", -n) + digits65+ esign := "+"
49- }66+ if exp < 0 {
50- e := n - 167+ esign = "-"
51- esign := "+"68+ exp = -exp
52- if e < 0 {69+ }
53- esign = "-"70+ e := strconv.Itoa(exp)
54- e = -e71+ if len(e) < 2 {
55- }72+ e = "0" + e
56- if k == 1 {73+ }
57- return sign + digits + "e" + esign + strconv.Itoa(e)74+ return sign + out + "E" + esign + e
58- }75+ case exp < 0:
59- return sign + digits[:1] + "." + digits[1:] + "e" + esign + strconv.Itoa(e)76+ return sign + "0." + strings.Repeat("0", -exp-1) + digits
77+ case len(digits) <= exp+1:
78+ return sign + digits + strings.Repeat("0", exp+1-len(digits))
79+ }
80+ return sign + digits[:exp+1] + "." + digits[exp+1:]
60 }81 }
61 82
62-var pow10to15 = new(big.Int).Exp(big.NewInt(10), big.NewInt(15), nil)83+// isNetSpace reports whether r is white space for .NET's Char.IsWhiteSpace,
63-84+// which String.Trim() removes: U+0009..U+000D, U+0020, U+0085, U+00A0,
64-// numberToCoercedString is NumberData.coerceString of the reference85+// U+1680, U+2000..U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. (The
65-// implementation: (+x.toFixed(15)).toString(). Rounding to 15 decimals86+// JavaScript trim also removes U+FEFF and not U+0085.)
66-// removes binary noise (0.1+0.2 is "0.3") and ties round half up like87+func isNetSpace(r rune) bool {
67-// toFixed.88+ switch {
68-func numberToCoercedString(x float64) string {89+ case r >= 0x09 && r <= 0x0D, r == 0x20, r == 0x85, r == 0xA0, r == 0x1680,
69- if x == 0 {90+ r >= 0x2000 && r <= 0x200A, r == 0x2028, r == 0x2029, r == 0x202F, r == 0x205F, r == 0x3000:
70- return "0"91+ return true
71- }
72- if math.IsNaN(x) || math.IsInf(x, 0) {
73- return jsNumberToString(x)
74- }
75- a := math.Abs(x)
76- // toFixed returns Number::toString from 1e21 up, and for integers the
77- // fixed form parses back to the same number.
78- if a >= 1e21 || a == math.Trunc(a) {
79- return jsNumberToString(x)
80- }
81- r := new(big.Rat).SetFloat64(a)
82- r.Mul(r, new(big.Rat).SetInt(pow10to15))
83- q, rem := new(big.Int).QuoRem(r.Num(), r.Denom(), new(big.Int))
84- if rem.Lsh(rem, 1).Cmp(r.Denom()) >= 0 {
85- q.Add(q, big.NewInt(1))
86- }
87- digits := q.String()
88- if len(digits) < 16 {
89- digits = strings.Repeat("0", 16-len(digits)) + digits
90- }
91- fixed := digits[:len(digits)-15] + "." + digits[len(digits)-15:]
92- v, _ := strconv.ParseFloat(fixed, 64)
93- if x < 0 {
94- v = -v
95 }92 }
96- return jsNumberToString(v)93+ return false
97 }94 }
98 95
99-// jsToNumber is Number(string): the StringToNumber algorithm of ECMAScript.96+// parseNumber converts a string to a number the way the runner does,
100-// Surrounding whitespace is ignored, the empty string is 0, and the accepted97+// ExpressionUtility.ParseNumber (src/Sdk/DTExpressions2/Expressions2/Sdk/
101-// forms are decimal literals with optional sign, fraction and exponent,98+// ExpressionUtility.cs), which is used for number literals in expressions and
102-// [+-]Infinity, and unsigned 0x, 0o and 0b integers. Everything else, including99+// for every implicit string-to-number conversion:
103-// digit separators and "inf" or "nan", is NaN.100+//
104-func jsToNumber(s string) float64 {101+// - the string is trimmed (see isNetSpace); empty means 0;
105- s = jsTrim(s)102+// - Double.TryParse with AllowLeadingSign | AllowDecimalPoint |
103+// AllowExponent in the invariant culture: [sign] digits [. digits]
104+// [e [sign] digits], at least one digit around the point, ASCII digits
105+// only, an out-of-range exponent giving an infinity, "Infinity" and
106+// "-Infinity" in any letter case, NaN, and any number of trailing NUL
107+// characters, which .NET ignores;
108+// - "0x" followed by hexadecimal digits and "0o" followed by octal digits
109+// (lower-case prefix, no sign), read as a 32-bit two's-complement
110+// integer: 0xffffffff is -1, and a value that does not fit in 32 bits is
111+// NaN. There is no binary form;
112+// - everything else is NaN.
113+//
114+// The JavaScript reference uses Number(), which differs: 0X, 0O and 0B
115+// prefixes, 64-bit exactness, no 32-bit wrap, and U+FEFF as white space.
116+func parseNumber(s string) float64 {
117+ s = strings.TrimFunc(s, isNetSpace)
106 if s == "" {118 if s == "" {
107 return 0119 return 0
108 }120 }
121+ t := strings.TrimRight(s, "\x00")
122+ if body, neg := strings.CutPrefix(t, "-"); neg {
123+ if v, ok := parseDecimal(body); ok {
124+ return -v
125+ }
126+ } else {
127+ body, _ = strings.CutPrefix(t, "+")
128+ if v, ok := parseDecimal(body); ok {
129+ return v
130+ }
131+ }
109 if len(s) > 2 && s[0] == '0' {132 if len(s) > 2 && s[0] == '0' {
110- base := 0
111 switch s[1] {133 switch s[1] {
112- case 'x', 'X':134+ case 'x':
113- base = 16135+ if v, ok := parseRadix32(s[2:], 16); ok {
114- case 'o', 'O':136+ return v
115- base = 8137+ }
116- case 'b', 'B':138+ case 'o':
117- base = 2139+ if v, ok := parseRadix32(s[2:], 8); ok {
118- }140+ return v
119- if base != 0 {
120- n, ok := new(big.Int).SetString(s[2:], base)
121- if !ok || strings.ContainsAny(s[2:], "_+-") {
122- return nan
123 }141 }
124- f, _ := new(big.Float).SetInt(n).Float64()
125- return f
126 }142 }
127 }143 }
128- body := s144+ return nan
129- neg := false145+}
130- if body[0] == '+' || body[0] == '-' {146+
131- neg = body[0] == '-'147+// parseDecimal parses an unsigned decimal literal or "Infinity".
132- body = body[1:]148+func parseDecimal(body string) (float64, bool) {
133- }149+ if len(body) == 8 && strings.EqualFold(body, "infinity") && isASCII(body) {
134- if body == "Infinity" {150+ return posInf, true
135- if neg {
136- return math.Inf(-1)
137- }
138- return math.Inf(1)
139 }151 }
140 if !isDecimalLiteral(body) {152 if !isDecimalLiteral(body) {
141- return nan153+ return 0, false
142 }154 }
143- v, _ := strconv.ParseFloat(body, 64) // out of range yields ±Inf, which is what JS does too155+ v, _ := strconv.ParseFloat(body, 64) // out of range yields an infinity, as in .NET
144- if neg {156+ return v, true
145- v = -v157+}
158+
159+// parseRadix32 reads digits in base 16 or 8 as an unsigned 32-bit value and
160+// returns it as a signed one (Int32.TryParse with AllowHexSpecifier,
161+// Convert.ToInt32(s, 8)). Leading zeros are free; digits beyond 32 bits fail.
162+func parseRadix32(digits string, base int) (float64, bool) {
163+ if digits == "" {
164+ return 0, false
165+ }
166+ var v uint64
167+ for i := 0; i < len(digits); i++ {
168+ c := digits[i]
169+ var d int
170+ switch {
171+ case c >= '0' && c <= '9':
172+ d = int(c - '0')
173+ case c >= 'a' && c <= 'f':
174+ d = int(c-'a') + 10
175+ case c >= 'A' && c <= 'F':
176+ d = int(c-'A') + 10
177+ default:
178+ return 0, false
179+ }
180+ if d >= base {
181+ return 0, false
182+ }
183+ v = v*uint64(base) + uint64(d)
184+ if v > math.MaxUint32 {
185+ return 0, false
186+ }
146 }187 }
147- return v188+ return float64(int32(uint32(v))), true
148 }189 }
149 190
150 // isDecimalLiteral reports whether s matches191 // isDecimalLiteral reports whether s matches
A expr/number_test.go +62 -0
@@ -0,0 +1,62 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "math"
7+ "os"
8+ "strconv"
9+ "strings"
10+ "testing"
11+)
12+
13+// TestParseNumberMatchesRunner replays testdata/number_cases.txt, the answers
14+// of the runner's ExpressionUtility.ParseNumber (gen/runner/number_cases.mjs)
15+// on 443 strings: hexadecimal, octal and binary forms, signs, exponents,
16+// Infinity and NaN spellings, Unicode digits and spaces.
17+func TestParseNumberMatchesRunner(t *testing.T) {
18+ data, err := os.ReadFile("testdata/number_cases.txt")
19+ if err != nil {
20+ t.Fatal(err)
21+ }
22+ n := 0
23+ for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
24+ f := strings.Fields(line)
25+ if len(f) != 2 {
26+ t.Fatalf("bad line %q", line)
27+ }
28+ in := decodeUnits(f[0])
29+ var want float64
30+ if f[1] == "NaN" {
31+ want = math.NaN()
32+ } else {
33+ bits, err := strconv.ParseUint(f[1], 16, 64)
34+ if err != nil {
35+ t.Fatal(err)
36+ }
37+ want = math.Float64frombits(bits)
38+ }
39+ got := parseNumber(in)
40+ if math.Float64bits(got) != math.Float64bits(want) && !(math.IsNaN(got) && math.IsNaN(want)) {
41+ t.Errorf("parseNumber(%q [%s]) = %v (%x), runner %v (%x)", in, f[0], got, math.Float64bits(got), want, math.Float64bits(want))
42+ }
43+ n++
44+ }
45+ if n < 400 {
46+ t.Errorf("only %d cases", n)
47+ }
48+}
49+
50+// decodeUnits decodes dot-separated hexadecimal UTF-16 code units ("-" is the
51+// empty string).
52+func decodeUnits(h string) string {
53+ if h == "-" {
54+ return ""
55+ }
56+ var units []uint16
57+ for _, u := range strings.Split(h, ".") {
58+ n, _ := strconv.ParseUint(u, 16, 16)
59+ units = append(units, uint16(n))
60+ }
61+ return utf16String(units)
62+}
M expr/oracle_test.go +40 -15
@@ -128,17 +128,37 @@ type oracleRun struct {
128 ctxNames []string128 ctxNames []string
129 failures int129 failures int
130 c oracleCase130 c oracleCase
131+ // pending holds the mismatches of the current case until the case is
132+ // settled: a mismatch with the JavaScript reference is excused when the
133+ // C# runner, which is what runs on github.com, gives this package's answer.
134+ pending []string
131 }135 }
132 136
133 func (r *oracleRun) fail(format string, args ...any) {137 func (r *oracleRun) fail(format string, args ...any) {
134- r.failures++138+ r.pending = append(r.pending, fmt.Sprintf(format, args...))
135- if r.failures <= 40 {139+}
136- e := r.c.E140+
137- if len(e) > 200 {141+// settle reports the pending mismatches of the current case, unless the
138- e = e[:200] + "..."142+// runner agrees with this package. It returns true when the case was excused.
143+func (r *oracleRun) settle(rc runnerCase, ctxNames []string) (excused bool) {
144+ if len(r.pending) == 0 {
145+ return false
146+ }
147+ defer func() { r.pending = r.pending[:0] }()
148+ if rc.E == r.c.E && sameAnswer(goAnswer(r.c.E, ctxNames, r.ec), rc) {
149+ return true
150+ }
151+ for _, m := range r.pending {
152+ r.failures++
153+ if r.failures <= 40 {
154+ e := r.c.E
155+ if len(e) > 200 {
156+ e = e[:200] + "..."
157+ }
158+ r.t.Errorf("%q: %s", e, m)
139 }159 }
140- r.t.Errorf("%q: %s", e, fmt.Sprintf(format, args...))
141 }160 }
161+ return false
142 }162 }
143 163
144 // checkLex compares the tokens and reports whether the case lexes.164 // checkLex compares the tokens and reports whether the case lexes.
@@ -264,8 +284,13 @@ func TestOracle(t *testing.T) {
264 t.Logf("reference: %s, integrity %s, generated with node %s (V8 %s, Unicode %s)", ref.Package, ref.Integrity, ref.Node, ref.V8, ref.Unicode)284 t.Logf("reference: %s, integrity %s, generated with node %s (V8 %s, Unicode %s)", ref.Package, ref.Integrity, ref.Node, ref.V8, ref.Unicode)
265 285
266 run := &oracleRun{t: t, ctxNames: ctxNames, ec: EvalContext{Contexts: ctxValues, Funcs: oracleFuncs, MaxResultBytes: -1}}286 run := &oracleRun{t: t, ctxNames: ctxNames, ec: EvalContext{Contexts: ctxValues, Funcs: oracleFuncs, MaxResultBytes: -1}}
287+ rf := loadRunner(t)
288+ if len(rf.Cases) != len(of.Cases) {
289+ t.Fatalf("runner answers %d cases, oracle has %d", len(rf.Cases), len(of.Cases))
290+ }
267 var counts struct{ lexErr, parseErr, evalErr, ok, empty int }291 var counts struct{ lexErr, parseErr, evalErr, ok, empty int }
268- for _, c := range of.Cases {292+ excused := 0
293+ for i, c := range of.Cases {
269 run.c = c294 run.c = c
270 switch {295 switch {
271 case c.LexErr != nil:296 case c.LexErr != nil:
@@ -279,17 +304,17 @@ func TestOracle(t *testing.T) {
279 default:304 default:
280 counts.ok++305 counts.ok++
281 }306 }
282- if !run.checkLex() {307+ if run.checkLex() {
283- continue308+ if node := run.checkParse(); node != nil {
309+ run.checkEval(node)
310+ }
284 }311 }
285- node := run.checkParse()312+ if run.settle(rf.Cases[i], ctxNames) {
286- if node == nil {313+ excused++
287- continue
288 }314 }
289- run.checkEval(node)
290 }315 }
291- t.Logf("%d cases: %d lex errors, %d parse errors, %d empty, %d eval errors, %d values; %d failures",316+ t.Logf("%d cases: %d lex errors, %d parse errors, %d empty, %d eval errors, %d values; %d differ from the JavaScript where the C# runner agrees with this package; %d failures",
292- len(of.Cases), counts.lexErr, counts.parseErr, counts.empty, counts.evalErr, counts.ok, run.failures)317+ len(of.Cases), counts.lexErr, counts.parseErr, counts.empty, counts.evalErr, counts.ok, excused, run.failures)
293 if run.failures > 0 {318 if run.failures > 0 {
294 t.Fatalf("%d oracle mismatches", run.failures)319 t.Fatalf("%d oracle mismatches", run.failures)
295 }320 }
A expr/ordinal.go +76 -0
@@ -0,0 +1,76 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "sort"
7+ "strings"
8+)
9+
10+// The runner compares and looks up strings with .NET's
11+// StringComparison.OrdinalIgnoreCase and StringComparer.OrdinalIgnoreCase:
12+// every code point is replaced by its simple upper-case form (a per-character
13+// table, not full Unicode case mapping), then the UTF-16 code units are
14+// compared. Sources, all in actions/runner src/Sdk/DTExpressions2/Expressions2:
15+//
16+// == != < <= > >= EvaluationResult.cs AbstractEqual/AbstractGreaterThan/
17+// AbstractLessThan: String.Equals / String.Compare
18+// contains Sdk/Functions/Contains.cs: IndexOf(..., OrdinalIgnoreCase)
19+// startsWith Sdk/Functions/StartsWith.cs
20+// endsWith Sdk/Functions/EndsWith.cs
21+// contexts, functions and dictionary keys:
22+// ExpressionParser.cs ParseContext and
23+// DTPipelines/Pipelines/ContextData/DictionaryContextData.cs
24+// (Dictionary<string,int>(StringComparer.OrdinalIgnoreCase))
25+//
26+// Differences from the JavaScript reference: "ß" is not "SS" (no multi-
27+// character mappings), the Kelvin sign is not "k", the dotless "ı" and the
28+// long "ſ" stay themselves, and contains() uses the same folding as the other
29+// comparisons. fold_tables.go is generated from the runner's own behaviour
30+// (go generate, gen/runner --fold).
31+
32+//go:generate sh -c "dotnet gen/runner/bin/Release/net9.0/runner.dll --fold | go run gen/fold.go -o fold_tables.go"
33+
34+// ordinalFoldRune returns the code point that r is folded to.
35+func ordinalFoldRune(r rune) rune {
36+ if r < 0x80 {
37+ if r >= 'a' && r <= 'z' {
38+ return r - 'a' + 'A'
39+ }
40+ return r
41+ }
42+ i := sort.Search(len(ordinalUpper), func(i int) bool { return ordinalUpper[i][0] >= r })
43+ if i < len(ordinalUpper) && ordinalUpper[i][0] == r {
44+ return ordinalUpper[i][1]
45+ }
46+ return r
47+}
48+
49+// ordinalFold folds s. Bytes that are not UTF-8 become U+FFFD, as everywhere
50+// in this package.
51+func ordinalFold(s string) string {
52+ if isASCII(s) {
53+ return strings.ToUpper(s)
54+ }
55+ var b strings.Builder
56+ b.Grow(len(s))
57+ for i := 0; i < len(s); {
58+ r, w := decodeRune(s[i:])
59+ i += w
60+ writeRune(&b, ordinalFoldRune(r))
61+ }
62+ return b.String()
63+}
64+
65+// ordinalEqual is String.Equals(a, b, OrdinalIgnoreCase).
66+func ordinalEqual(a, b string) bool { return ordinalFold(a) == ordinalFold(b) }
67+
68+// ordinalCompare is Math.Sign(String.Compare(a, b, OrdinalIgnoreCase)). The
69+// runner orders by code point after folding (a supplementary character sorts
70+// after U+E000..U+FFFF, unlike in JavaScript, which compares UTF-16 units),
71+// which is the byte order of UTF-8.
72+func ordinalCompare(a, b string) int { return strings.Compare(ordinalFold(a), ordinalFold(b)) }
73+
74+// foldKey is the case-insensitive form of a dictionary key, context name or
75+// function name.
76+func foldKey(key string) string { return ordinalFold(key) }
A expr/ordinal_test.go +73 -0
@@ -0,0 +1,73 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "os"
7+ "strings"
8+ "testing"
9+ "unicode/utf16"
10+)
11+
12+// TestOrdinalFoldMatchesRunner replays a few hundred strings through .NET's
13+// StringComparison.OrdinalIgnoreCase, recorded by `runner.dll --cmp` (see
14+// gen/runner), and checks equality, ordering, contains, startsWith and
15+// endsWith. The cases are chosen around what differs from JavaScript: ß, ʼn,
16+// ı, İ, ſ, the Kelvin and Angstrom signs, final sigma, titlecase digraphs,
17+// supplementary letters, and code points on both sides of U+E000.
18+func TestOrdinalFoldMatchesRunner(t *testing.T) {
19+ // "a b want" where a and b are dot-separated UTF-16 code units in hex and
20+ // want is "equal order contains startsWith endsWith" as printed by --cmp.
21+ data, err := os.ReadFile("testdata/ordinal_cases.txt")
22+ if err != nil {
23+ t.Fatal(err)
24+ }
25+ decode := func(h string) string {
26+ if h == "-" {
27+ return ""
28+ }
29+ var units []uint16
30+ for _, u := range strings.Split(h, ".") {
31+ var n uint16
32+ for _, c := range u {
33+ n = n<<4 | uint16(strings.IndexRune("0123456789ABCDEF", c))
34+ }
35+ units = append(units, n)
36+ }
37+ return utf16String(units)
38+ }
39+ n := 0
40+ for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
41+ f := strings.Fields(line)
42+ if len(f) != 7 {
43+ t.Fatalf("bad line %q", line)
44+ }
45+ a, b := decode(f[0]), decode(f[1])
46+ want := strings.Join(f[2:], " ")
47+ b2i := func(v bool) string {
48+ if v {
49+ return "1"
50+ }
51+ return "0"
52+ }
53+ fa, fb := ordinalFold(a), ordinalFold(b)
54+ got := strings.Join([]string{
55+ b2i(ordinalEqual(a, b)),
56+ map[int]string{-1: "-1", 0: "0", 1: "1"}[ordinalCompare(a, b)],
57+ b2i(jsIncludes(fa, fb)),
58+ b2i(jsHasPrefix(fa, fb)),
59+ b2i(jsHasSuffix(fa, fb)),
60+ }, " ")
61+ if got != want {
62+ t.Errorf("%q vs %q (%s %s): got %s, runner %s", a, b, f[0], f[1], got, want)
63+ }
64+ n++
65+ }
66+ if n < 300 {
67+ t.Errorf("only %d cases", n)
68+ }
69+}
70+
71+// utf16String decodes UTF-16 code units. The cases here hold only paired
72+// surrogates.
73+func utf16String(units []uint16) string { return string(utf16.Decode(units)) }
A expr/parse_runner.go +790 -0
@@ -0,0 +1,790 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "sort"
7+ "strconv"
8+ "strings"
9+ "unicode/utf16"
10+)
11+
12+// This file is a port of the runner's tokenizer and parser:
13+// src/Sdk/DTExpressions2/Expressions2/Tokens/LexicalAnalyzer.cs, Tokens/Token.cs
14+// and ExpressionParser.cs. They differ from the JavaScript reference's
15+// (lexer.go, parser.go) in ways users can see:
16+//
17+// - the tokenizer is context sensitive: each token is checked against the
18+// one before it, so "a b", "1 2" or "f(1,)" fail at the second token;
19+// - tokens are produced on demand and the parser stops at the first
20+// unexpected token or unknown name, in order of appearance (the JavaScript
21+// lexes everything first, so a later lexical error wins there);
22+// - white space is Char.IsWhiteSpace (U+0085 and U+00A0 included);
23+// - "Unexpected end of expression" is reported for an unfinished expression
24+// where the JavaScript says "Unexpected symbol: 'EOF'";
25+// - error positions are absolute indexes in the expression, not columns;
26+// - the depth limit applies to the shape of the finished tree, in which
27+// parentheses do not count and a flat run of && or || is one node;
28+// - the "too long" and "too deep" errors carry no token.
29+//
30+// Parse builds the same AST as the JavaScript-style parser did.
31+
32+type csKind int
33+
34+const (
35+ csStartGroup csKind = iota
36+ csStartIndex
37+ csStartParameters
38+ csEndGroup
39+ csEndIndex
40+ csEndParameters
41+ csSeparator
42+ csDereference
43+ csWildcard
44+ csLogicalOperator
45+ csNull
46+ csBoolean
47+ csNumber
48+ csString
49+ csPropertyName
50+ csFunction
51+ csNamedValue
52+ csUnexpected
53+)
54+
55+type csToken struct {
56+ kind csKind
57+ raw string
58+ index int
59+ value Value // Boolean, Number or String for literals
60+}
61+
62+func (t *csToken) isOperator() bool {
63+ switch t.kind {
64+ case csStartGroup, csStartIndex, csStartParameters, csEndGroup, csEndIndex, csEndParameters, csSeparator, csDereference, csLogicalOperator:
65+ return true
66+ }
67+ return false
68+}
69+
70+// leftToRight reports whether the token is a left-associative operator.
71+func (t *csToken) leftToRight() bool {
72+ switch {
73+ case t.kind == csStartGroup:
74+ return false
75+ case t.kind == csLogicalOperator && t.raw == "!":
76+ return false
77+ }
78+ return t.isOperator()
79+}
80+
81+func (t *csToken) precedence() int {
82+ switch t.kind {
83+ case csStartGroup:
84+ return 20
85+ case csStartIndex, csStartParameters, csDereference:
86+ return 19
87+ case csLogicalOperator:
88+ switch t.raw {
89+ case "!":
90+ return 16
91+ case ">", ">=", "<", "<=":
92+ return 11
93+ case "==", "!=":
94+ return 10
95+ case "&&":
96+ return 6
97+ case "||":
98+ return 5
99+ }
100+ case csEndGroup, csEndIndex, csEndParameters, csSeparator:
101+ return 1
102+ }
103+ return 0
104+}
105+
106+func (t *csToken) operandCount() int {
107+ switch t.kind {
108+ case csStartIndex, csDereference:
109+ return 2
110+ case csLogicalOperator:
111+ if t.raw == "!" {
112+ return 1
113+ }
114+ return 2
115+ }
116+ return 0
117+}
118+
119+// csLexer is LexicalAnalyzer.
120+type csLexer struct {
121+ units []uint16
122+ index int
123+ last *csToken
124+ unclosed []*csToken // stack of "(", "[" and function "("
125+}
126+
127+func (l *csLexer) substr(from, to int) string { return string(utf16.Decode(l.units[from:to])) }
128+
129+func isCSWhiteSpace(c uint16) bool { return isNetSpace(rune(c)) }
130+
131+func isCSBoundary(c uint16) bool {
132+ switch c {
133+ case '(', '[', ')', ']', ',', '.', '!', '>', '<', '=', '&', '|':
134+ return true
135+ }
136+ return isCSWhiteSpace(c)
137+}
138+
139+func (l *csLexer) topUnclosed() csKind {
140+ if len(l.unclosed) == 0 {
141+ return -1
142+ }
143+ return l.unclosed[len(l.unclosed)-1].kind
144+}
145+
146+// next returns the next token, or nil at the end of the expression.
147+func (l *csLexer) next() *csToken {
148+ for l.index < len(l.units) && isCSWhiteSpace(l.units[l.index]) {
149+ l.index++
150+ }
151+ if l.index >= len(l.units) {
152+ return nil
153+ }
154+ c := l.units[l.index]
155+ var tok *csToken
156+ switch c {
157+ case '(':
158+ if l.last != nil && l.last.kind == csFunction {
159+ tok = l.create(csStartParameters, "(", l.index, nil)
160+ } else {
161+ tok = l.create(csStartGroup, "(", l.index, nil)
162+ }
163+ l.index++
164+ case '[':
165+ tok = l.create(csStartIndex, "[", l.index, nil)
166+ l.index++
167+ case ')':
168+ if l.topUnclosed() == csStartParameters {
169+ tok = l.create(csEndParameters, ")", l.index, nil)
170+ } else {
171+ tok = l.create(csEndGroup, ")", l.index, nil)
172+ }
173+ l.index++
174+ case ']':
175+ tok = l.create(csEndIndex, "]", l.index, nil)
176+ l.index++
177+ case ',':
178+ tok = l.create(csSeparator, ",", l.index, nil)
179+ l.index++
180+ case '*':
181+ tok = l.create(csWildcard, "*", l.index, nil)
182+ l.index++
183+ case '\'':
184+ tok = l.readString()
185+ case '!', '>', '<', '=', '&', '|':
186+ tok = l.readOperator()
187+ default:
188+ switch {
189+ case c == '.':
190+ if l.last == nil || l.last.kind == csSeparator || l.last.kind == csStartGroup || l.last.kind == csStartIndex ||
191+ l.last.kind == csStartParameters || l.last.kind == csLogicalOperator {
192+ tok = l.readNumber()
193+ } else {
194+ tok = l.create(csDereference, ".", l.index, nil)
195+ l.index++
196+ }
197+ case c == '-' || c == '+' || (c >= '0' && c <= '9'):
198+ tok = l.readNumber()
199+ default:
200+ tok = l.readKeyword()
201+ }
202+ }
203+ l.last = tok
204+ return tok
205+}
206+
207+func (l *csLexer) readNumber() *csToken {
208+ start := l.index
209+ for {
210+ l.index++
211+ if l.index >= len(l.units) || (isCSBoundary(l.units[l.index]) && l.units[l.index] != '.') {
212+ break
213+ }
214+ }
215+ str := l.substr(start, l.index)
216+ d := parseNumber(str)
217+ if d != d { // NaN
218+ return l.create(csUnexpected, str, start, nil)
219+ }
220+ return l.create(csNumber, str, start, Number(d))
221+}
222+
223+func isLegalKeyword(s string) bool { return isLegalIdentifier(s) }
224+
225+func (l *csLexer) readKeyword() *csToken {
226+ start := l.index
227+ l.index++
228+ for l.index < len(l.units) && !isCSBoundary(l.units[l.index]) {
229+ l.index++
230+ }
231+ str := l.substr(start, l.index)
232+ if !isLegalKeyword(str) {
233+ return l.create(csUnexpected, str, start, nil)
234+ }
235+ if l.last != nil && l.last.kind == csDereference {
236+ return l.create(csPropertyName, str, start, nil)
237+ }
238+ switch str {
239+ case "null":
240+ return l.create(csNull, str, start, Null{})
241+ case "true":
242+ return l.create(csBoolean, str, start, Boolean(true))
243+ case "false":
244+ return l.create(csBoolean, str, start, Boolean(false))
245+ case "NaN":
246+ return l.create(csNumber, str, start, Number(nan))
247+ case "Infinity":
248+ return l.create(csNumber, str, start, Number(posInf))
249+ }
250+ // Lookahead: a function is a name followed by "(", white space allowed.
251+ t := l.index
252+ for t < len(l.units) && isCSWhiteSpace(l.units[t]) {
253+ t++
254+ }
255+ if t < len(l.units) && l.units[t] == '(' {
256+ return l.create(csFunction, str, start, nil)
257+ }
258+ return l.create(csNamedValue, str, start, nil)
259+}
260+
261+func (l *csLexer) readString() *csToken {
262+ start := l.index
263+ var b strings.Builder
264+ closed := false
265+ l.index++ // the opening quote
266+ var run []uint16
267+ for l.index < len(l.units) {
268+ c := l.units[l.index]
269+ l.index++
270+ if c == '\'' {
271+ if l.index >= len(l.units) || l.units[l.index] != '\'' {
272+ closed = true
273+ break
274+ }
275+ l.index++ // an escaped quote
276+ }
277+ run = append(run, c)
278+ }
279+ b.WriteString(string(utf16.Decode(run)))
280+ raw := l.substr(start, l.index)
281+ if closed {
282+ return l.create(csString, raw, start, String(b.String()))
283+ }
284+ return l.create(csUnexpected, raw, start, nil)
285+}
286+
287+func (l *csLexer) readOperator() *csToken {
288+ start := l.index
289+ l.index++
290+ if l.index < len(l.units) {
291+ l.index++
292+ switch raw := l.substr(start, start+2); raw {
293+ case "!=", ">=", "<=", "==", "&&", "||":
294+ return l.create(csLogicalOperator, raw, start, nil)
295+ }
296+ l.index-- // back up
297+ }
298+ switch raw := l.substr(start, start+1); raw {
299+ case "!", ">", "<":
300+ return l.create(csLogicalOperator, raw, start, nil)
301+ }
302+ for l.index < len(l.units) && !isCSBoundary(l.units[l.index]) {
303+ l.index++
304+ }
305+ return l.create(csUnexpected, l.substr(start, l.index), start, nil)
306+}
307+
308+// lastIs reports whether the previous token is one of kinds; a nil kind list
309+// entry (csUnexpected here) stands for "no previous token".
310+func (l *csLexer) lastIs(kinds ...csKind) bool {
311+ if l.last == nil {
312+ for _, k := range kinds {
313+ if k == -1 {
314+ return true
315+ }
316+ }
317+ return false
318+ }
319+ for _, k := range kinds {
320+ if k == l.last.kind {
321+ return true
322+ }
323+ }
324+ return false
325+}
326+
327+const csFirst csKind = -1 // "is the first token"
328+
329+// create builds a token after checking that it may follow the previous one
330+// and that brackets are balanced; an illegal one becomes csUnexpected.
331+func (l *csLexer) create(kind csKind, raw string, index int, v Value) *csToken {
332+ var legal bool
333+ switch kind {
334+ case csStartGroup:
335+ legal = l.lastIs(csFirst, csSeparator, csStartGroup, csStartParameters, csStartIndex, csLogicalOperator)
336+ case csStartIndex:
337+ legal = l.lastIs(csEndGroup, csEndParameters, csEndIndex, csWildcard, csPropertyName, csNamedValue)
338+ case csStartParameters:
339+ legal = l.lastIs(csFunction)
340+ case csEndGroup, csEndIndex:
341+ legal = l.lastIs(csEndGroup, csEndParameters, csEndIndex, csWildcard, csNull, csBoolean, csNumber, csString, csPropertyName, csNamedValue)
342+ case csEndParameters:
343+ legal = l.lastIs(csStartParameters, csEndGroup, csEndParameters, csEndIndex, csWildcard, csNull, csBoolean, csNumber, csString, csPropertyName, csNamedValue)
344+ case csSeparator:
345+ legal = l.lastIs(csEndGroup, csEndParameters, csEndIndex, csWildcard, csNull, csBoolean, csNumber, csString, csPropertyName, csNamedValue)
346+ case csDereference:
347+ legal = l.lastIs(csEndGroup, csEndParameters, csEndIndex, csWildcard, csPropertyName, csNamedValue)
348+ case csWildcard:
349+ legal = l.lastIs(csStartIndex, csDereference)
350+ case csLogicalOperator:
351+ if raw == "!" {
352+ legal = l.lastIs(csFirst, csSeparator, csStartGroup, csStartParameters, csStartIndex, csLogicalOperator)
353+ } else {
354+ legal = l.lastIs(csEndGroup, csEndParameters, csEndIndex, csWildcard, csNull, csBoolean, csNumber, csString, csPropertyName, csNamedValue)
355+ }
356+ case csNull, csBoolean, csNumber, csString, csFunction, csNamedValue:
357+ legal = l.lastIs(csFirst, csSeparator, csStartIndex, csStartGroup, csStartParameters, csLogicalOperator)
358+ case csPropertyName:
359+ legal = l.lastIs(csDereference)
360+ }
361+ if !legal {
362+ return &csToken{kind: csUnexpected, raw: raw, index: index}
363+ }
364+ tok := &csToken{kind: kind, raw: raw, index: index, value: v}
365+ switch kind {
366+ case csStartGroup, csStartIndex, csStartParameters:
367+ l.unclosed = append(l.unclosed, tok)
368+ case csEndGroup:
369+ if l.topUnclosed() != csStartGroup {
370+ return &csToken{kind: csUnexpected, raw: raw, index: index}
371+ }
372+ l.unclosed = l.unclosed[:len(l.unclosed)-1]
373+ case csEndIndex:
374+ if l.topUnclosed() != csStartIndex {
375+ return &csToken{kind: csUnexpected, raw: raw, index: index}
376+ }
377+ l.unclosed = l.unclosed[:len(l.unclosed)-1]
378+ case csEndParameters:
379+ if l.topUnclosed() != csStartParameters {
380+ return &csToken{kind: csUnexpected, raw: raw, index: index}
381+ }
382+ l.unclosed = l.unclosed[:len(l.unclosed)-1]
383+ case csSeparator:
384+ if l.topUnclosed() != csStartParameters {
385+ return &csToken{kind: csUnexpected, raw: raw, index: index}
386+ }
387+ }
388+ return tok
389+}
390+
391+// csParser is ExpressionParser.CreateTree: a shunting-yard parser.
392+type csParser struct {
393+ expression string
394+ units []uint16
395+ lex *csLexer
396+ contexts map[string]bool
397+ funcs map[string]FuncInfo
398+ operands []Node
399+ operators []*csToken
400+ last *csToken // the previous token
401+ cur *csToken
402+ lineStarts []int // offsets of the first unit of each line, see position
403+}
404+
405+// fail builds the ParseException of the runner: with a token, the message names
406+// it and its absolute position; without, only the description.
407+func (p *csParser) fail(t ErrorType, tok *csToken) *Error {
408+ if tok == nil {
409+ return &Error{Type: t, Message: t.description()}
410+ }
411+ return &Error{
412+ Type: t,
413+ Message: t.description() + ": '" + tok.raw + "'. Located at position " + strconv.Itoa(tok.index+1) + " within expression: " + p.expression,
414+ Pos: p.position(tok.index),
415+ Token: tok.raw,
416+ }
417+}
418+
419+// position converts an absolute index into the line and column of the
420+// JavaScript-style Position (line breaks are U+000A). The offsets of the line
421+// starts are computed once, on first use, so that building the position of
422+// every token stays linear in the length of the expression.
423+func (p *csParser) position(index int) Position {
424+ if p.lineStarts == nil {
425+ p.lineStarts = []int{0}
426+ for i, u := range p.units {
427+ if u == '\n' {
428+ p.lineStarts = append(p.lineStarts, i+1)
429+ }
430+ }
431+ }
432+ // The line is the last start that is <= index.
433+ line := sort.Search(len(p.lineStarts), func(i int) bool { return p.lineStarts[i] > index }) - 1
434+ return Position{Line: line, Column: index - p.lineStarts[line]}
435+}
436+
437+func (p *csParser) token(t *csToken, typ TokenType) Token {
438+ end := t.index + len(utf16.Encode([]rune(t.raw)))
439+ return Token{Type: typ, Lexeme: t.raw, Range: Range{Start: p.position(t.index), End: p.position(end)}, Value: t.value}
440+}
441+
442+func (p *csParser) wellKnownOrExtension(name string) (FuncInfo, bool) {
443+ k := foldKey(name)
444+ if f, ok := builtinFuncs[k]; ok {
445+ return f, true
446+ }
447+ f, ok := p.funcs[k]
448+ return f, ok
449+}
450+
451+// parseRunner parses expression the way the runner does.
452+func parseRunner(expression string, contexts []string, funcs []FuncInfo) (Node, error) {
453+ p := &csParser{contexts: make(map[string]bool, len(contexts)), funcs: make(map[string]FuncInfo, len(funcs))}
454+ if exceedsUTF16Len(expression, MaxExpressionLength) {
455+ return nil, p.fail(ErrorExceededMaxLength, nil)
456+ }
457+ p.units = utf16.Encode([]rune(expression))
458+ p.expression = string(utf16.Decode(p.units))
459+ for _, c := range contexts {
460+ p.contexts[foldKey(c)] = true
461+ }
462+ for _, f := range funcs {
463+ p.funcs[foldKey(f.Name)] = f
464+ }
465+ p.lex = &csLexer{units: p.units}
466+ return p.createTree()
467+}
468+
469+func (p *csParser) createTree() (Node, error) {
470+ for {
471+ tok := p.lex.next()
472+ if tok == nil {
473+ break
474+ }
475+ p.cur = tok
476+ switch {
477+ case tok.kind == csUnexpected:
478+ return nil, p.fail(ErrorUnexpectedSymbol, tok)
479+ case tok.isOperator():
480+ if err := p.pushOperator(tok); err != nil {
481+ return nil, err
482+ }
483+ default:
484+ if err := p.pushOperand(tok); err != nil {
485+ return nil, err
486+ }
487+ }
488+ p.last = tok
489+ }
490+ if p.last == nil {
491+ return nil, nil
492+ }
493+ if len(p.operators) > 0 {
494+ unexpected := false
495+ switch p.last.kind {
496+ case csEndGroup, csEndIndex, csEndParameters:
497+ case csFunction:
498+ unexpected = true
499+ default:
500+ unexpected = p.last.isOperator()
501+ }
502+ if unexpected || len(p.lex.unclosed) > 0 {
503+ return nil, p.fail(ErrorUnexpectedEndOfExpression, p.last)
504+ }
505+ }
506+ for len(p.operators) > 0 {
507+ if err := p.flushTop(); err != nil {
508+ return nil, err
509+ }
510+ }
511+ if len(p.operands) != 1 {
512+ return nil, p.fail(ErrorUnexpectedSymbol, p.last)
513+ }
514+ root := p.operands[0]
515+ if exceedsDepth(root, 1) {
516+ return nil, p.fail(ErrorExceededMaxDepth, nil)
517+ }
518+ return root, nil
519+}
520+
521+func (p *csParser) pushOperand(tok *csToken) error {
522+ var n Node
523+ switch tok.kind {
524+ case csFunction:
525+ if _, ok := p.wellKnownOrExtension(tok.raw); !ok {
526+ return p.fail(ErrorUnrecognizedFunction, tok)
527+ }
528+ n = &FunctionCall{Name: p.token(tok, TokenIdentifier)}
529+ case csNamedValue:
530+ if !p.contexts[foldKey(tok.raw)] {
531+ return p.fail(ErrorUnrecognizedContext, tok)
532+ }
533+ n = &ContextAccess{Name: p.token(tok, TokenIdentifier)}
534+ case csNull:
535+ n = &Literal{Value: Null{}, Token: p.token(tok, TokenNull)}
536+ case csBoolean:
537+ typ := TokenFalse
538+ if tok.value == Boolean(true) {
539+ typ = TokenTrue
540+ }
541+ n = &Literal{Value: tok.value, Token: p.token(tok, typ)}
542+ case csNumber:
543+ n = &Literal{Value: tok.value, Token: p.token(tok, TokenNumber)}
544+ case csString:
545+ n = &Literal{Value: tok.value, Token: p.token(tok, TokenString)}
546+ case csPropertyName:
547+ n = &Literal{Value: String(tok.raw), Token: p.token(tok, TokenIdentifier)}
548+ case csWildcard:
549+ n = &Star{}
550+ default:
551+ return p.fail(ErrorUnexpectedSymbol, tok)
552+ }
553+ p.operands = append(p.operands, n)
554+ return nil
555+}
556+
557+func (p *csParser) pushOperator(tok *csToken) error {
558+ if tok.leftToRight() {
559+ prec := tok.precedence()
560+ for len(p.operators) > 0 {
561+ top := p.operators[len(p.operators)-1]
562+ if prec <= top.precedence() && top.kind != csStartGroup && top.kind != csStartIndex &&
563+ top.kind != csStartParameters && top.kind != csSeparator {
564+ if err := p.flushTop(); err != nil {
565+ return err
566+ }
567+ continue
568+ }
569+ break
570+ }
571+ }
572+ p.operators = append(p.operators, tok)
573+ switch tok.kind {
574+ case csEndGroup, csEndIndex, csEndParameters:
575+ return p.flushTop()
576+ }
577+ return nil
578+}
579+
580+func (p *csParser) popOperator(want csKind) (*csToken, error) {
581+ if len(p.operators) == 0 || p.operators[len(p.operators)-1].kind != want {
582+ return nil, p.fail(ErrorUnexpectedSymbol, p.cur)
583+ }
584+ t := p.operators[len(p.operators)-1]
585+ p.operators = p.operators[:len(p.operators)-1]
586+ return t, nil
587+}
588+
589+// popOperands pops n operands, returned in their written order.
590+func (p *csParser) popOperands(n int) ([]Node, error) {
591+ if len(p.operands) < n {
592+ return nil, p.fail(ErrorUnexpectedSymbol, p.cur)
593+ }
594+ out := append([]Node(nil), p.operands[len(p.operands)-n:]...)
595+ p.operands = p.operands[:len(p.operands)-n]
596+ return out, nil
597+}
598+
599+func (p *csParser) flushTop() error {
600+ top := p.operators[len(p.operators)-1]
601+ switch top.kind {
602+ case csEndIndex:
603+ if _, err := p.popOperator(csEndIndex); err != nil {
604+ return err
605+ }
606+ op, err := p.popOperator(csStartIndex)
607+ if err != nil {
608+ return err
609+ }
610+ args, err := p.popOperands(op.operandCount())
611+ if err != nil {
612+ return err
613+ }
614+ p.operands = append(p.operands, &IndexAccess{Expr: args[0], Index: args[1]})
615+ return nil
616+ case csEndGroup:
617+ if _, err := p.popOperator(csEndGroup); err != nil {
618+ return err
619+ }
620+ if _, err := p.popOperator(csStartGroup); err != nil {
621+ return err
622+ }
623+ if len(p.operands) == 0 {
624+ return p.fail(ErrorUnexpectedSymbol, p.cur)
625+ }
626+ g := &Grouping{Group: p.operands[len(p.operands)-1]}
627+ p.operands[len(p.operands)-1] = g
628+ return nil
629+ case csEndParameters:
630+ return p.flushEndParameters()
631+ }
632+ op, _ := p.popOperator(top.kind)
633+ args, err := p.popOperands(op.operandCount())
634+ if err != nil {
635+ return err
636+ }
637+ switch {
638+ case op.kind == csStartIndex || op.kind == csDereference:
639+ p.operands = append(p.operands, &IndexAccess{Expr: args[0], Index: args[1]})
640+ case op.kind == csLogicalOperator && op.raw == "!":
641+ p.operands = append(p.operands, &Unary{Operator: p.token(op, TokenBang), Expr: args[0]})
642+ case op.kind == csLogicalOperator && (op.raw == "&&" || op.raw == "||"):
643+ typ := TokenAnd
644+ if op.raw == "||" {
645+ typ = TokenOr
646+ }
647+ l := &Logical{Operator: p.token(op, typ)}
648+ for i, a := range args {
649+ // A nested chain of the same operator is merged, as the runner does.
650+ // The left operand of "a || b || c" is the chain built so far: its
651+ // slice is extended instead of copied, which keeps a long chain
652+ // linear.
653+ if nested, ok := a.(*Logical); ok && nested.Operator.Type == typ {
654+ if i == 0 {
655+ l.Operator = nested.Operator
656+ l.Args = nested.Args
657+ } else {
658+ l.Args = append(l.Args, nested.Args...)
659+ }
660+ } else {
661+ l.Args = append(l.Args, a)
662+ }
663+ }
664+ p.operands = append(p.operands, l)
665+ case op.kind == csLogicalOperator:
666+ var typ TokenType
667+ switch op.raw {
668+ case "!=":
669+ typ = TokenBangEqual
670+ case "==":
671+ typ = TokenEqualEqual
672+ case ">":
673+ typ = TokenGreater
674+ case ">=":
675+ typ = TokenGreaterEqual
676+ case "<":
677+ typ = TokenLess
678+ default:
679+ typ = TokenLessEqual
680+ }
681+ p.operands = append(p.operands, &Binary{Left: args[0], Operator: p.token(op, typ), Right: args[1]})
682+ default:
683+ return p.fail(ErrorUnexpectedSymbol, op)
684+ }
685+ return nil
686+}
687+
688+func (p *csParser) flushEndParameters() error {
689+ end, err := p.popOperator(csEndParameters)
690+ if err != nil {
691+ return err
692+ }
693+ if end != p.cur || p.last == nil {
694+ return p.fail(ErrorUnexpectedSymbol, p.cur)
695+ }
696+ count := 0
697+ if p.last.kind != csStartParameters {
698+ count = 1
699+ for len(p.operators) > 0 && p.operators[len(p.operators)-1].kind == csSeparator {
700+ count++
701+ p.operators = p.operators[:len(p.operators)-1]
702+ }
703+ }
704+ args, err := p.popOperands(count)
705+ if err != nil {
706+ return err
707+ }
708+ if len(p.operands) == 0 {
709+ return p.fail(ErrorUnexpectedSymbol, p.cur)
710+ }
711+ call, ok := p.operands[len(p.operands)-1].(*FunctionCall)
712+ if !ok {
713+ return p.fail(ErrorUnexpectedSymbol, p.cur)
714+ }
715+ call.Args = args
716+ open, err := p.popOperator(csStartParameters)
717+ if err != nil {
718+ return err
719+ }
720+ info, _ := p.wellKnownOrExtension(call.Name.Lexeme)
721+ switch {
722+ case len(args) < info.MinArgs:
723+ return p.fail(ErrorTooFewParameters, open)
724+ case info.MaxArgs >= 0 && len(args) > info.MaxArgs:
725+ return p.fail(ErrorTooManyParameters, open)
726+ case strings.EqualFold(info.Name, "case") && len(args)%2 == 0:
727+ return p.fail(ErrorEvenParameters, open)
728+ }
729+ return nil
730+}
731+
732+// exceedsDepth reports whether the tree is deeper than MaxParserDepth, counted
733+// as the runner does (CheckMaxDepth): the root is at depth 1, the operands of
734+// an operator, the arguments of a call and the two parts of an index access
735+// are one deeper, parentheses add nothing, and a run of && or || is one node,
736+// also when parenthesized.
737+func exceedsDepth(n Node, depth int) bool {
738+ for {
739+ g, ok := n.(*Grouping)
740+ if !ok {
741+ break
742+ }
743+ n = g.Group
744+ }
745+ if depth > MaxParserDepth {
746+ return true
747+ }
748+ switch n := n.(type) {
749+ case *Unary:
750+ return exceedsDepth(n.Expr, depth+1)
751+ case *Binary:
752+ return exceedsDepth(n.Left, depth+1) || exceedsDepth(n.Right, depth+1)
753+ case *Logical:
754+ for _, a := range flatOperands(n, nil) {
755+ if exceedsDepth(a, depth+1) {
756+ return true
757+ }
758+ }
759+ case *FunctionCall:
760+ for _, a := range n.Args {
761+ if exceedsDepth(a, depth+1) {
762+ return true
763+ }
764+ }
765+ case *IndexAccess:
766+ return exceedsDepth(n.Expr, depth+1) || exceedsDepth(n.Index, depth+1)
767+ }
768+ return false
769+}
770+
771+// flatOperands lists the operands of a && or || chain with nested chains of
772+// the same operator (parenthesized or not) merged into it.
773+func flatOperands(l *Logical, out []Node) []Node {
774+ for _, a := range l.Args {
775+ inner := a
776+ for {
777+ g, ok := inner.(*Grouping)
778+ if !ok {
779+ break
780+ }
781+ inner = g.Group
782+ }
783+ if nested, ok := inner.(*Logical); ok && nested.Operator.Type == l.Operator.Type {
784+ out = flatOperands(nested, out)
785+ } else {
786+ out = append(out, a)
787+ }
788+ }
789+ return out
790+}
M expr/parser.go +13 -328
@@ -18,18 +18,18 @@ type FuncInfo struct {
18 MaxArgs int18 MaxArgs int
19 }19 }
20 20
21-// builtinFuncs are the functions of the reference's wellKnownFunctions, keyed21+// builtinFuncs are the functions of ExpressionConstants.WellKnownFunctions, keyed
22-// by lower-cased name. They take precedence over caller-supplied FuncInfo for22+// by upper-cased name (see foldKey). They take precedence over caller-supplied FuncInfo for
23 // validation.23 // validation.
24 var builtinFuncs = map[string]FuncInfo{24 var builtinFuncs = map[string]FuncInfo{
25- "case": {"case", 3, math.MaxInt},25+ "CASE": {"case", 3, math.MaxInt},
26- "contains": {"contains", 2, 2},26+ "CONTAINS": {"contains", 2, 2},
27- "endswith": {"endsWith", 2, 2},27+ "ENDSWITH": {"endsWith", 2, 2},
28- "format": {"format", 1, 255},28+ "FORMAT": {"format", 1, 255},
29- "fromjson": {"fromJson", 1, 1},29+ "FROMJSON": {"fromJson", 1, 1},
30- "join": {"join", 1, 2},30+ "JOIN": {"join", 1, 2},
31- "startswith": {"startsWith", 2, 2},31+ "STARTSWITH": {"startsWith", 2, 2},
32- "tojson": {"toJson", 1, 1},32+ "TOJSON": {"toJson", 1, 1},
33 }33 }
34 34
35 // Parse parses the text of one expression (the inside of ${{ }}).35 // Parse parses the text of one expression (the inside of ${{ }}).
@@ -43,334 +43,19 @@ var builtinFuncs = map[string]FuncInfo{
43 // "" or " ", is not an error: the reference returns nothing for it, and Parse43 // "" or " ", is not an error: the reference returns nothing for it, and Parse
44 // returns a nil [Node], which [Evaluate] rejects.44 // returns a nil [Node], which [Evaluate] rejects.
45 func Parse(expr string, contexts []string, funcs []FuncInfo) (Node, error) {45 func Parse(expr string, contexts []string, funcs []FuncInfo) (Node, error) {
46- tokens, err := Lex(expr)46+ return parseRunner(expr, contexts, funcs)
47- if err != nil {
48- return nil, err
49- }
50- p := &parser{
51- tokens: tokens,
52- contexts: make(map[string]bool, len(contexts)),
53- funcs: make(map[string]FuncInfo, len(funcs)),
54- }
55- for _, c := range contexts {
56- p.contexts[foldKey(c)] = true
57- }
58- for _, f := range funcs {
59- p.funcs[foldKey(f.Name)] = f
60- }
61- return p.parse()
62-}
63-
64-// parser is a port of Parser in parser.js: recursive descent, with the same
65-// precedence (lowest first): ||, &&, == !=, < <= > >=, !, then index and
66-// call.
67-type parser struct {
68- tokens []Token
69- offset int
70- depth int
71- contexts map[string]bool
72- funcs map[string]FuncInfo
73-}
74-
75-func (p *parser) parse() (Node, error) {
76- if p.atEnd() {
77- return nil, nil
78- }
79- n, err := p.expression()
80- if err != nil {
81- return nil, err
82- }
83- if !p.atEnd() {
84- return nil, newTokenError(ErrorUnexpectedSymbol, p.peek())
85- }
86- return n, nil
87-}
88-
89-func (p *parser) expression() (Node, error) {
90- if err := p.incrDepth(); err != nil {
91- return nil, err
92- }
93- n, err := p.logicalOr()
94- p.depth--
95- return n, err
96-}
97-
98-func (p *parser) logicalOr() (Node, error) {
99- n, err := p.logicalAnd()
100- if err != nil || !p.check(TokenOr) {
101- return n, err
102- }
103- return p.logicalChain(TokenOr, n, p.logicalAnd)
104-}
105-
106-func (p *parser) logicalAnd() (Node, error) {
107- n, err := p.equality()
108- if err != nil || !p.check(TokenAnd) {
109- return n, err
110- }
111- return p.logicalChain(TokenAnd, n, p.equality)
112-}
113-
114-// logicalChain parses the rest of a flat chain of operator operands, the
115-// first of which is already parsed.
116-func (p *parser) logicalChain(op TokenType, first Node, operand func() (Node, error)) (Node, error) {
117- if err := p.incrDepth(); err != nil {
118- return nil, err
119- }
120- logical := &Logical{Operator: p.peek(), Args: []Node{first}}
121- for p.match(op) {
122- right, err := operand()
123- if err != nil {
124- return nil, err
125- }
126- logical.Args = append(logical.Args, right)
127- }
128- p.depth--
129- return logical, nil
130-}
131-
132-func (p *parser) equality() (Node, error) {
133- n, err := p.comparison()
134- if err != nil {
135- return nil, err
136- }
137- for p.match(TokenBangEqual, TokenEqualEqual) {
138- op := p.previous()
139- right, err := p.comparison()
140- if err != nil {
141- return nil, err
142- }
143- n = &Binary{Left: n, Operator: op, Right: right}
144- }
145- return n, nil
146-}
147-
148-func (p *parser) comparison() (Node, error) {
149- n, err := p.unary()
150- if err != nil {
151- return nil, err
152- }
153- for p.match(TokenGreater, TokenGreaterEqual, TokenLess, TokenLessEqual) {
154- op := p.previous()
155- right, err := p.unary()
156- if err != nil {
157- return nil, err
158- }
159- n = &Binary{Left: n, Operator: op, Right: right}
160- }
161- return n, nil
162-}
163-
164-func (p *parser) unary() (Node, error) {
165- if p.match(TokenBang) {
166- if err := p.incrDepth(); err != nil {
167- return nil, err
168- }
169- op := p.previous()
170- operand, err := p.unary()
171- if err != nil {
172- return nil, err
173- }
174- p.depth--
175- return &Unary{Operator: op, Expr: operand}, nil
176- }
177- return p.index()
178-}
179-
180-func (p *parser) index() (Node, error) {
181- n, err := p.call()
182- if err != nil {
183- return nil, err
184- }
185- depthIncreased := 0
186- switch n.(type) {
187- case *Grouping, *FunctionCall, *ContextAccess:
188- loop:
189- for {
190- switch {
191- case p.match(TokenLeftBracket):
192- var idx Node
193- if p.match(TokenStar) {
194- idx = &Star{}
195- } else if idx, err = p.expression(); err != nil {
196- return nil, err
197- }
198- if err := p.consume(TokenRightBracket, ErrorUnexpectedSymbol); err != nil {
199- return nil, err
200- }
201- if err := p.incrDepth(); err != nil {
202- return nil, err
203- }
204- depthIncreased++
205- n = &IndexAccess{Expr: n, Index: idx}
206- case p.match(TokenDot):
207- if err := p.incrDepth(); err != nil {
208- return nil, err
209- }
210- depthIncreased++
211- switch {
212- case p.match(TokenIdentifier):
213- prop := p.previous()
214- n = &IndexAccess{Expr: n, Index: &Literal{Value: String(prop.Lexeme), Token: prop}}
215- case p.match(TokenStar):
216- n = &IndexAccess{Expr: n, Index: &Star{}}
217- default:
218- return nil, newTokenError(ErrorUnexpectedSymbol, p.peek())
219- }
220- default:
221- break loop
222- }
223- }
224- }
225- p.depth -= depthIncreased
226- return n, nil
227-}
228-
229-func (p *parser) call() (Node, error) {
230- if !p.check(TokenIdentifier) {
231- return p.primary()
232- }
233- ident := p.next()
234- if !p.match(TokenLeftParen) {
235- if !p.contexts[foldKey(ident.Lexeme)] {
236- return nil, newTokenError(ErrorUnrecognizedContext, ident)
237- }
238- return &ContextAccess{Name: ident}, nil
239- }
240- var args []Node
241- for !p.match(TokenRightParen) {
242- arg, err := p.expression()
243- if err != nil {
244- return nil, err
245- }
246- args = append(args, arg)
247- if !p.check(TokenRightParen) {
248- // A trailing comma before ")" is accepted, as in the reference.
249- if err := p.consume(TokenComma, ErrorUnexpectedSymbol); err != nil {
250- return nil, err
251- }
252- }
253- }
254- if err := p.validateFunction(ident, len(args)); err != nil {
255- return nil, err
256- }
257- return &FunctionCall{Name: ident, Args: args}, nil
258-}
259-
260-// validateFunction checks that the function exists and accepts argCount
261-// arguments (funcs.js validateFunction).
262-func (p *parser) validateFunction(ident Token, argCount int) error {
263- name := foldKey(ident.Lexeme)
264- f, ok := builtinFuncs[name]
265- if !ok {
266- f, ok = p.funcs[name]
267- if !ok {
268- return newTokenError(ErrorUnrecognizedFunction, ident)
269- }
270- }
271- if t, bad := arityError(name, f, argCount); bad {
272- return newTokenError(t, ident)
273- }
274- return nil
275 }47 }
276 48
277 // arityError reports whether argCount is wrong for f, and how. name is the49 // arityError reports whether argCount is wrong for f, and how. name is the
278-// lower-cased function name: case also needs an odd number of arguments.50+// upper-cased function name (see foldKey): case also needs an odd number of arguments.
279 func arityError(name string, f FuncInfo, argCount int) (ErrorType, bool) {51 func arityError(name string, f FuncInfo, argCount int) (ErrorType, bool) {
280 switch {52 switch {
281 case argCount < f.MinArgs:53 case argCount < f.MinArgs:
282 return ErrorTooFewParameters, true54 return ErrorTooFewParameters, true
283 case f.MaxArgs >= 0 && argCount > f.MaxArgs:55 case f.MaxArgs >= 0 && argCount > f.MaxArgs:
284 return ErrorTooManyParameters, true56 return ErrorTooManyParameters, true
285- case name == "case" && argCount%2 == 0:57+ case name == "CASE" && argCount%2 == 0:
286 return ErrorEvenParameters, true58 return ErrorEvenParameters, true
287 }59 }
288 return 0, false60 return 0, false
289 }61 }
290-
291-func (p *parser) primary() (Node, error) {
292- switch {
293- case p.match(TokenFalse):
294- return &Literal{Value: Boolean(false), Token: p.previous()}, nil
295- case p.match(TokenTrue):
296- return &Literal{Value: Boolean(true), Token: p.previous()}, nil
297- case p.match(TokenNull):
298- return &Literal{Value: Null{}, Token: p.previous()}, nil
299- case p.match(TokenNumber):
300- return &Literal{Value: p.previous().Value, Token: p.previous()}, nil
301- case p.match(TokenString):
302- return &Literal{Value: p.previous().Value, Token: p.previous()}, nil
303- case p.match(TokenLeftParen):
304- n, err := p.expression()
305- if err != nil {
306- return nil, err
307- }
308- if p.atEnd() {
309- // Back up to the last token before the EOF.
310- return nil, newTokenError(ErrorUnexpectedEndOfExpression, p.previous())
311- }
312- if err := p.consume(TokenRightParen, ErrorUnexpectedSymbol); err != nil {
313- return nil, err
314- }
315- return &Grouping{Group: n}, nil
316- case p.atEnd():
317- return nil, newTokenError(ErrorUnexpectedEndOfExpression, p.previous())
318- }
319- return nil, newTokenError(ErrorUnexpectedSymbol, p.peek())
320-}
321-
322-// match consumes the next token if it has one of the given types.
323-func (p *parser) match(types ...TokenType) bool {
324- for _, t := range types {
325- if p.check(t) {
326- p.next()
327- return true
328- }
329- }
330- return false
331-}
332-
333-func (p *parser) check(t TokenType) bool {
334- if p.atEnd() {
335- return false
336- }
337- return p.peek().Type == t
338-}
339-
340-func (p *parser) atEnd() bool { return p.peek().Type == TokenEOF }
341-
342-func (p *parser) next() Token {
343- if !p.atEnd() {
344- p.offset++
345- }
346- return p.previous()
347-}
348-
349-func (p *parser) peek() Token { return p.tokens[p.offset] }
350-
351-// previous returns the token before the current one. At offset 0 the
352-// reference reads tokens[-1], which is undefined and crashes; here it
353-// returns the zero Token. That is unreachable: previous is only used after
354-// a token was consumed.
355-func (p *parser) previous() Token {
356- if p.offset == 0 {
357- return Token{}
358- }
359- return p.tokens[p.offset-1]
360-}
361-
362-func (p *parser) consume(t TokenType, errType ErrorType) error {
363- if p.check(t) {
364- p.next()
365- return nil
366- }
367- return newTokenError(errType, p.peek())
368-}
369-
370-func (p *parser) incrDepth() error {
371- p.depth++
372- if p.depth > MaxParserDepth {
373- return newTokenError(ErrorExceededMaxDepth, p.peek())
374- }
375- return nil
376-}
A expr/runner_l0_test.go +159 -0
@@ -0,0 +1,159 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "context"
7+ "strings"
8+ "testing"
9+)
10+
11+// Ports of the expression-semantic L0 tests of actions/runner
12+// (commit 5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798, MIT, GitHub, Inc.):
13+//
14+// src/Test/L0/Sdk/ExpressionParserL0.cs
15+// src/Test/L0/Worker/Expressions/ConditionFunctionsL0.cs
16+
17+// TestRunnerExpressionParserL0 ports ExpressionParserL0: the parser must
18+// reject names that were not declared (the regression was a positional
19+// argument that turned on "allow unknown keywords" for every parse).
20+func TestRunnerExpressionParserL0(t *testing.T) {
21+ tests := []struct {
22+ name string // the C# test method
23+ src string
24+ contexts []string
25+ wantErr string // substring of the error; empty means success
26+ }{
27+ {"CreateTree_RejectsUnrecognizedNamedValue", "github.event.repository.private", []string{"inputs"}, "Unrecognized named-value"},
28+ {"CreateTree_AcceptsRecognizedNamedValue", "inputs.foo", []string{"inputs"}, ""},
29+ {"CreateTree_CaseFunctionWorks", "case(github.event_name, 'push', 'Push Event')", []string{"github"}, ""},
30+ {"CreateTree_CaseFunctionDoesNotAffectUnknownKeywords", "github.ref", []string{"inputs"}, "Unrecognized named-value"},
31+ }
32+ for _, tc := range tests {
33+ t.Run(tc.name, func(t *testing.T) {
34+ node, err := Parse(tc.src, tc.contexts, nil)
35+ if tc.wantErr != "" {
36+ if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
37+ t.Fatalf("Parse(%q) error = %v, want one containing %q", tc.src, err, tc.wantErr)
38+ }
39+ return
40+ }
41+ if err != nil || node == nil {
42+ t.Fatalf("Parse(%q) = %v, %v; want a tree", tc.src, node, err)
43+ }
44+ })
45+ }
46+}
47+
48+// Job and action results, as the runner's ActionResult enum names them.
49+const (
50+ resNone = ""
51+ resSuccess = "Success"
52+ resFailure = "Failure"
53+ resCancelled = "Cancelled"
54+)
55+
56+// statusFuncs mirrors src/Runner.Worker/Expressions/{Always,Cancelled,Failure,
57+// Success}Function.cs. A missing job status counts as Success. In a composite
58+// action's main step (isEmbedded and main stage) failure() and success() read
59+// the action status instead of the job status; cancelled() and always() never do.
60+func statusFuncs(jobStatus, actionStatus string, compositeMain bool) (map[string]Func, []FuncInfo) {
61+ orSuccess := func(s string) string {
62+ if s == resNone {
63+ return resSuccess
64+ }
65+ return s
66+ }
67+ status := func() string {
68+ if compositeMain {
69+ return orSuccess(actionStatus)
70+ }
71+ return orSuccess(jobStatus)
72+ }
73+ fn := func(f func() bool) Func {
74+ return func(context.Context, []Value) (Value, error) { return Boolean(f()), nil }
75+ }
76+ funcs := map[string]Func{
77+ "always": fn(func() bool { return true }),
78+ "cancelled": fn(func() bool { return orSuccess(jobStatus) == resCancelled }),
79+ "failure": fn(func() bool { return status() == resFailure }),
80+ "success": fn(func() bool { return status() == resSuccess }),
81+ }
82+ var infos []FuncInfo
83+ for _, n := range []string{"always", "cancelled", "failure", "success"} {
84+ infos = append(infos, FuncInfo{Name: n, MinArgs: 0, MaxArgs: 0})
85+ }
86+ return funcs, infos
87+}
88+
89+func evalCondition(t *testing.T, src, jobStatus, actionStatus string, compositeMain bool) bool {
90+ t.Helper()
91+ funcs, infos := statusFuncs(jobStatus, actionStatus, compositeMain)
92+ node, err := Parse(src, nil, infos)
93+ if err != nil {
94+ t.Fatalf("Parse(%q): %v", src, err)
95+ }
96+ v, err := Evaluate(context.Background(), node, EvalContext{Funcs: funcs})
97+ if err != nil {
98+ t.Fatalf("Evaluate(%q): %v", src, err)
99+ }
100+ return Truthy(v)
101+}
102+
103+// TestRunnerConditionFunctionsL0 ports AlwaysFunction, CancelledFunction,
104+// FailureFunction and SuccessFunction of ConditionFunctionsL0, and the two
105+// composite-action theories. The functions themselves live in the runner; what
106+// is checked here is that package expr carries zero-argument extension
107+// functions the way the runner declares them (FunctionInfo(name, 0, 0)).
108+func TestRunnerConditionFunctionsL0(t *testing.T) {
109+ jobStatuses := []string{resNone, resCancelled, resFailure, resSuccess}
110+ want := map[string]map[string]bool{
111+ "always": {resNone: true, resCancelled: true, resFailure: true, resSuccess: true},
112+ "cancelled": {resNone: false, resCancelled: true, resFailure: false, resSuccess: false},
113+ "failure": {resNone: false, resCancelled: false, resFailure: true, resSuccess: false},
114+ "success": {resNone: true, resCancelled: false, resFailure: false, resSuccess: true},
115+ }
116+ for name, byStatus := range want {
117+ for _, js := range jobStatuses {
118+ if got := evalCondition(t, name+"()", js, "", false); got != byStatus[js] {
119+ t.Errorf("%s() with job status %q = %v, want %v", name, js, got, byStatus[js])
120+ }
121+ // Names are case-insensitive, as in the runner's dictionaries.
122+ if got := evalCondition(t, strings.ToUpper(name)+"()", js, "", false); got != byStatus[js] {
123+ t.Errorf("%s() with job status %q = %v, want %v", strings.ToUpper(name), js, got, byStatus[js])
124+ }
125+ }
126+ }
127+
128+ composite := []struct {
129+ fn string
130+ job, action string
131+ want bool
132+ }{
133+ {"failure", resFailure, resFailure, true},
134+ {"failure", resFailure, resSuccess, false},
135+ {"failure", resSuccess, resFailure, true},
136+ {"failure", resSuccess, resSuccess, false},
137+ {"failure", resSuccess, resNone, false},
138+ {"success", resFailure, resFailure, false},
139+ {"success", resFailure, resSuccess, true},
140+ {"success", resSuccess, resFailure, false},
141+ {"success", resSuccess, resSuccess, true},
142+ {"success", resSuccess, resNone, true},
143+ }
144+ for _, tc := range composite {
145+ if got := evalCondition(t, tc.fn+"()", tc.job, tc.action, true); got != tc.want {
146+ t.Errorf("%s() in a composite main step, job %q action %q = %v, want %v", tc.fn, tc.job, tc.action, got, tc.want)
147+ }
148+ }
149+
150+ // Arity is declared 0..0: the parser rejects arguments.
151+ _, infos := statusFuncs("", "", false)
152+ if _, err := Parse("success(1)", nil, infos); err == nil || !strings.Contains(err.Error(), "Too many parameters supplied") {
153+ t.Errorf("success(1) = %v, want Too many parameters supplied", err)
154+ }
155+ // A condition combining them: runner conditions are ordinary expressions.
156+ if !evalCondition(t, "failure() || cancelled()", resCancelled, "", false) || evalCondition(t, "!always()", resFailure, "", false) {
157+ t.Error("combined status conditions")
158+ }
159+}
A expr/runner_oracle_test.go +205 -0
@@ -0,0 +1,205 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "compress/gzip"
7+ "context"
8+ "encoding/json"
9+ "errors"
10+ "os"
11+ "strings"
12+ "testing"
13+ "unicode/utf8"
14+)
15+
16+// testdata/runner.json.gz holds what the C# runner's expression engine
17+// (actions/runner, src/Sdk/DTExpressions2) answers on the corpus of
18+// testdata/oracle.json.gz. It is produced by gen/runner. Where the runner and
19+// the JavaScript reference disagree, the runner is what users see at run time
20+// and this package follows it (see PORTING.md). TestOracle excuses a mismatch
21+// with the JavaScript only if this package gives the runner's answer there.
22+
23+// runnerCommit is the commit of actions/runner that every file recorded from the
24+// runner is about. gen/runner/runner-commit.txt holds the same value and the
25+// generator scripts refuse a clone at another commit; TestRunnerCommit checks
26+// that the two agree and that runner.json.gz was made from it.
27+const runnerCommit = "5f0b3ddcc6ee9fe9441e00d55e85b94ea4aad798"
28+
29+type runnerFile struct {
30+ Reference struct {
31+ Source string `json:"source"`
32+ Commit string `json:"commit"`
33+ Runtime string `json:"runtime"`
34+ } `json:"reference"`
35+ Cases []runnerCase `json:"cases"`
36+}
37+
38+type runnerCase struct {
39+ E string `json:"e"`
40+ Empty bool `json:"empty"`
41+ K string `json:"k"`
42+ S string `json:"s"`
43+ J string `json:"j"`
44+ Err *runnerErr `json:"err"`
45+}
46+
47+// runnerErr is an error of the runner: the phase, the message, and the name of
48+// the .NET exception class ("JsonReaderException" for a fromJSON failure).
49+type runnerErr struct {
50+ T string `json:"t"`
51+ M string `json:"m"`
52+ X string `json:"x"`
53+}
54+
55+func loadRunner(tb testing.TB) *runnerFile {
56+ tb.Helper()
57+ f, err := os.Open("testdata/runner.json.gz")
58+ if err != nil {
59+ tb.Fatal(err)
60+ }
61+ defer func() { _ = f.Close() }()
62+ zr, err := gzip.NewReader(f)
63+ if err != nil {
64+ tb.Fatal(err)
65+ }
66+ var rf runnerFile
67+ if err := json.NewDecoder(zr).Decode(&rf); err != nil {
68+ tb.Fatal(err)
69+ }
70+ return &rf
71+}
72+
73+// lossy replaces every lone surrogate (WTF-8) by U+FFFD, which is what a JSON
74+// decoder makes of the \ud800 escapes in the recorded answers.
75+func lossy(s string) string {
76+ if !hasWTF8(s) {
77+ return s
78+ }
79+ var b strings.Builder
80+ for i := 0; i < len(s); {
81+ r, w := decodeRune(s[i:])
82+ if r >= 0xD800 && r < 0xE000 {
83+ b.WriteRune(utf8.RuneError)
84+ } else {
85+ b.WriteString(s[i : i+w])
86+ }
87+ i += w
88+ }
89+ return b.String()
90+}
91+
92+// goAnswer evaluates e like the runner harness does and returns its answer in
93+// the runner's format.
94+func goAnswer(e string, ctxNames []string, ec EvalContext) runnerCase {
95+ rc := runnerCase{E: e}
96+ node, err := Parse(e, ctxNames, oracleFuncInfos)
97+ if err != nil {
98+ rc.Err = &runnerErr{T: "parse", M: err.Error()}
99+ return rc
100+ }
101+ if node == nil {
102+ rc.Empty = true
103+ return rc
104+ }
105+ v, err := Evaluate(context.Background(), node, ec)
106+ if err != nil {
107+ rc.Err = &runnerErr{T: "eval", M: err.Error(), X: goErrorClass(err)}
108+ return rc
109+ }
110+ rc.K = v.Kind().String()
111+ rc.S = lossy(CoerceString(v))
112+ rc.J = lossy(ToJSON(v))
113+ return rc
114+}
115+
116+// goErrorClass names the kind of an evaluation error of this package in the
117+// terms of runnerErr.X: "JsonReaderException" for the failures of fromJSON.
118+func goErrorClass(err error) string {
119+ var ee *EvalError
120+ if errors.As(err, &ee) && (ee.Message == "empty input" || ee.Message == "Error parsing JSON when evaluating fromJson") {
121+ return "JsonReaderException"
122+ }
123+ return ""
124+}
125+
126+// sameAnswer reports whether a Go answer matches the runner's. Errors match
127+// when they are of the same phase and have the same message, parse errors
128+// included (token, position and expression), except a fromJSON failure, whose runner message is Json.NET's: it
129+// matches only a fromJSON failure of this package (both are JSON reader
130+// failures, never just "the expression mentions fromJSON").
131+func sameAnswer(g, r runnerCase) bool {
132+ if (g.Err == nil) != (r.Err == nil) {
133+ return false
134+ }
135+ if g.Err == nil {
136+ return g.Empty == r.Empty && g.K == r.K && g.S == r.S && g.J == r.J
137+ }
138+ if g.Err.T != r.Err.T {
139+ return false
140+ }
141+ if r.Err.X == "JsonReaderException" || g.Err.X == "JsonReaderException" {
142+ return r.Err.X == g.Err.X
143+ }
144+ return g.Err.M == r.Err.M
145+}
146+
147+func clip(s string) string {
148+ if len(s) > 160 {
149+ return s[:160] + "..."
150+ }
151+ return s
152+}
153+
154+// TestSameAnswerFromJSON: an error is excused as "a fromJSON failure" only
155+// when both sides are one; an expression that merely mentions fromJSON does not
156+// get a free pass.
157+func TestSameAnswerFromJSON(t *testing.T) {
158+ ev := func(m, x string) runnerCase {
159+ return runnerCase{E: "fromJSON('x')", Err: &runnerErr{T: "eval", M: m, X: x}}
160+ }
161+ reader := ev("Error parsing JSON when evaluating fromJson", "JsonReaderException")
162+ runnerReader := ev("Unexpected character encountered while parsing value: x. Path '', line 0, position 0.", "JsonReaderException")
163+ format := ev("The following format string is invalid: {", "FormatException")
164+ value := runnerCase{E: "fromJSON('1')", K: "Number", S: "1", J: "1"}
165+ for _, tc := range []struct {
166+ name string
167+ g, r runnerCase
168+ want bool
169+ }{
170+ {"both reader failures", reader, runnerReader, true},
171+ {"go fails, runner succeeds", reader, value, false},
172+ {"runner fails, go succeeds", value, runnerReader, false},
173+ {"go reader failure, runner format error", reader, format, false},
174+ {"runner reader failure, go format error", format, runnerReader, false},
175+ {"same non-reader error", format, format, true},
176+ } {
177+ if got := sameAnswer(tc.g, tc.r); got != tc.want {
178+ t.Errorf("%s: sameAnswer = %v, want %v", tc.name, got, tc.want)
179+ }
180+ }
181+}
182+
183+func TestRunnerCommit(t *testing.T) {
184+ txt, err := os.ReadFile("gen/runner/runner-commit.txt")
185+ if err != nil {
186+ t.Fatal(err)
187+ }
188+ if got := strings.TrimSpace(string(txt)); got != runnerCommit {
189+ t.Errorf("gen/runner/runner-commit.txt = %q, runnerCommit = %q", got, runnerCommit)
190+ }
191+ rf := loadRunner(t)
192+ if rf.Reference.Commit != runnerCommit {
193+ t.Errorf("testdata/runner.json.gz was recorded at %s, want %s", rf.Reference.Commit, runnerCommit)
194+ }
195+ if !strings.HasPrefix(rf.Reference.Runtime, "Microsoft.NETCore.App 9.0.") {
196+ t.Errorf("runtime %q, want Microsoft.NETCore.App 9.0.x", rf.Reference.Runtime)
197+ }
198+ doc, err := os.ReadFile("PORTING.md")
199+ if err != nil {
200+ t.Fatal(err)
201+ }
202+ if !strings.Contains(string(doc), runnerCommit) {
203+ t.Errorf("PORTING.md does not mention the runner commit %s", runnerCommit)
204+ }
205+}
A expr/runner_semantics_test.go +143 -0
@@ -0,0 +1,143 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "context"
7+ "strings"
8+ "testing"
9+ "unicode/utf16"
10+)
11+
12+// TestRunnerSemantics pins behaviours of the PORTING.md list that the C# runner
13+// and the JavaScript reference share or differ on. Every expectation was
14+// obtained by running the expression through the runner's own code
15+// (gen/runner); the bulk is also covered by TestRunnerOracle.
16+func TestRunnerSemantics(t *testing.T) {
17+ ctx := map[string]Value{"github": mustJSON(t, `{
18+ "commits": [{"id": "1"}, {"id": "2", "x": 1}, {"y": 3}],
19+ "obj": {"a": {"a": 1}, "K": "v", "b": {"c": 2}},
20+ "nested": [[1, 2], [3], []]}`)}
21+ tests := []struct{ src, want string }{
22+ // Index access: strings and numbers index arrays by their numeric value,
23+ // booleans and null become 1 and 0, negative, NaN and infinite give null.
24+ {"github.commits['1'].id", "String:2"},
25+ {"github.commits[' 1 '].id", "String:2"},
26+ {"github.commits['1.9'].id", "String:2"},
27+ {"github.commits[1.9].id", "String:2"},
28+ {"github.commits[true].id", "String:2"},
29+ {"github.commits[null].id", "String:1"},
30+ {"github.commits[-1]", "Null:"},
31+ {"github.commits[NaN]", "Null:"},
32+ {"github.commits[Infinity]", "Null:"},
33+ {"github.commits['x']", "Null:"},
34+ {"github.commits[5].id", "Null:"},
35+ // On a dictionary only the string form counts, case-insensitively.
36+ {"github.obj[true]", "Null:"},
37+ {"github.obj[null]", "Null:"},
38+ {"github.obj[1]", "Null:"},
39+ {"github.obj['K']", "String:v"},
40+ {"GITHUB.OBJ.K", "String:v"},
41+ {"github.OBJ.k", "String:v"},
42+ {"github.nope.deeper", "Null:"},
43+ // Filters.
44+ {"toJSON(github.obj.*.a)", "String:[\n 1\n]"},
45+ {"toJSON(github.commits.*.id)", "String:[\n \"1\",\n \"2\"\n]"},
46+ {"toJSON(github.commits.*.*)", "String:[\n \"1\",\n \"2\",\n 1,\n 3\n]"},
47+ {"toJSON(github.nested.*.*)", "String:[\n 1,\n 2,\n 3\n]"},
48+ {"toJSON(github.nested.*[0])", "String:[\n 1,\n 3\n]"},
49+ {"github.commits.* == github.commits.*", "Boolean:false"},
50+ {"github.obj.* == github.obj.*", "Boolean:false"},
51+ {"github.commits == github.commits", "Boolean:true"},
52+ {"github.obj == github.obj", "Boolean:true"},
53+ // Comparison.
54+ {"null < 1", "Boolean:true"},
55+ {"null > -1", "Boolean:true"},
56+ {"null == 0", "Boolean:true"},
57+ {"null < true", "Boolean:true"},
58+ {"null < ''", "Boolean:false"},
59+ {"null == ''", "Boolean:true"},
60+ {"NaN == NaN", "Boolean:false"},
61+ {"NaN != NaN", "Boolean:true"},
62+ {"NaN < 1", "Boolean:false"},
63+ {"true > false", "Boolean:true"},
64+ {"true > 0", "Boolean:true"},
65+ // && and || return an operand.
66+ {"'' || 'x'", "String:x"},
67+ {"'a' && 'b'", "String:b"},
68+ {"0 && 'x'", "Number:0"},
69+ {"null || false", "Boolean:false"},
70+ // White space may surround "." and "[" and sit between a function name
71+ // and its "(" (the lexer looks ahead past it).
72+ {"github . obj . K", "String:v"},
73+ {"github.obj.\nK", "String:v"},
74+ {"github.obj .K", "String:v"},
75+ {"github [ 'obj' ].K", "String:v"},
76+ {"toJSON (1)", "String:1"},
77+ // White space is Char.IsWhiteSpace: U+00A0, U+000B and U+0085 are skipped.
78+ {"1\u00a0", "Number:1"},
79+ {"\u00a01", "Number:1"},
80+ {"\v1", "Number:1"},
81+ {"'a'\u0085", "String:a"},
82+ }
83+ for _, tc := range tests {
84+ node, err := Parse(tc.src, []string{"github"}, nil)
85+ if err != nil {
86+ t.Errorf("Parse(%q): %v", tc.src, err)
87+ continue
88+ }
89+ v, err := Evaluate(context.Background(), node, EvalContext{Contexts: ctx})
90+ if err != nil {
91+ t.Errorf("Evaluate(%q): %v", tc.src, err)
92+ continue
93+ }
94+ if got := v.Kind().String() + ":" + CoerceString(v); got != tc.want {
95+ t.Errorf("%s = %q, want %q", tc.src, got, tc.want)
96+ }
97+ }
98+
99+ // Parse errors.
100+ for _, tc := range []struct{ src, want string }{
101+ {"contains('a','b',)", "Unexpected symbol: ')'. Located at position 18 within expression: contains('a','b',)"},
102+ {"contains(,'a')", "Unexpected symbol: ','. Located at position 10 within expression: contains(,'a')"},
103+ {"[1] == [1]", "Unexpected symbol: '['. Located at position 1 within expression: [1] == [1]"},
104+ {"1 \u00a0 2", "Unexpected symbol: '2'. Located at position 5 within expression: 1 \u00a0 2"},
105+ } {
106+ if _, err := Parse(tc.src, []string{"github"}, nil); err == nil || err.Error() != tc.want {
107+ t.Errorf("Parse(%q) = %v, want %q", tc.src, err, tc.want)
108+ }
109+ }
110+
111+ // An empty expression is not an error and has no tree.
112+ for _, src := range []string{"", " ", "\n", "\u00a0"} {
113+ if n, err := Parse(src, nil, nil); n != nil || err != nil {
114+ t.Errorf("Parse(%q) = %v, %v; want nil, nil", src, n, err)
115+ }
116+ }
117+}
118+
119+// BenchmarkParseLong parses an expression of the maximum length made of many
120+// short tokens spread over many lines: building the position of every token
121+// must not rescan the expression.
122+func BenchmarkParseLong(b *testing.B) {
123+ src := strings.Repeat("1 ||\n", MaxExpressionLength/5-1) + "1"
124+ if len(src) > MaxExpressionLength {
125+ b.Fatal(len(src))
126+ }
127+ b.SetBytes(int64(len(src)))
128+ for i := 0; i < b.N; i++ {
129+ if _, err := Parse(src, nil, nil); err != nil {
130+ b.Fatal(err)
131+ }
132+ }
133+}
134+
135+func TestPositionLines(t *testing.T) {
136+ src := "a\n\nbc d\n e"
137+ p := &csParser{units: utf16.Encode([]rune(src))}
138+ for _, tc := range []struct{ idx, line, col int }{{0, 0, 0}, {1, 0, 1}, {2, 1, 0}, {3, 2, 0}, {5, 2, 2}, {7, 2, 4}, {8, 3, 0}, {10, 3, 2}, {len(src), 3, 3}} {
139+ if got := p.position(tc.idx); got.Line != tc.line || got.Column != tc.col {
140+ t.Errorf("position(%d) = %v, want %d:%d", tc.idx, got, tc.line, tc.col)
141+ }
142+ }
143+}
A expr/runner_strict_test.go +61 -0
@@ -0,0 +1,61 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "encoding/json"
7+ "sort"
8+ "testing"
9+)
10+
11+// knownRunnerDifference classifies the answers of the corpus on which this
12+// package knowingly differs from the runner. It returns the reason, or "".
13+// There is none left: negative zero is written "-0" like the legacy engine.
14+func knownRunnerDifference(g, r runnerCase) string { return "" }
15+
16+// TestRunnerOracle holds the package to the answers of the runner's own code on
17+// the 34 765 expressions of the oracle corpus: same result kind, string form
18+// and toJSON text, same error phase and kind (the error texts of the runner's
19+// Json.NET are not reproduced).
20+func TestRunnerOracle(t *testing.T) {
21+ of, ctxValues, ctxNames := loadOracle(t)
22+ rf := loadRunner(t)
23+ if len(rf.Cases) != len(of.Cases) {
24+ t.Fatalf("runner answers %d cases, oracle has %d", len(rf.Cases), len(of.Cases))
25+ }
26+ t.Logf("runner: %s @ %s, %s", rf.Reference.Source, rf.Reference.Commit, rf.Reference.Runtime)
27+ ec := EvalContext{Contexts: ctxValues, Funcs: oracleFuncs, MaxResultBytes: -1}
28+ known := map[string]int{}
29+ failures := 0
30+ for i, rc := range rf.Cases {
31+ if rc.E != of.Cases[i].E {
32+ t.Fatalf("case %d: runner expression %q, oracle %q", i, rc.E, of.Cases[i].E)
33+ }
34+ g := goAnswer(rc.E, ctxNames, ec)
35+ if sameAnswer(g, rc) {
36+ continue
37+ }
38+ if reason := knownRunnerDifference(g, rc); reason != "" {
39+ known[reason]++
40+ continue
41+ }
42+ failures++
43+ if failures <= 30 {
44+ gj, _ := json.Marshal(g)
45+ rj, _ := json.Marshal(rc)
46+ t.Errorf("%q:\n go %s\n runner %s", clip(rc.E), gj, rj)
47+ }
48+ }
49+ reasons := make([]string, 0, len(known))
50+ for k := range known {
51+ reasons = append(reasons, k)
52+ }
53+ sort.Strings(reasons)
54+ for _, k := range reasons {
55+ t.Logf("known difference from the runner (%d cases): %s", known[k], k)
56+ }
57+ t.Logf("%d cases, %d failures", len(rf.Cases), failures)
58+ if failures > 0 {
59+ t.Fatalf("%d mismatches with the runner", failures)
60+ }
61+}
A expr/testdata/fromjson_cases.json +1 -0
@@ -0,0 +1,1 @@
1+[{"in":"null","ok":true,"out":"null"},{"in":"true","ok":true,"out":"true"},{"in":"false","ok":true,"out":"false"},{"in":"NaN","ok":true,"out":"NaN"},{"in":"Infinity","ok":true,"out":"Infinity"},{"in":"-Infinity","ok":true,"out":"-Infinity"},{"in":"undefined","ok":true,"out":"\"\""},{"in":"0","ok":true,"out":"0"},{"in":"-0","ok":true,"out":"0"},{"in":"010","ok":true,"out":"8"},{"in":"08","ok":false},{"in":"0x1F","ok":true,"out":"31"},{"in":"1.5e3","ok":true,"out":"1500"},{"in":".5","ok":true,"out":"0.5"},{"in":"5.","ok":true,"out":"5"},{"in":"12345678901234567890","ok":true,"out":"1.23456789012346E+19"},{"in":"\"a\"","ok":true,"out":"\"a\""},{"in":"'a'","ok":true,"out":"\"a\""},{"in":"\"\\u00e9\\ud83d\\ude00\"","ok":true,"out":"\"é😀\""},{"in":"\"\\ud800\"","ok":true,"out":"\"�\""},{"in":"[]","ok":true,"out":"[]"},{"in":"{}","ok":true,"out":"{}"},{"in":"[1,2,3]","ok":true,"out":"[\n 1,\n 2,\n 3\n]"},{"in":"[1,]","ok":true,"out":"[\n 1\n]"},{"in":"[,1]","ok":true,"out":"[\n \"\",\n 1\n]"},{"in":"[1,,2]","ok":true,"out":"[\n 1,\n \"\",\n 2\n]"},{"in":"{\"a\":1}","ok":true,"out":"{\n \"a\": 1\n}"},{"in":"{\"a\":1,\"A\":2}","ok":true,"out":"{\n \"a\": 2\n}"},{"in":"{a:1}","ok":true,"out":"{\n \"a\": 1\n}"},{"in":"{'a':'b'}","ok":true,"out":"{\n \"a\": \"b\"\n}"},{"in":"{\"a\":[1,{\"b\":null}]}","ok":true,"out":"{\n \"a\": [\n 1,\n {\n \"b\": null\n }\n ]\n}"},{"in":"[[[1]]]","ok":true,"out":"[\n [\n [\n 1\n ]\n ]\n]"},{"in":"[null,true,false]","ok":true,"out":"[\n null,\n true,\n false\n]"},{"in":"1 2","ok":true,"out":"1"},{"in":"[] x","ok":true,"out":"[]"},{"in":",","ok":true,"out":"\"\""},{"in":",1","ok":true,"out":"\"\""},{"in":"/*c*/1","ok":true,"out":"\"c\""},{"in":"1/*c*/","ok":true,"out":"1"},{"in":"[1/*c*/,2]","ok":true,"out":"[\n 1,\n 2\n]"},{"in":"null/x","ok":false},{"in":"new Date(1)","ok":true,"out":"\"new Date(\\n 1\\n)\""},{"in":"[010,08,-010]","ok":false},{"in":"{\"a\":010}","ok":true,"out":"{\n \"a\": 8\n}"},{"in":"1e5","ok":true,"out":"100000"},{"in":"1E+5","ok":true,"out":"100000"},{"in":"-1.5e-3","ok":true,"out":"-0.0015"},{"in":"0.1e1","ok":true,"out":"1"},{"in":"0e0","ok":true,"out":"0"},{"in":"00","ok":true,"out":"0"},{"in":"-00","ok":true,"out":"0"},{"in":"0.0","ok":true,"out":"0"},{"in":"00.5","ok":false},{"in":"\"\\u00e9[\\ud83d\\ud00","ok":false},{"in":"t8/*cN*/","ok":false},{"in":"0x\\1F","ok":false},{"in":"[[[1]]]/","ok":true,"out":"[\n [\n [\n 1\n ]\n ]\n]"},{"in":"b00","ok":false},{"in":"t'ta'","ok":false},{"in":"{\"a\"/010}","ok":false},{"in":"3-.5e-3","ok":false},{"in":"[1e5X","ok":false},{"in":"0.te1","ok":false},{"in":"1.N5e7","ok":false},{"in":"{ab1}","ok":false},{"in":"N0e0","ok":false},{"in":"Nx.F","ok":false},{"in":"[rueX","ok":false},{"in":"[null,truef\\lse]","ok":false},{"in":"\"\\u00e\\9\\ud83d\\ude0\"","ok":false},{"in":".un3efi3ned","ok":false},{"in":"treb","ok":false},{"in":".-","ok":false},{"in":"bNaN","ok":false},{"in":"//*c*/1","ok":true,"out":"\"*c*/1\""},{"in":"1E3+t","ok":false},{"in":".53","ok":true,"out":"0.53"},{"in":"7.","ok":true,"out":"7"},{"in":"[1,","ok":false},{"in":"/[1/c*/,2]","ok":false},{"in":"\".a.:1}","ok":false},{"in":"\"\\u00e9\\ud83\\ude0\"","ok":false},{"in":"X.0","ok":false},{"in":"{X}","ok":false},{"in":"\\/10","ok":false},{"in":"nullNx","ok":false},{"in":"X01e\\","ok":false},{"in":"E\"+","ok":false},{"in":"{\"a\":[1,{\"b\"7:null}]}","ok":false},{"in":"1/3*c*/","ok":true,"out":"1"},{"in":"\\null7","ok":false},{"in":"\"XE+","ok":false},{"in":"\\.0","ok":false},{"in":"3","ok":true,"out":"3"},{"in":"3'a':'b'.}","ok":false},{"in":"3{","ok":false},{"in":"{\"a010}","ok":false},{"in":"1..b5e","ok":false},{"in":"undefibed","ok":false},{"in":"\\N00","ok":false},{"in":"a:1}","ok":false},{"in":"{\"a\":00N}","ok":false},{"in":"\"\\u00e.\\ud83d\\ude00\"","ok":false},{"in":"\"[1,2,3]","ok":false},{"in":"{\"\":1,\"A:2}","ok":false},{"in":"3] x","ok":true,"out":"3"},{"in":"[1\\]","ok":false},{"in":"\"","ok":false},{"in":"7[1,]","ok":false},{"in":"70.1e1","ok":true,"out":"701"},{"in":"-Infinit\"y","ok":false},{"in":"[nult,\"true,false]","ok":false},{"in":"trute","ok":false},{"in":".0x1","ok":false},{"in":",X1","ok":true,"out":"\"\""},{"in":"b{\"a\":1}","ok":false},{"in":"/*c*\"","ok":false},{"in":"300","ok":true,"out":"300"},{"in":".efined","ok":false},{"in":"Xa'","ok":false},{"in":"7//*c*/","ok":true,"out":"7"},{"in":"undeined","ok":false},{"in":"{a31","ok":false},{"in":"ue[","ok":false},{"in":"}","ok":false},{"in":"7\"\":010}","ok":false},{"in":"\\b[1/*c*/,2]","ok":false},{"in":"[5b.","ok":false},{"in":"12\\45678/01234567890","ok":false},{"in":"N':'b'}","ok":false},{"in":"","ok":false},{"in":"0[","ok":false},{"in":"1E5","ok":true,"out":"100000"},{"in":"7a:1b","ok":false},{"in":"7X08","ok":false},{"in":"[1/*c*/\\3,2]","ok":false},{"in":"[1/*c*7,2]","ok":false},{"in":"1/c[","ok":true,"out":"1"},{"in":"'tNueb","ok":false},{"in":"t.u","ok":false},{"in":"[] xb","ok":true,"out":"[]"},{"in":"\\0be1","ok":false},{"in":"30[","ok":false},{"in":"b1\\5","ok":false},{"in":".0.1e1","ok":false},{"in":"t[] x","ok":false},{"in":"\\\\u00e9\\ud83d\\de00\"","ok":false},{"in":"\\u0Xe9\\3ud83d\\ude00\"","ok":false},{"in":"1.3e3","ok":true,"out":"1300"},{"in":"3.1","ok":true,"out":"3.1"},{"in":"00.","ok":false},{"in":"0x3/F","ok":true,"out":"3"},{"in":"[1]","ok":true,"out":"[\n 1\n]"},{"in":"t. x","ok":false},{"in":"123456789012345[7890","ok":false},{"in":"at","ok":false},{"in":"{'a':'b'X}","ok":false},{"in":"t.","ok":false},{"in":"1XX","ok":false},{"in":"1,2,3]","ok":true,"out":"1"},{"in":"-","ok":false},{"in":"[null,true,flse]","ok":false},{"in":"nfinit","ok":false},{"in":"0x1/F","ok":true,"out":"1"},{"in":"\"\"\\d/00\"","ok":true,"out":"\"\""},{"in":"\"[null,true,false]","ok":false},{"in":"00\\5","ok":false},{"in":"t.e5","ok":false},{"in":"10","ok":true,"out":"10"},{"in":"X'ab'","ok":false},{"in":"In\\finiy/","ok":false},{"in":"Na'","ok":false},{"in":".*c*[1","ok":false},{"in":"/*c*/[1","ok":true,"out":"\"c\""},{"in":"I.nfinitb.y","ok":false},{"in":"\\0","ok":false},{"in":"\"\\u00e9\\ud83d\\Nde00\"","ok":false},{"in":"t[ue","ok":false},{"in":"/**/","ok":true,"out":"\"\""},{"in":"[17,[]","ok":false},{"in":".ull7","ok":false},{"in":".5e[X3","ok":false},{"in":"07x1F","ok":false},{"in":"\"30","ok":false},{"in":"}N","ok":false},{"in":"b1/*c*/[7]","ok":false},{"in":"3be\"","ok":false},{"in":"3-0","ok":false},{"in":"\\10","ok":false},{"in":"...5","ok":false},{"in":".0","ok":true,"out":"0"},{"in":" 2","ok":true,"out":"2"},{"in":"010\"08,-010]","ok":false},{"in":"{\"a\"1,\"A\":2N","ok":false},{"in":"\\0e0","ok":false},{"in":"X1.5e-3","ok":false},{"in":"a'","ok":false},{"in":"[010,08,-00]","ok":false},{"in":"0x\\1F\"","ok":false},{"in":"[1/*c*//,2]","ok":false},{"in":"05","ok":true,"out":"5"},{"in":"[/[[1]]]","ok":false},{"in":"ne /Date(1)","ok":false},{"in":"treX","ok":false},{"in":"NX\"0","ok":false},{"in":".a:1}","ok":false},{"in":"nNll/x","ok":false},{"in":"\\","ok":false},{"in":"b/a\"'","ok":false},{"in":"/,1]","ok":false},{"in":" N2","ok":false},{"in":".x1","ok":false},{"in":"[1,,7].","ok":true,"out":"[\n 1,\n \"\",\n 7\n]"},{"in":"[010,08,-010","ok":false},{"in":"71b5","ok":false},{"in":"[e0","ok":false},{"in":"[1\",2]","ok":false},{"in":"[3/*c*/,2]","ok":true,"out":"[\n 3,\n 2\n]"},{"in":"[5X","ok":false},{"in":"nultl","ok":false},{"in":"/Na","ok":false},{"in":"b.b","ok":false},{"in":"[null,true.false","ok":false},{"in":"\"ul7\"","ok":true,"out":"\"ul7\""},{"in":"[null,Xrue,f/alsNe]","ok":false},{"in":"08.","ok":false},{"in":"3.ud800\"","ok":false},{"in":"\"\\u00e9\\ud83\\\\ude0b\"","ok":false},{"in":"X[1,2,3]","ok":false},{"in":"7","ok":true,"out":"7"},{"in":"{\"a\":[X1,{\"b\":null}]}","ok":false},{"in":"nu.l/x","ok":false},{"in":"bnullx","ok":false},{"in":"X","ok":false},{"in":"\"\"a\":[1,N{\"b\":null}]}","ok":true,"out":"\"\""},{"in":"[] x[","ok":true,"out":"[]"},{"in":"[] ","ok":true,"out":"[]"},{"in":"[1*c*/,2","ok":false},{"in":"/","ok":false},{"in":"\"In/finty","ok":false},{"in":"b\"","ok":false},{"in":"null/x3","ok":false},{"in":"Intiniy[","ok":false},{"in":"t0e","ok":false},{"in":"t","ok":false},{"in":"\".\\u800\"","ok":false},{"in":"e5\"","ok":false},{"in":"[[1]]]","ok":true,"out":"[\n [\n 1\n ]\n]"},{"in":"N.5e/3","ok":false},{"in":"{\"[a\"[:00}","ok":false},{"in":"7[ul\"/x","ok":false},{"in":"X08","ok":false},{"in":"{\"a\":[1,{\"b\":null}]}3","ok":true,"out":"{\n \"a\": [\n 1,\n {\n \"b\": null\n }\n ]\n}"},{"in":"N","ok":false},{"in":"[","ok":false},{"in":"7\"a\":010}","ok":false},{"in":".5t","ok":false},{"in":"\"[1,]","ok":false},{"in":"00\\","ok":false},{"in":"[N,]","ok":false},{"in":"[nfiity","ok":false},{"in":"-Inf[init\"","ok":false},{"in":"\"a\"\\X:010}","ok":true,"out":"\"a\""},{"in":"INf[nit\\","ok":false},{"in":"1.7e3","ok":true,"out":"1700"},{"in":"\\\"'a","ok":false},{"in":"738","ok":true,"out":"738"},{"in":"new XDate(1)","ok":true,"out":"\"new XDate(\\n 1\\n)\""},{"in":"3undef/ined","ok":false},{"in":".[[[1]]]","ok":false},{"in":"trt\\ue","ok":false},{"in":"/ll/x","ok":false},{"in":"{\"a\":[1,{\"b\":nuNll}]/","ok":false},{"in":"12345678901234567890\\","ok":false},{"in":"{a1}","ok":false},{"in":"{\"\":1,\"A\":2}","ok":true,"out":"{\n \"\": 1,\n \"A\": 2\n}"},{"in":"1","ok":true,"out":"1"},{"in":"X/*c*/1X","ok":false},{"in":"fa[s\"3","ok":false},{"in":"X0","ok":false},{"in":"t1 2","ok":false},{"in":"/null,t[rue,fale]","ok":false},{"in":"1.5e33","ok":true,"out":"1.5E+33"},{"in":"[8.","ok":false},{"in":".5e3","ok":true,"out":"500"},{"in":"new ate(\\1X)","ok":false},{"in":"falst","ok":false},{"in":"\\/\"a\":17}","ok":false},{"in":"[00,08,.-010","ok":false},{"in":"[] 3x","ok":true,"out":"[]"},{"in":"true[N","ok":false},{"in":"2/","ok":true,"out":"2"},{"in":"[0.","ok":false},{"in":"u\"ll/x","ok":false},{"in":"b/e0","ok":false},{"in":"\\NX0","ok":false},{"in":"\"\\ud8\"00\"","ok":false},{"in":"N.0","ok":false},{"in":"31E+","ok":false},{"in":"[[X1]]]","ok":false},{"in":"\"t\":1}","ok":true,"out":"\"t\""},{"in":"7b08.","ok":false},{"in":"[5.N[","ok":false},{"in":"[1,3]","ok":true,"out":"[\n 1,\n 3\n]"},{"in":"{\"a\":1,\"A:23}","ok":false},{"in":".00","ok":true,"out":"0"},{"in":"123745678901X34567890","ok":false},{"in":"\"undefined","ok":false},{"in":"\\[[1]]]","ok":false},{"in":"{'a'\\:'b'}","ok":false},{"in":"Xa\"","ok":false},{"in":"\\{\"a\":1}","ok":false},{"in":"]X","ok":false},{"in":"undebine3d","ok":false},{"in":"3e0","ok":true,"out":"3"},{"in":"new 3Datet1)","ok":false},{"in":"01F","ok":false},{"in":".]","ok":false},{"in":"[00,08,-010]","ok":false},{"in":"t[]","ok":false},{"in":"[\"","ok":false},{"in":"t.e","ok":false},{"in":"b/","ok":false},{"in":"1F","ok":false},{"in":".08X","ok":false},{"in":"\"3\\]","ok":false},{"in":"[0","ok":false},{"in":"1E+7/","ok":true,"out":"10000000"},{"in":"\\a'","ok":false},{"in":"7,b\"","ok":true,"out":"7"},{"in":"]","ok":false},{"in":"1\\2345678901234567890","ok":false},{"in":"0x1.","ok":false},{"in":"N'a'","ok":false},{"in":"\"\\u0e9\\ud83d\\ude00\"","ok":false},{"in":"t1+5","ok":false},{"in":"XX1]3","ok":false},{"in":"tnNw Date(1)","ok":false},{"in":"'\\","ok":false},{"in":"71 2","ok":true,"out":"71"},{"in":"3ublb","ok":false},{"in":"trtuX","ok":false},{"in":".,7","ok":false},{"in":"{\"a\"N1,\"[\"32}","ok":false},{"in":"N,1","ok":false},{"in":"X1t2345678901.34567890","ok":false},{"in":"a1,2,3]","ok":false},{"in":"b[[[1]]]","ok":false},{"in":"\"\\u00te9\\ud83d\\ude00\"","ok":false},{"in":"X1/*c*/","ok":false},{"in":"N1. 23","ok":false},{"in":"1/*.*/","ok":true,"out":"1"},{"in":"[1[,2","ok":false},{"in":"t10","ok":false},{"in":"[1N,23","ok":false},{"in":"'a':'b'","ok":true,"out":"\"a\""},{"in":"Infinity3","ok":false},{"in":"X[[1]]]","ok":false},{"in":".0e","ok":false},{"in":"3.","ok":true,"out":"3"},{"in":".null,tr\"ue,false]","ok":false},{"in":"e5","ok":false},{"in":"e0","ok":false},{"in":"\"\\ud8007\"","ok":true,"out":"\"�7\""},{"in":"ull","ok":false},{"in":".5e-3","ok":true,"out":"0.0005"},{"in":"X1/7Nc*/","ok":false},{"in":"\"\\ud800\"7","ok":true,"out":"\"�\""},{"in":".1e1","ok":true,"out":"1"},{"in":".70","ok":true,"out":"0.7"},{"in":" 2.","ok":true,"out":"2"},{"in":"b0.1e\"","ok":false},{"in":"1/*c/","ok":true,"out":"1"},{"in":"31,2,3]","ok":true,"out":"31"},{"in":"{at:1","ok":false},{"in":"/c*/1","ok":false},{"in":"1E+[","ok":false},{"in":".t[ue","ok":false},{"in":"a","ok":false},{"in":"-X3","ok":false},{"in":"5'","ok":false},{"in":"\"\\ud8t00\"","ok":false},{"in":"[.1]","ok":true,"out":"[\n 0.1\n]"},{"in":"/010","ok":false},{"in":"t.0","ok":false},{"in":"\\\",]","ok":false},{"in":"{a:1N}","ok":false},{"in":"007","ok":true,"out":"7"},{"in":"[.","ok":false},{"in":"b.1","ok":false},{"in":"\\5.b","ok":false},{"in":"\"\\ud80\"","ok":false},{"in":"\\1]","ok":false},{"in":"3,]","ok":true,"out":"3"},{"in":"1.5e73","ok":true,"out":"1.5E+73"},{"in":"3e5","ok":true,"out":"300000"},{"in":"{'a':''}","ok":true,"out":"{\n \"a\": \"\"\n}"},{"in":"33","ok":true,"out":"33"},{"in":"0.5","ok":true,"out":"0.5"},{"in":"X,N1]","ok":false},{"in":"unde/fited","ok":false},{"in":".3e3","ok":true,"out":"300"},{"in":"{\"/\"[1,{\"b7\":null}]}","ok":false},{"in":"new Date71)","ok":false},{"in":"nulltrue,fale]","ok":false},{"in":"1.5e","ok":false},{"in":"12345678\\01[34567890","ok":false},{"in":"{\"a\":[1,{\"b\":null}]t","ok":false},{"in":"/ull/x","ok":false},{"in":"tb[[1]]]","ok":false},{"in":"/ue","ok":false},{"in":"{'a''b'","ok":false},{"in":"0x1\"/","ok":false},{"in":"[I\"nfinitb","ok":false},{"in":"{a31.","ok":false},{"in":"{\"a\":1,\"A\":2N","ok":false},{"in":"3a'","ok":false},{"in":"70.5","ok":true,"out":"70.5"},{"in":"tnew Datet1/","ok":false},{"in":"\"\\u00e\\udt83d\\ude00\"","ok":false},{"in":"/nfinity","ok":false},{"in":"3.0.","ok":false},{"in":"\\u00e9\\ud83d\\ude00\"","ok":false},{"in":"-Infinbity","ok":false},{"in":"{\"a\":[1,{\"b[:null}]}","ok":false},{"in":"[]b","ok":true,"out":"[]"},{"in":"ntew ate\"1)","ok":false},{"in":"[1,2","ok":false},{"in":"0b0","ok":false},{"in":"t[1,b2,3]","ok":false},{"in":"tr.e","ok":false},{"in":".t0","ok":false},{"in":"/\\10","ok":false},{"in":"\"0","ok":false},{"in":"tr.u3","ok":false},{"in":"71[/*c*/","ok":false},{"in":"\"\"\":010}","ok":true,"out":"\"\""},{"in":"] 7x","ok":false},{"in":"3NN","ok":false},{"in":"t\\ue","ok":false},{"in":"[b\"a\":1}","ok":false},{"in":"[1,3,2]","ok":true,"out":"[\n 1,\n 3,\n 2\n]"},{"in":"/[1,2,\\3]","ok":false},{"in":"[NNa\"","ok":false},{"in":"{\"a\":1,\"A\"32}","ok":false},{"in":"[b x","ok":false},{"in":".[1,2,3]","ok":false},{"in":"3 2","ok":true,"out":"3"},{"in":"[[./]]","ok":false},{"in":"\"\\u00e9\\\\d83d\\ude00\"","ok":true,"out":"\"é\\\\d83d�\""},{"in":"{\"a\":1,\".\".2","ok":false},{"in":"/*.c*/1","ok":true,"out":"\".c\""},{"in":"-.0","ok":true,"out":"-0"},{"in":"Infinity.","ok":false},{"in":"[t,","ok":false},{"in":"{'ba':'b'}","ok":true,"out":"{\n \"ba\": \"b\"\n}"},{"in":"[/","ok":false},{"in":"X-Infinity","ok":false},{"in":"-I]nfinity","ok":false},{"in":"[,]","ok":true,"out":"[\n \"\"\n]"},{"in":"aN","ok":false},{"in":"0b","ok":false},{"in":"00[5","ok":false},{"in":"1234567890234567890","ok":true,"out":"1.23456789023457E+18"},{"in":"{'a'.'b'}","ok":false},{"in":"bull7/","ok":false},{"in":"-Ifiity","ok":false},{"in":"123456789012345678390","ok":true,"out":"1.23456789012346E+20"},{"in":".false","ok":false},{"in":"1N234567/89012345/67890","ok":false},{"in":"0\"","ok":false},{"in":"un7e3ined","ok":false},{"in":"{\"3\":00}","ok":true,"out":"{\n \"3\": 0\n}"},{"in":"1/*c*/t","ok":true,"out":"1"},{"in":"[010,08,-0\\0t","ok":false},{"in":"/1,2,3]","ok":false},{"in":"Nr.u3","ok":false},{"in":",1[","ok":true,"out":"\"\""},{"in":"Infi[7\"y","ok":false},{"in":"[\\","ok":false},{"in":"{\"a\":1\",\"A\":2}","ok":false},{"in":"talse","ok":false},{"in":"[1/*c*/,2","ok":false},{"in":"\"aX\"1}","ok":true,"out":"\"aX\""},{"in":"[[1]]","ok":true,"out":"[\n [\n 1\n ]\n]"},{"in":"\"null","ok":false},{"in":"[[1]]X]","ok":true,"out":"[\n [\n 1\n ]\n]"},{"in":"t[\\,]","ok":false},{"in":"\\.7","ok":false},{"in":"[]x","ok":true,"out":"[]"},{"in":"t.5e-33","ok":false},{"in":"{\"a3\":[1,{\"b\":nullb}]}t","ok":false},{"in":"-Ininit","ok":false},{"in":"{\"\":13,\"A\"\"2}","ok":false},{"in":"\"-0X","ok":false},{"in":"/1/*cX/","ok":false},{"in":"-Inf3nity","ok":false},{"in":"fase","ok":false},{"in":"/ 2","ok":false},{"in":"{\"a\":[1,{\"b\":null}]/}","ok":false},{"in":"[ulNl","ok":false},{"in":"{a:1/\"}","ok":false},{"in":"\\NaN","ok":false},{"in":"\\new Date(1)","ok":false},{"in":"\"e5","ok":false},{"in":",1/]","ok":true,"out":"\"\""},{"in":"\\1EX","ok":false},{"in":".","ok":false},{"in":"b\"a\"","ok":false},{"in":"7.Xet1","ok":false},{"in":"[1/,X\"","ok":false},{"in":"2","ok":true,"out":"2"},{"in":"\"\\bu09\\ud83d\\ude00\"","ok":true,"out":"\"\\bu09😀\""},{"in":"Ininity","ok":false},{"in":"1234567890[23456789b0","ok":false},{"in":"XXa/lse","ok":false},{"in":"Infi\\i7y","ok":false},{"in":".0X","ok":false},{"in":"5./","ok":true,"out":"5"},{"in":"\\[1/*c*/,2]","ok":false},{"in":".{\".\":1}","ok":false},{"in":"t-1.5e-3","ok":false},{"in":"tNaN","ok":false},{"in":".5/","ok":true,"out":"0.5"},{"in":"\"7u00e9\\ud83d\\ude00\"","ok":true,"out":"\"7u00e9😀\""},{"in":"[1,2b,3]","ok":false},{"in":"ew Date(b1)","ok":false},{"in":"5","ok":true,"out":"5"},{"in":".7","ok":true,"out":"0.7"},{"in":"-Inf3ini7y","ok":false},{"in":"[aN","ok":false},{"in":"X1.","ok":false},{"in":"{ta\":00}","ok":false},{"in":"5/","ok":true,"out":"5"},{"in":"N.[N","ok":false},{"in":"1,,2N]","ok":true,"out":"1"},{"in":"[null,tr[e,false]","ok":false},{"in":"3.aN","ok":false},{"in":"t8","ok":false},{"in":"XaN","ok":false},{"in":"X00","ok":false},{"in":"{'a':'b'7X","ok":false},{"in":"0.e1","ok":true,"out":"0"},{"in":"/*ct*/1","ok":true,"out":"\"ct\""},{"in":"un.defined","ok":false},{"in":"[,","ok":false},{"in":"7\\{\"a\":1}","ok":false},{"in":"[] xNt","ok":true,"out":"[]"},{"in":"0X","ok":false},{"in":"newDate[(1)","ok":false},{"in":"Infini[y","ok":false},{"in":".0/0..","ok":true,"out":"0"},{"in":"\"\\u30e9\\ud83d\\ude00\"","ok":true,"out":"\"ラ😀\""},{"in":"[null,truefalse]","ok":false},{"in":"[010.,08,-010]","ok":false},{"in":"/010,08,-00]","ok":false},{"in":"t0e0","ok":false},{"in":"1/","ok":true,"out":"1"},{"in":"\"\\u00e9\\ud83d\\ude00\"t","ok":true,"out":"\"é😀\""},{"in":"new ate(1)","ok":true,"out":"\"new ate(\\n 1\\n)\""},{"in":"5X","ok":false},{"in":"3X,2,3]","ok":false},{"in":"N/*c*7/1","ok":false},{"in":"8","ok":true,"out":"8"},{"in":"] .x","ok":false},{"in":"1234X678901234567890","ok":false},{"in":"3[null,true,false]7","ok":false},{"in":"{\"a\":[1,{\"b\":null}\\]}","ok":false},{"in":"1234567890123457890","ok":true,"out":"1.23456789012346E+18"},{"in":"00.5b","ok":false},{"in":"Xnul","ok":false},{"in":"\".1eX17","ok":false},{"in":"3300t","ok":false},{"in":"N{\"a\":010}","ok":false},{"in":"Infinittb","ok":false},{"in":"{\"\":030}","ok":true,"out":"{\n \"\": 24\n}"},{"in":"new 7ate.1)","ok":false},{"in":"{a\"010}","ok":false},{"in":".1/\\c*/.2]","ok":true,"out":"0.1"},{"in":"3E+X","ok":false},{"in":"/a\"","ok":false},{"in":"1X0","ok":false},{"in":"[/*c*/","ok":false},{"in":"1/*c/,2]","ok":true,"out":"1"},{"in":"310","ok":true,"out":"310"},{"in":"0.1e","ok":false},{"in":"b","ok":false},{"in":"\\31]","ok":false},{"in":"{\"a\"31,\"A\"\"2}","ok":false},{"in":"[7}","ok":false},{"in":"{'':'b\"'}","ok":true,"out":"{\n \"\": \"b\\\"\"\n}"},{"in":"[nutll,true[alse]","ok":false},{"in":"t\"a\":[1,{\"7\"\":null}]}","ok":false},{"in":"123456890123567890","ok":true,"out":"1.23456890123568E+17"},{"in":"[010,08,-010[","ok":false},{"in":"{\"a\":1,\"A2\\","ok":false},{"in":"/,31","ok":false},{"in":"/[1,b2,3]7","ok":false},{"in":"[\"eb","ok":false},{"in":"[a'","ok":false},{"in":"N,1[N]","ok":false},{"in":"bull/x7","ok":false},{"in":"/3","ok":false},{"in":"tr.7e","ok":false},{"in":"[.5","ok":false},{"in":"rue","ok":false},{"in":"1.5e/","ok":false},{"in":".ull","ok":false},{"in":"{\"ba\":1,{\"b\":null}]}\"","ok":false},{"in":"0/","ok":true,"out":"0"},{"in":"tru[","ok":false},{"in":"b-Infinity","ok":false},{"in":"{\"a\":1,\"A\":2","ok":false},{"in":"\"{a:1}","ok":false},{"in":"{'a':'b'","ok":false},{"in":"00.t5","ok":false},{"in":"3{\"a\":1}","ok":false},{"in":"tre","ok":false},{"in":"Inf\"nity","ok":false},{"in":"{'a':'b'N}","ok":false},{"in":"1234567890134567890","ok":true,"out":"1.23456789013457E+18"},{"in":"7a/1}","ok":false},{"in":"70.0","ok":true,"out":"70"},{"in":"\\ud80\"","ok":false},{"in":"[1.5e-3","ok":false},{"in":"/1e5","ok":false},{"in":"X\"\\ud800\"","ok":false},{"in":"\"\\u00e9[Nud83d\\ude00\"","ok":true,"out":"\"é[Nud83d�\""},{"in":"3new Date(1)","ok":false},{"in":"1.57e-3","ok":true,"out":"0.00157"},{"in":".5X","ok":false},{"in":"-finity","ok":false},{"in":"t/*c*/1[X","ok":false},{"in":"1,\",3]","ok":true,"out":"1"},{"in":"\"a\":1}","ok":true,"out":"\"a\""},{"in":"/1","ok":false},{"in":"10,08,-01]","ok":true,"out":"10"},{"in":"ninity","ok":false},{"in":"t0N07","ok":false},{"in":"{a:1[","ok":false},{"in":"[.a.","ok":false},{"in":"a\"","ok":false},{"in":"\"-00","ok":false},{"in":"3\"10","ok":false},{"in":"0.1/","ok":true,"out":"0.1"},{"in":"/0.0","ok":false},{"in":"5.t","ok":false},{"in":"3a7\"","ok":false},{"in":"Nt0e0","ok":false},{"in":"70e0","ok":true,"out":"70"},{"in":"1/*c7/","ok":true,"out":"1"},{"in":"X'a':'\\b'","ok":false},{"in":",][","ok":true,"out":"\"\""},{"in":"{'a'\"'b'b","ok":false},{"in":"tnull,.rue,fal7se]","ok":false},{"in":"1\\345678901N34567\"90","ok":false},{"in":"/*cb]/","ok":false},{"in":"\\ull/x","ok":false},{"in":"700","ok":true,"out":"700"},{"in":"N{\"a\":1}","ok":false},{"in":"\\se","ok":false},{"in":"lse","ok":false},{"in":"[a\"","ok":false},{"in":" ","ok":false},{"in":"\"\\\\ud800\"\"","ok":true,"out":"\"\\\\ud800\""},{"in":"[null,.true,false3","ok":false},{"in":"[]t","ok":true,"out":"[]"},{"in":"bInfinity","ok":false},{"in":"[1/b*c*/,2]","ok":false},{"in":"17.5e-3","ok":true,"out":"0.0175"},{"in":"{'a':'b'/","ok":false},{"in":"\"a:1}","ok":false},{"in":"t 2","ok":false},{"in":"12[456780123456890","ok":false},{"in":"b.1e.1","ok":false},{"in":"30/0","ok":true,"out":"30"},{"in":"{'a':'b'\"","ok":false},{"in":"/0","ok":false},{"in":"1.5eX","ok":false},{"in":"t010,08,-00]","ok":false},{"in":"XN","ok":false},{"in":"{'a':'Nb'}","ok":true,"out":"{\n \"a\": \"Nb\"\n}"},{"in":"0\\8","ok":false},{"in":"[1,t","ok":false},{"in":"bew Date(1)","ok":false},{"in":"010,08,-X10]","ok":true,"out":"8"},{"in":"0.1","ok":true,"out":"0.1"},{"in":"[[1]","ok":false},{"in":"/*7*/1","ok":true,"out":"\"7\""},{"in":"1.5e\"3","ok":false},{"in":"[1/*X*/,2]","ok":true,"out":"[\n 1,\n 2\n]"},{"in":"t[t[1]]]","ok":false},{"in":"..1e1","ok":false},{"in":"1/b*/","ok":true,"out":"1"},{"in":"3X\\ud80\"","ok":false},{"in":"0.1et","ok":false},{"in":"X.e1","ok":false},{"in":"e[0","ok":false},{"in":"/*c*/17","ok":true,"out":"\"c\""},{"in":"3[] x","ok":false},{"in":"-Infni73ty","ok":false},{"in":"3{\"a\":010}","ok":false},{"in":"{'':'b'}","ok":true,"out":"{\n \"\": \"b\"\n}"},{"in":"X[1X,2,\"]","ok":false},{"in":"\"\\d800\"\"","ok":false},{"in":".{\"\":010","ok":false},{"in":"\"a\":1\"}","ok":true,"out":"\"a\""},{"in":"1E+5[","ok":false},{"in":",.1","ok":true,"out":"\"\""},{"in":"[1X*c*/7t]","ok":false},{"in":"N/","ok":false},{"in":"null,true,false].","ok":true,"out":"null"},{"in":"tnull/xb","ok":false},{"in":"7new Da.e(1)","ok":false},{"in":"\\0.","ok":false},{"in":"bxF","ok":false},{"in":"70","ok":true,"out":"70"},{"in":"t[1,2,3]","ok":false},{"in":"N{\"a\"1,\"A\"2}","ok":false},{"in":"nulbl","ok":false},{"in":"00\\/","ok":false},{"in":"\"010,08,-010","ok":false},{"in":"X-1.5e-3","ok":false},{"in":"[\",]","ok":false},{"in":"{\"a\":[1,{.b\":null}]}","ok":false},{"in":"{'3':''}","ok":true,"out":"{\n \"3\": \"\"\n}"},{"in":"[N1]","ok":false},{"in":"..5\\","ok":false},{"in":"nul","ok":false},{"in":"\"5","ok":false},{"in":".-1.5e-3","ok":false},{"in":"[nul,\"rue,false]","ok":false},{"in":"t,]","ok":false},{"in":"[ru","ok":false},{"in":"[null,tre,false]","ok":false},{"in":",1N","ok":true,"out":"\"\""},{"in":"n3\"l","ok":false},{"in":"{\"3a\":1,\"A\":2}","ok":true,"out":"{\n \"3a\": 1,\n \"A\": 2\n}"},{"in":"ne\\ ate()","ok":false},{"in":"3[[1]]]","ok":false},{"in":"\\u00e9\\/ud83d\\ude00\"","ok":false},{"in":"\"\\00e9\\Xud83d\\ude00\"","ok":false},{"in":"\"-0.","ok":false},{"in":"1.-3","ok":false},{"in":"1+5","ok":false},{"in":"\"t1[,,2]","ok":false},{"in":"XInfinbty","ok":false},{"in":"t123456790123567890","ok":false},{"in":"3,1","ok":true,"out":"3"},{"in":"\"3\\u00e9\\ud83d\\ude00\"","ok":true,"out":"\"3é😀\""},{"in":"0.N1et","ok":false},{"in":"[null,3rue,f/lse]","ok":false},{"in":"\"00","ok":false},{"in":"/{\"a\":010}","ok":false},{"in":"ul\\","ok":false},{"in":"{\"a/\":010","ok":false},{"in":"{\"a\":1,\"\":2}","ok":true,"out":"{\n \"a\": 1,\n \"\": 2\n}"},{"in":"Xull/x","ok":false},{"in":"null/x/","ok":false},{"in":"[01008,-010]","ok":false},{"in":"t-003/","ok":false},{"in":"1.5e\"/3","ok":false},{"in":"\\a\"","ok":false},{"in":",2,3]b","ok":true,"out":"\"\""},{"in":".08","ok":true,"out":"0.08"},{"in":"\"0[0","ok":false},{"in":"{\"a\":[1,{\"\\\"/:nul/}]}","ok":false},{"in":"]7","ok":false},{"in":"nutlt","ok":false},{"in":"0/1F","ok":true,"out":"0"},{"in":"{\"a\":1,\"bA\":b}","ok":false},{"in":"/-1.5e-3","ok":false},{"in":".ull/\\[","ok":false},{"in":"7010,08,-0\\10]","ok":true,"out":"7010"},{"in":"X\"a\"","ok":false},{"in":"nu7ll","ok":false},{"in":"undefiXe.","ok":false},{"in":"]/","ok":false},{"in":"ull/x\".","ok":false},{"in":"0.1Ne.1","ok":false},{"in":"{'7a\"':'b'}","ok":true,"out":"{\n \"7a\\\"\": \"b\"\n}"},{"in":"{\"a\":1,\"A\\2}","ok":false},{"in":"nul/x","ok":false},{"in":".5et","ok":false},{"in":"*c*/","ok":false},{"in":"[1/**/32X","ok":false},{"in":"b'a'","ok":false},{"in":"NN b2","ok":false},{"in":"\"[\\Nud8t00\"","ok":false},{"in":"a'\\","ok":false},{"in":"/t{'a':'b'}","ok":false},{"in":"{\"a\"7:1,\"bA\":2}","ok":false},{"in":"null/","ok":false},{"in":"-IXfinity","ok":false},{"in":"unefined","ok":false},{"in":"[1/*.c*/7b2]","ok":false},{"in":"-Infinitby","ok":false},{"in":"b-00","ok":false},{"in":"N[1,","ok":false},{"in":"7[[[1]]]","ok":false},{"in":"\\0N","ok":false},{"in":"\"\\ud800\"N","ok":true,"out":"\"�\""},{"in":"0x1F/","ok":true,"out":"31"},{"in":"1.5-3","ok":false},{"in":"[[1]/]]","ok":false},{"in":"/Nte5","ok":false},{"in":"InfiXiXXy","ok":false},{"in":"308X","ok":false},{"in":"[''.","ok":false},{"in":"X[/.5","ok":false},{"in":"\\[null,tru,false]","ok":false},{"in":"1/*\\*/","ok":true,"out":"1"},{"in":"7[]\"","ok":false},{"in":"{3a''X'}","ok":false},{"in":"nfinity","ok":false},{"in":"3und3fined","ok":false},{"in":"Nre","ok":false},{"in":"[/. x","ok":false},{"in":"7n\"inity","ok":false},{"in":"bud800\"","ok":false},{"in":"1E/+5","ok":false},{"in":"\"[","ok":false},{"in":"X5","ok":false},{"in":"[N,1X]","ok":false},{"in":"{'a':Xbb'}","ok":false},{"in":"3.5","ok":true,"out":"3.5"},{"in":"t00/","ok":false},{"in":"nul/[","ok":false},{"in":"7\\.5","ok":false},{"in":"[null,trbu3.false]","ok":false},{"in":"[010,08,-0\\/0","ok":false},{"in":",1]","ok":true,"out":"\"\""},{"in":"undeNined","ok":false},{"in":"08t","ok":false},{"in":"X-1.5e-/3","ok":false},{"in":"N.","ok":false},{"in":"083","ok":false},{"in":"1.5e-3","ok":true,"out":"0.0015"},{"in":"\"\\u00e9\\ud83d\\ude0","ok":false},{"in":"33[a\":1}","ok":false},{"in":"\\,","ok":false},{"in":"{7}","ok":false},{"in":"ul[","ok":false},{"in":"1/*t*/","ok":true,"out":"1"},{"in":"7,]","ok":true,"out":"7"},{"in":"x","ok":false},{"in":"3Na:1}","ok":false},{"in":"[,b1][","ok":false},{"in":"{\"a\":010","ok":false},{"in":"[]bx","ok":true,"out":"[]"},{"in":"30,08,-010]","ok":true,"out":"30"},{"in":"[1*c*/,2]","ok":false},{"in":"XN\"","ok":false},{"in":"t/*c*7","ok":false},{"in":"[1/N]","ok":false},{"in":"-\\Infinit","ok":false},{"in":"\"\\u007e9\\ud83d\\ude00","ok":false},{"in":"fal","ok":false},{"in":"Infinitb","ok":false},{"in":"b0F","ok":false},{"in":"new Date.(1)","ok":false},{"in":"/[null,truefals]","ok":false},{"in":"Infinit3","ok":false},{"in":"XN5","ok":false},{"in":"\"a\":010}","ok":true,"out":"\"a\""},{"in":"3Na\"N","ok":false},{"in":"\\1t/*c*[","ok":false},{"in":"\\01","ok":false},{"in":"/NaN","ok":false},{"in":"N1e5","ok":false},{"in":"\"\\d800\"","ok":false},{"in":"1,]","ok":true,"out":"1"},{"in":"nul/t","ok":false},{"in":"{\"a\":010[","ok":false},{"in":"\"7\\ud800\"","ok":true,"out":"\"7�\""},{"in":"w Date(1\\","ok":false},{"in":"[0N10,08,-010]","ok":false},{"in":",1.","ok":true,"out":"\"\""},{"in":"[010,08,t010]","ok":false},{"in":"{\"a\"7:1,\"A\":2}","ok":false},{"in":"[-1.5e-3","ok":false},{"in":"1.5e-3\\","ok":false},{"in":"[1/*3X/,2]3","ok":false},{"in":"tx3F\\","ok":false},{"in":"0[1,2,3]","ok":false},{"in":"[010,08,.010]","ok":false},{"in":"ul/x","ok":false},{"in":"'.a'","ok":true,"out":"\".a\""},{"in":"un/deined","ok":false},{"in":"[010,08,010\\","ok":false},{"in":"e0b","ok":false},{"in":"x1","ok":false},{"in":"X15","ok":false},{"in":"t1.5e-3","ok":false},{"in":"00.[5","ok":false},{"in":"{\"a\":0N0}","ok":false},{"in":"b.e3","ok":false},{"in":"tabs","ok":false},{"in":"{\"a\":[b1,{b:null}]}","ok":false},{"in":"nuNlb","ok":false},{"in":"{\"a\":[1[\\\"b\":null}]}","ok":false},{"in":"trNu","ok":false},{"in":"tr\"e","ok":false},{"in":"t1F","ok":false},{"in":"X0e\"0","ok":false},{"in":"/*c*/1/","ok":true,"out":"\"c\""},{"in":".53X","ok":false},{"in":"..e[","ok":false},{"in":"\"ll","ok":false},{"in":"\"[ud800\"","ok":true,"out":"\"[ud800\""},{"in":"tun/efine7","ok":false},{"in":"}.","ok":false},{"in":"[,t1]","ok":false},{"in":"\"\\u00e[9\\ud8[X3d\\ude00\"","ok":false},{"in":"[t","ok":false},{"in":"XN00","ok":false},{"in":"1*c","ok":false},{"in":"1234[567890123456790","ok":false},{"in":"/.e","ok":false},{"in":"\"\\u0e9\\ud83d[\\ude00\"","ok":false},{"in":"0.1e\"","ok":false},{"in":"b.be1","ok":false},{"in":"3ul//x","ok":false},{"in":"{}.","ok":true,"out":"{}"},{"in":"X1/*/N*/","ok":false},{"in":"a:}","ok":false},{"in":"\"\\u00e9\\ud3d\\ude0[0\"","ok":false},{"in":"{\"a\":010\"b}3","ok":false},{"in":"0.1e31","ok":true,"out":"1E+30"},{"in":"X[.1,2,3]","ok":false},{"in":"Xntdeined","ok":false},{"in":"{'a'N'b'","ok":false},{"in":"X[[[1]]]","ok":false},{"in":"123[5677b01234567890","ok":false},{"in":"{\"a\"1,\"A\":2}","ok":false},{"in":"12345678.9012345678.","ok":false},{"in":"-Infin3ty","ok":false},{"in":"t1/*b*/,\"2]","ok":false},{"in":"700.t5","ok":false},{"in":"undefi\"ned","ok":false},{"in":"[\\a'","ok":false},{"in":"NN30","ok":false},{"in":"[null,trut,false3]","ok":false},{"in":"[,\\]\\","ok":false},{"in":"{\"a\":1,\"A\":2\\}","ok":false},{"in":"\".[","ok":false},{"in":"{aX}\"","ok":false},{"in":".{'a'.''}","ok":false},{"in":"1.\"eb","ok":false},{"in":"\"\\u00e9\\ud\"83d\\de0\"","ok":false},{"in":"0btx1","ok":false},{"in":"003\"\"","ok":false},{"in":"t1E\\+5","ok":false},{"in":".[] x","ok":false},{"in":"[/[ull/x","ok":false},{"in":"[1,X]","ok":false},{"in":".07","ok":true,"out":"0.07"},{"in":"/X","ok":false},{"in":"{\"a\"010}","ok":false},{"in":"[1/*c*/,/]","ok":false},{"in":"{\"a\":[,{\"b\":null/]}","ok":false},{"in":"N+5","ok":false},{"in":"\"\\ud[00\"","ok":false},{"in":"t,1]","ok":false},{"in":"1.e3","ok":true,"out":"1000"},{"in":"Xnfiniy","ok":false},{"in":"13+5","ok":false},{"in":"/[1]]]","ok":false},{"in":"1*c*/","ok":false},{"in":"0\\0","ok":false},{"in":"[X] x","ok":false},{"in":"-Infni[y3","ok":false},{"in":"\"{\"a\":1,\"\":2","ok":true,"out":"\"{\""},{"in":"t[]\"","ok":false},{"in":"new Date1/","ok":false},{"in":"\".5","ok":false},{"in":"3-1.5e-3","ok":false},{"in":"{a1\\","ok":false},{"in":"3}","ok":true,"out":"3"},{"in":"Nfbals","ok":false},{"in":"1/.c*/,2]","ok":true,"out":"1"},{"in":"N[null,ruebfalse]","ok":false},{"in":"15-3","ok":false},{"in":"-I3finty","ok":false},{"in":"b-15e-3","ok":false},{"in":"ud00\"","ok":false},{"in":"[Infinit","ok":false},{"in":"be0","ok":false},{"in":"X\"a\":1}","ok":false},{"in":"7b1,2,3]","ok":false},{"in":"7/5","ok":true,"out":"7"},{"in":"0b.77e1","ok":false},{"in":"balse","ok":false},{"in":"X x","ok":false},{"in":"7[010,0X8,-0170]","ok":false},{"in":"7\"\\u00e9\\ud83d\\ude00\"","ok":false},{"in":"\"1.5e-3","ok":false},{"in":"\"a\":1\\,\"A\":2[","ok":true,"out":"\"a\""},{"in":".5e7","ok":true,"out":"5000000"},{"in":"\"X\":1}","ok":true,"out":"\"X\""},{"in":"\"b{\"a\":1","ok":true,"out":"\"b{\""},{"in":"7null","ok":false},{"in":"\"a\":1,\"A\":2}","ok":true,"out":"\"a\""},{"in":"N\".Xe1","ok":false},{"in":"{\"\":010}","ok":true,"out":"{\n \"\": 8\n}"},{"in":"\"\\u0Xe9\\ud83d\\ude00\"","ok":false},{"in":"finit","ok":false},{"in":"..\"","ok":false},{"in":"\"'a':''}","ok":false},{"in":"7,,2]","ok":true,"out":"7"},{"in":"X71,,2]","ok":false},{"in":"\\00t","ok":false},{"in":"X0103","ok":false},{"in":"8[","ok":false},{"in":"{\"a\"00}","ok":false},{"in":"/*c*[/1","ok":false},{"in":"tX,.","ok":false},{"in":"[[1,2,]","ok":false},{"in":"un7Neined","ok":false},{"in":"Xa","ok":false},{"in":".[1,,\"2","ok":false},{"in":"..","ok":false},{"in":"ls3","ok":false},{"in":"\"\\ud/\"0\"","ok":false},{"in":"[010[,08,-010\\]","ok":false},{"in":"{\".a\"\\1,\"A\":2}","ok":false},{"in":"null7x","ok":false},{"in":"new atX1)","ok":false},{"in":"tr.7","ok":false},{"in":"\\{'a':'bX'}.","ok":false},{"in":"[010\\,08,-010]","ok":false},{"in":"\\1.5e7","ok":false},{"in":".b1 .","ok":false},{"in":"{'a':'b'3","ok":false},{"in":"/*c*/","ok":true,"out":"\"c\""},{"in":"new Date7(1)","ok":true,"out":"\"new Date7(\\n 1\\n)\""},{"in":"b1,,2]","ok":false},{"in":"3-Infnty","ok":false},{"in":"\\{\"a\":010}","ok":false},{"in":"ul","ok":false},{"in":"\\0.0","ok":false},{"in":"{'a\\t:'b'X}","ok":false},{"in":"a''b'}","ok":false},{"in":"tXrue","ok":false},{"in":"tr[e3","ok":false},{"in":"new Date1)","ok":false},{"in":"X{\"a\":010}","ok":false},{"in":"XN+5","ok":false},{"in":"\".[,1","ok":false},{"in":"[N1,,2]","ok":false},{"in":"\"\\7ud800\"","ok":false},{"in":"t{'a'7:''}","ok":false},{"in":"3.5b","ok":false},{"in":"[010,0,-010]","ok":true,"out":"[\n 8,\n 0,\n -10\n]"},{"in":"{\"a\":1\\\"A\"32}","ok":false},{"in":"b0[","ok":false},{"in":"[null,true,fale]","ok":false},{"in":"new \"Date(1)","ok":false},{"in":"b0.0","ok":false},{"in":"\\\"a\":1}","ok":false},{"in":"N-00","ok":false},{"in":"lt","ok":false},{"in":"\"\\u00e9\\ud8\"3d\\ude0N0X\"","ok":false},{"in":".5-3","ok":false},{"in":"\"\\ud[8007\"","ok":false},{"in":"1.eX","ok":false},{"in":"\\n7\\ull","ok":false},{"in":".1/*Xc*/b","ok":true,"out":"0.1"},{"in":",2,3N]","ok":true,"out":"\"\""},{"in":"[null,true,flse/","ok":false},{"in":"[010","ok":false},{"in":"\"\\u00e9\\ud83d\\ude0\"","ok":false},{"in":"N0.b","ok":false},{"in":"/\"0b0","ok":false},{"in":"[nul3,true,false]","ok":false},{"in":"b.","ok":false},{"in":"tr/u3e","ok":false},{"in":"08X","ok":false},{"in":"1/*\\c*/","ok":true,"out":"1"},{"in":"f[ls\"et","ok":false},{"in":"{/a\":[1,{\"b\":nul7l}]}","ok":false},{"in":"InfinXby","ok":false},{"in":"\"\\u00e9\\ud/3\\udte00\"","ok":false},{"in":"{7\"a\":1,\"A\"b:2\"","ok":false},{"in":"[1F","ok":false},{"in":"3700","ok":true,"out":"3700"},{"in":"t/300","ok":false},{"in":".5.[","ok":false},{"in":"{a:1.}","ok":true,"out":"{\n \"a\": 1\n}"},{"in":",N","ok":true,"out":"\"\""},{"in":"/*N*/1","ok":true,"out":"\"N\""},{"in":"\\t8","ok":false},{"in":"\"{t\"a\":1}","ok":true,"out":"\"{t\""},{"in":"/[[[1].]]","ok":false},{"in":"1/*c*/[","ok":true,"out":"1"},{"in":"[null,true,fNlse]","ok":false},{"in":"{\"a\":3010","ok":false},{"in":"null7","ok":false},{"in":".5\\e3","ok":false},{"in":"07x1\\","ok":false},{"in":"0x1Ft","ok":false},{"in":".E+5","ok":false},{"in":"7\"b","ok":false},{"in":" X","ok":false},{"in":"\"\\u009\\ud83dtude00\"","ok":false},{"in":"\"\\u0[0e9\\ud83d\\ue00\"","ok":false},{"in":"{'a'/:'b'}","ok":false},{"in":"[1/**/.2]","ok":false},{"in":"[1,/X","ok":false},{"in":"\"10","ok":false},{"in":"{\"a\":.[1,{\"b\":null}]}","ok":false},{"in":".1e/1","ok":false},{"in":"\"\"a\":1}","ok":true,"out":"\"\""},{"in":"1/c*/","ok":true,"out":"1"},{"in":"tx","ok":false},{"in":"\\/1,2,3]","ok":false},{"in":".a'","ok":false},{"in":"nbll/x","ok":false},{"in":"e","ok":false},{"in":"\"aN","ok":false},{"in":"babse","ok":false},{"in":"-Infintt","ok":false},{"in":"/e0","ok":false},{"in":",31","ok":true,"out":"\"\""},{"in":"3x1F","ok":false},{"in":"130","ok":true,"out":"130"},{"in":"{a1N","ok":false},{"in":"Ininity\"","ok":false},{"in":"ud800\"","ok":false},{"in":"070.\\","ok":false},{"in":"\\\"/1 2","ok":false},{"in":"[]7","ok":true,"out":"[]"},{"in":"N[].","ok":false},{"in":"Infinity[","ok":false},{"in":"a7","ok":false},{"in":"\"\\udN80","ok":false},{"in":"\"a\":\\1}","ok":true,"out":"\"a\""},{"in":"new DaX1)","ok":false},{"in":"a\"'","ok":false},{"in":"[null,true,fale/[","ok":false},{"in":"\"/0","ok":false},{"in":".0xFb","ok":false},{"in":"X[][","ok":false},{"in":"ew Date(1)","ok":false},{"in":"0x1/","ok":true,"out":"1"},{"in":"{\"a\"t:1,\"A\"b2}","ok":false},{"in":"undefi.7d","ok":false},{"in":"undtine","ok":false},{"in":"\"X\\u00e9\\ud83d\\ude00\"","ok":true,"out":"\"Xé😀\""},{"in":"1\\","ok":false},{"in":"X'a'","ok":false},{"in":"[1/*\"*/,2]X","ok":true,"out":"[\n 1,\n 2\n]"},{"in":".tr\\ue","ok":false},{"in":"1N","ok":false},{"in":"-1.e-3","ok":true,"out":"-0.001"},{"in":"\\.e5","ok":false},{"in":"Xb","ok":false},{"in":"-Inf[n/ty","ok":false},{"in":"NNaN","ok":false},{"in":"7] x\"","ok":true,"out":"7"},{"in":"7ull","ok":false},{"in":"00X5","ok":false},{"in":"[1/*.c*/N]","ok":false},{"in":"X''","ok":false},{"in":"eN","ok":false},{"in":"{a:1[}3","ok":false},{"in":"[]\"","ok":true,"out":"[]"},{"in":"31/*cX*/,2]","ok":true,"out":"31"},{"in":"7{}3","ok":false},{"in":"b0x1/","ok":false},{"in":"{}b","ok":true,"out":"{}"},{"in":"\\\".0","ok":false},{"in":"{\"a\"7010}N3","ok":false},{"in":".[5","ok":false},{"in":"N/*\"c*/1","ok":false},{"in":".}\\","ok":false},{"in":".0.5","ok":false},{"in":"\"a\":[1}","ok":true,"out":"\"a\""},{"in":"0x\\XF","ok":false},{"in":"/[1,2,3]","ok":false},{"in":"[\\ud00\"/","ok":false},{"in":"[17,N","ok":false},{"in":"\"\\7d[00\"/","ok":false},{"in":"{\"/\":1,\"\"A\":2}","ok":false},{"in":"undeNfined","ok":false},{"in":"n.7ll/x","ok":false},{"in":"[.,1]","ok":false},{"in":"/t*c*/1","ok":false},{"in":"nu\"lbx","ok":false},{"in":"1]","ok":true,"out":"1"},{"in":"t1/*c*/3","ok":false},{"in":"N\\}X","ok":false},{"in":"1/*\"*/","ok":true,"out":"1"},{"in":"0..eb","ok":false},{"in":"1.Ne3.","ok":false},{"in":".[3 ","ok":false},{"in":"[null,trbe,false]","ok":false},{"in":"b/*c*/17","ok":false},{"in":"/-1.5b-3","ok":false},{"in":"N{\"a\":1,\"A\":2}","ok":false},{"in":"{\"\":1\"A\":2}","ok":false},{"in":"{a:1","ok":false},{"in":"1E+","ok":false},{"in":"bN*c*1","ok":false},{"in":"{\"a\"N[1,{\"b7null}]}","ok":false},{"in":"[010,08,-0b0","ok":false},{"in":"123456/89.234567890","ok":true,"out":"123456"},{"in":"[\"1]","ok":false},{"in":"\"a/se","ok":false},{"in":"{\"a\"b,\"A\":2}","ok":false},{"in":"nNll\\","ok":false},{"in":"/0b[","ok":false},{"in":"unXdebineN","ok":false},{"in":"/00.5","ok":false},{"in":"XN1 ","ok":false},{"in":"/1,]\"","ok":false},{"in":"new DateXN(1)","ok":true,"out":"\"new DateXN(\\n 1\\n)\""},{"in":"['/':'b'/","ok":false},{"in":"[7e5","ok":false},{"in":"[]. x","ok":true,"out":"[]"},{"in":"[[[1]]t]","ok":false},{"in":"[1,//","ok":false},{"in":"30","ok":true,"out":"30"},{"in":"+5","ok":false},{"in":"{\"a\":/[1,{\"b\":nul}]}","ok":false},{"in":"\"\\u00e9\\.d83d\\ud.00\"","ok":false},{"in":"[35","ok":false},{"in":"701[","ok":false},{"in":"true[","ok":false},{"in":"Inf3inity","ok":false},{"in":"1*b/","ok":false},{"in":"b\\}3","ok":false},{"in":"null,true,false][","ok":true,"out":"null"},{"in":"31F","ok":false},{"in":"undefine","ok":false},{"in":"[1,,2","ok":false},{"in":"X{}X","ok":false},{"in":"3[]","ok":false},{"in":"Infinit\\","ok":false},{"in":"[\"\\u00e.9\\ud83d\\ude00\"","ok":false},{"in":"/*c*/\\/","ok":true,"out":"\"c\""},{"in":"InfiNit.y","ok":false},{"in":".1\\e1","ok":false},{"in":"\"aN:1}","ok":false},{"in":"Xbe/","ok":false},{"in":"{\"\":1,\"A\"\"2}","ok":false},{"in":"\\[1,]/","ok":false},{"in":"1/*c*/,.2]","ok":true,"out":"1"},{"in":"\"\\u00e9\\ud83d\\utde00\"","ok":false},{"in":"-I.n\\f\\inity","ok":false},{"in":"\"\\/0e9\\ud83d\\ude00\"b","ok":true,"out":"\"/0e9😀\""},{"in":"1,2,3\\]","ok":true,"out":"1"},{"in":"\"\"0","ok":true,"out":"\"\""},{"in":"0.7","ok":true,"out":"0.7"},{"in":"[1/.c*X,2]","ok":false},{"in":".007.5","ok":false},{"in":"\"\\udb800\"[","ok":true,"out":"\"�0\""},{"in":"[\\/*c*/","ok":false},{"in":"{\"a\":[1,{b\":null]}","ok":false},{"in":"[[1,,77]","ok":false},{"in":".[0","ok":false},{"in":"Infinty","ok":false},{"in":".5e-\"3","ok":false},{"in":"\"\"\\u00e\\ud8d\\ude00\"","ok":true,"out":"\"\""},{"in":"./","ok":false},{"in":"\"'a'","ok":false},{"in":"10N","ok":false},{"in":"nu\"l/x","ok":false},{"in":"\"\\N00e9\\ud83d\\ude7t00\"","ok":false},{"in":"[-0","ok":false},{"in":".t","ok":false},{"in":"trueX","ok":false},{"in":"N\"","ok":false},{"in":"[,1[","ok":false},{"in":"[null,true,f7als]","ok":false},{"in":"N-70","ok":false},{"in":"/0.","ok":false},{"in":"{'a'7'b'3}","ok":false},{"in":".07.5","ok":false},{"in":"3[1,2,3]","ok":false},{"in":"{a.1}","ok":false},{"in":".0.0","ok":false},{"in":"13E+5","ok":true,"out":"1300000"},{"in":"\\.","ok":false},{"in":"\\.aN","ok":false},{"in":"\\X[]","ok":false},{"in":"\"{\"a[\":1}","ok":true,"out":"\"{\""},{"in":"nu\"lN","ok":false},{"in":"01","ok":true,"out":"1"},{"in":"[alse","ok":false},{"in":"-Infint\\","ok":false},{"in":"[010\\,08,-0310]","ok":false},{"in":".5\"","ok":false},{"in":"/0.5","ok":false},{"in":"123456780123567890","ok":true,"out":"1.23456780123568E+17"},{"in":"\"\\u00e9\\.d83d\\ude00\"X","ok":false},{"in":"Nc","ok":false},{"in":"\"\\u00e9\\ud83d\\ude00\"N","ok":true,"out":"\"é😀\""},{"in":"71e\\5","ok":false},{"in":"ta'","ok":false},{"in":"{\"\\\":1,\"A\":2\"}","ok":false},{"in":"\\5.N","ok":false},{"in":"\"\\\\ud800\".","ok":true,"out":"\"\\\\ud800\""},{"in":"[010,08,\"-010]","ok":false},{"in":"\"010,08,-010]","ok":false},{"in":"unefineNd","ok":false},{"in":"fNseX","ok":false},{"in":"\\/","ok":false},{"in":"\"\"X","ok":true,"out":"\"\""},{"in":"Infnity","ok":false},{"in":"7[,1]","ok":false},{"in":"b\\1,2,]","ok":false},{"in":"-..","ok":false},{"in":"{N","ok":false},{"in":"X[[e0","ok":false},{"in":"[0.10,08,-010]","ok":false},{"in":"truXe","ok":false},{"in":"X0.1e1","ok":false},{"in":"{tb'':'b'}","ok":false},{"in":"nulx","ok":false},{"in":"bub.l","ok":false},{"in":"[,,2]","ok":true,"out":"[\n \"\",\n \"\",\n 2\n]"},{"in":"[1,2]","ok":true,"out":"[\n 1,\n 2\n]"},{"in":"-0X","ok":false},{"in":"31.5e-3","ok":true,"out":"0.0315"},{"in":"{a:1/","ok":false},{"in":"{'a'N:'b'}","ok":false},{"in":"b}/","ok":false},{"in":".e5","ok":false},{"in":"{'a'/:'\\'}X","ok":false},{"in":"finity","ok":false},{"in":".b1\"","ok":false},{"in":"[\"[[1N]]]","ok":false},{"in":"X\"1,","ok":false},{"in":"/0bb","ok":false},{"in":"aNN","ok":false},{"in":"[N/*c*/,2]","ok":false},{"in":"1E\\+\"5","ok":false},{"in":"\"/t*c*/1","ok":false},{"in":"0x1F3tt","ok":false},{"in":"[\"a\":[1,{b\":null}]}","ok":false},{"in":"bXa'","ok":false},{"in":"un\"de\"find","ok":false},{"in":"{a:N1","ok":false},{"in":"/t/ue","ok":false},{"in":"/708X","ok":false},{"in":".[[1]]]","ok":false},{"in":"[null,true,fa\\3se]","ok":false},{"in":"X0.0","ok":false},{"in":"03x1F","ok":false},{"in":"t1\"5e","ok":false},{"in":"[null,tr7ue,false\"","ok":false},{"in":"[010,0,-10]","ok":true,"out":"[\n 8,\n 0,\n -10\n]"},{"in":"[,/","ok":false},{"in":"N]","ok":false},{"in":"\"\\Nd80\"","ok":false},{"in":"0[0.5","ok":false},{"in":"t/*cX*/1","ok":false},{"in":"Inftnity","ok":false},{"in":"[null,tr\\e,false\"","ok":false},{"in":"bull/x","ok":false},{"in":".{\"a\"t1,\"A7\":2}","ok":false},{"in":"]\\aN","ok":false},{"in":"[1/]","ok":false},{"in":"\"0\"","ok":true,"out":"\"0\""},{"in":"{\"a\".1,\"A\":2}","ok":false},{"in":"[1 2b","ok":false},{"in":"1Ne5","ok":false},{"in":"\"1","ok":false},{"in":"{\"a\":[,{\"b\"\":null}\\}","ok":false},{"in":"/ndined","ok":false},{"in":"X[1\\]]","ok":false},{"in":"fals\\","ok":false},{"in":"t{\"a\":.}","ok":false},{"in":"\"a\":13}","ok":true,"out":"\"a\""},{"in":"{\"a\"N:010/}","ok":false},{"in":"a:[1}","ok":false},{"in":"41e5","ok":true,"out":"4100000"},{"in":"{\"a\"\\:\"010.","ok":false},{"in":"/aNN","ok":false},{"in":"\\31,2,3]","ok":false},{"in":"Nt0t","ok":false},{"in":"\"\\udb800\"3","ok":true,"out":"\"�0\""},{"in":"\"\\u00e9\\ud83[d\\ude00\"","ok":false},{"in":"7[1,N]","ok":false},{"in":"ba'","ok":false},{"in":"[[1]b]","ok":false},{"in":"[]t x","ok":true,"out":"[]"},{"in":"[1.5e[","ok":false},{"in":"/0be0","ok":false},{"in":"/-1X.5e-3","ok":false},{"in":"/0e0","ok":false},{"in":"[1,,2t3","ok":false},{"in":"1,bt]","ok":true,"out":"1"},{"in":"[ x","ok":false},{"in":"-Infintty","ok":false},{"in":"1,7]","ok":true,"out":"1"},{"in":"\"\\u0e9ud83d\\ude00\"","ok":false},{"in":"bN","ok":false},{"in":"ud80\"","ok":false},{"in":"new [Date(1)","ok":false},{"in":"[null,tr.e,false]","ok":false},{"in":"ew D/te(1)","ok":false},{"in":",2,3]","ok":true,"out":"\"\""},{"in":"-I\"fin\\itty","ok":false},{"in":".\\5","ok":false},{"in":"undefiNed","ok":false},{"in":"0x1","ok":true,"out":"1"},{"in":".{a1}7","ok":false},{"in":"/0[","ok":false},{"in":"x1F","ok":false},{"in":".finity","ok":false},{"in":"373ull/x","ok":false},{"in":"5.N","ok":false},{"in":"{a:\\1","ok":false},{"in":"0x1Fb\\","ok":false},{"in":"[1,]\\","ok":true,"out":"[\n 1\n]"},{"in":"nuXlbx","ok":false},{"in":"[//c*/,2]","ok":false},{"in":"tXue","ok":false},{"in":"N15","ok":false},{"in":"-tInfnity","ok":false},{"in":"\"0e0[","ok":false},{"in":"bt1","ok":false},{"in":"b1.5e-3","ok":false},{"in":"7,1]","ok":true,"out":"7"},{"in":"3[a:1b","ok":false},{"in":".10\\","ok":false},{"in":"\\[1/]/","ok":false},{"in":"null/xb","ok":false},{"in":"\\[[1].]]","ok":false},{"in":"Infinity\"","ok":false},{"in":"[\"\\ubd800\"","ok":false},{"in":"[1N*/*b,2]","ok":false},{"in":"b[1,2,3]","ok":false},{"in":"{\"a\":010/","ok":false},{"in":"[false","ok":false},{"in":"[1.57/","ok":false},{"in":"[[7[1]]]","ok":false},{"in":"tt}","ok":false},{"in":"{\"a:[1,{\"b\":7ull]}","ok":false},{"in":"N1E+[","ok":false},{"in":"c\"\\ub.800\"","ok":false},{"in":"1.Ne.","ok":false},{"in":"b{\"a\":010}","ok":false},{"in":"7Infiniy","ok":false},{"in":"\".'","ok":false},{"in":"unde\"ined","ok":false},{"in":"\"\\u\\d8N0","ok":false},{"in":"/false","ok":false},{"in":"\\5.","ok":false},{"in":".\\ud800","ok":false},{"in":"trut","ok":false},{"in":"'","ok":false},{"in":"tIN/nfinity","ok":false},{"in":"X-.X5e-3","ok":false},{"in":"7[1,,]","ok":false},{"in":"'a':'b'}","ok":true,"out":"\"a\""},{"in":"[5b","ok":false},{"in":"{\"3a\"010}[","ok":false},{"in":"ue","ok":false},{"in":"X-N0","ok":false},{"in":"\\.0X","ok":false},{"in":"1,2\\,3]","ok":true,"out":"1"},{"in":"\\-1.5e-3","ok":false},{"in":"\"\\u00e9\\ud83d\\ue00\"","ok":false},{"in":"\"N.0","ok":false},{"in":"[nu\\lb","ok":false},{"in":"XE+","ok":false},{"in":"[}t","ok":false},{"in":"N0\\0","ok":false},{"in":"\"1+5","ok":false},{"in":"//*tc*/","ok":true,"out":"\"*tc*/\""},{"in":"t..","ok":false},{"in":"[,\\]","ok":false},{"in":"[t]","ok":false},{"in":"new /Date(1)","ok":false},{"in":"[\\]","ok":false},{"in":"t5.","ok":false},{"in":"null,true,false]","ok":true,"out":"null"},{"in":"new DateXb1)","ok":false},{"in":"{'a't''}","ok":false},{"in":"X\"\"a\":[13{\"b\":null}]}","ok":false},{"in":"{\"a\":17\"A\":2}","ok":false},{"in":"bb 2","ok":false},{"in":"\"\\3u00e9\\ub83d\\ude00\"","ok":false},{"in":"\"a\"\\:1}","ok":true,"out":"\"a\""},{"in":"[n3ll,true,false]","ok":false},{"in":"-0/","ok":true,"out":"0"},{"in":"\"\\/ud800\"\\","ok":true,"out":"\"/ud800\""},{"in":"8b","ok":false},{"in":"/08","ok":false},{"in":"bnull,truefalse]","ok":false},{"in":"1E5t","ok":false},{"in":".5e3-3","ok":false},{"in":"../","ok":false},{"in":"01et7","ok":false},{"in":"3b 2","ok":false},{"in":"[1/*c/,2","ok":false},{"in":"{\"a\"\\N:1,\"A\":2}","ok":false},{"in":"X{\"a\":1}","ok":false},{"in":"1/*Xc*/,2]","ok":true,"out":"1"},{"in":"{'a':7\"b'.","ok":false},{"in":"\"\\udX0b\"","ok":false},{"in":"Ne0","ok":false},{"in":"ulXl/x.","ok":false},{"in":"[01008,t010]","ok":false},{"in":"[010,30X,010]","ok":false},{"in":"b5\\","ok":false},{"in":",1\\","ok":true,"out":"\"\""},{"in":"0x31","ok":true,"out":"49"},{"in":"\"\".3\"","ok":true,"out":"\"\""},{"in":"[,\".]","ok":false},{"in":"/aN","ok":false},{"in":"0X1F","ok":true,"out":"31"},{"in":"\"\\u00e9\\u83d\\ude00\"","ok":false},{"in":"/-00","ok":false},{"in":"\"\\u00e9\\ud83dbude00\"","ok":true,"out":"\"é�bude00\""},{"in":"Null/x","ok":false},{"in":"1.\"e","ok":false},{"in":"{a:1\\}","ok":false},{"in":"\"a\":01","ok":true,"out":"\"a\""},{"in":"{\"a\"[0.10}","ok":false},{"in":"0\"1F","ok":false},{"in":"*c/13","ok":false},{"in":"NX","ok":false},{"in":"\\N/N/","ok":false},{"in":"null/.x","ok":false},{"in":"Xullb","ok":false},{"in":"1.5e-b3","ok":false},{"in":"tN","ok":false},{"in":"{a:1X","ok":false},{"in":"\"t}","ok":false},{"in":"3ew Date(1)","ok":false},{"in":"\".ud800\"","ok":true,"out":"\".ud800\""},{"in":"{.a\":00}","ok":false},{"in":"0.b1b1.","ok":false},{"in":"7NaN","ok":false},{"in":"1/*c*/\\","ok":true,"out":"1"},{"in":"[null,tr3e,false]","ok":false},{"in":"\":1}","ok":false},{"in":".'a':'b'}","ok":false},{"in":"ab","ok":false},{"in":"[1//**/,2]","ok":false},{"in":"\\00","ok":false},{"in":"\"\\u00e9\\ud83d\\udt/700\"","ok":false},{"in":"NI\\nfinty","ok":false},{"in":"ull/","ok":false},{"in":"n[ll","ok":false},{"in":"\"a\":}","ok":true,"out":"\"a\""},{"in":"/.","ok":false},{"in":"X\\N{}","ok":false},{"in":"[010,08,-030]","ok":false},{"in":"/3c/1","ok":false},{"in":"[1/,2]","ok":false},{"in":"00.5\"","ok":false},{"in":"[,1","ok":false},{"in":"N] .b","ok":false},{"in":"new b3Date(1)","ok":true,"out":"\"new b3Date(\\n 1\\n)\""},{"in":"[\\.","ok":false},{"in":" t","ok":false},{"in":".\\ud80\"","ok":false},{"in":"1,3]","ok":true,"out":"1"},{"in":"01e/","ok":false},{"in":"b0e0","ok":false},{"in":"77N","ok":false},{"in":"baN","ok":false},{"in":"[.}","ok":false},{"in":".1.5e-3","ok":false},{"in":"-0\"","ok":false},{"in":"N\"a\":1}","ok":false},{"in":"3be0/","ok":false},{"in":"t[[[1]]]","ok":false},{"in":"7010","ok":true,"out":"7010"},{"in":"-Infinbt","ok":false},{"in":"3ew Datbe(1)","ok":false},{"in":"[1,,t]","ok":false},{"in":".aN","ok":false},{"in":"/*c*/1N","ok":true,"out":"\"c\""},{"in":"{a:1.","ok":false},{"in":"\"n\"ll","ok":true,"out":"\"n\""},{"in":"7X","ok":false},{"in":"7\"a\"","ok":false},{"in":"undebf/ed","ok":false},{"in":"Nue","ok":false},{"in":"\"t","ok":false},{"in":"\\ba'","ok":false},{"in":"[7,]","ok":true,"out":"[\n 7\n]"},{"in":"undefineX","ok":false},{"in":".a:1b}","ok":false},{"in":"/*c\"31","ok":false},{"in":"tt10","ok":false},{"in":"\"\\u0e9\\td83d/ude00\"","ok":false},{"in":"1e1","ok":true,"out":"10"},{"in":"\\ 2","ok":false},{"in":"\"1/*c*/[","ok":false},{"in":"{\"a\":[1,{\"b:null7}]}","ok":false},{"in":"ew ate()","ok":false},{"in":"\"new Date(1)","ok":false},{"in":"3010,08,-0\\0]","ok":true,"out":"3010"},{"in":"3.1,2,]","ok":true,"out":"3.1"},{"in":"53","ok":true,"out":"53"},{"in":"nll,tre,false]","ok":false},{"in":"0.1X","ok":false},{"in":"1*c*","ok":false},{"in":"[,1]X","ok":true,"out":"[\n \"\",\n 1\n]"},{"in":"f3ase","ok":false},{"in":"Xe5","ok":false},{"in":"5\\","ok":false},{"in":",1b.","ok":true,"out":"\"\""},{"in":"XX]N","ok":false},{"in":"\"010","ok":false},{"in":"\"\\u00e9\\3ud83d\\ude007\"","ok":false},{"in":".u\"","ok":false},{"in":"\\0[","ok":false},{"in":"7\"0","ok":false},{"in":"12345678t0234567890","ok":false},{"in":"\\null/x","ok":false},{"in":"null\"","ok":false},{"in":"\\ull/Nx","ok":false},{"in":"t0.0","ok":false},{"in":"udN00\"","ok":false},{"in":"-I.nfinityX","ok":false},{"in":"New bate(1)","ok":false},{"in":"tru\\","ok":false},{"in":"[010,08,01\\0]","ok":false},{"in":"10\\","ok":false},{"in":"\\Infinit","ok":false},{"in":"ull\\.x","ok":false},{"in":"[n[ull,true,false][","ok":false},{"in":"[1,23]","ok":true,"out":"[\n 1,\n 23\n]"},{"in":"70.X","ok":false},{"in":"btru","ok":false},{"in":"{'a':'b'[","ok":false},{"in":"a:1}[","ok":false},{"in":"7Infinit","ok":false},{"in":"\"NaN","ok":false},{"in":"[00","ok":false},{"in":"trNe","ok":false},{"in":"[0N.008,-010]","ok":false},{"in":"X}","ok":false},{"in":"[01,08,-010","ok":false},{"in":"Xt","ok":false},{"in":",]","ok":true,"out":"\"\""},{"in":"unde/i[e","ok":false},{"in":"{\"\"\":1,\"A\":2}","ok":false},{"in":"X-00","ok":false},{"in":"t{\"a\"7:010\\}","ok":false},{"in":"[\\5","ok":false},{"in":"t00","ok":false},{"in":"1/5b-3","ok":true,"out":"1"},{"in":"nullb","ok":false},{"in":"b5.","ok":false},{"in":"1/*cb*/","ok":true,"out":"1"},{"in":"N00.","ok":false},{"in":"7{'a':'b'}","ok":false},{"in":"\"]b","ok":false},{"in":"t53","ok":false},{"in":"/+5","ok":false},{"in":"\"[\\1,,2]","ok":false},{"in":"/\"\\u00e9\\/d83dude00\"","ok":false},{"in":"nu\"ll","ok":false},{"in":"\"\"a\":1,\"A\":2}","ok":true,"out":"\"\""},{"in":"b3","ok":false},{"in":"-In.finty","ok":false},{"in":"Xt00","ok":false},{"in":"{","ok":false},{"in":"t] ","ok":false},{"in":"0.","ok":true,"out":"0"},{"in":"0x/1F","ok":false},{"in":".0N","ok":false},{"in":"\\-00","ok":false},{"in":"[.a'","ok":false},{"in":"[1/*c\"/,2]","ok":false},{"in":"\"\\ud.8\\0X\"","ok":false},{"in":"/t","ok":false},{"in":".new Date(1","ok":false},{"in":".N0","ok":false},{"in":"1E+[3","ok":false},{"in":" b2","ok":false},{"in":"nXull","ok":false},{"in":"7.e0","ok":true,"out":"7"},{"in":"\\\"","ok":false},{"in":"\"a\":1,\"A\":2.","ok":true,"out":"\"a\""},{"in":"15Xe-3","ok":false},{"in":"nbl/x","ok":false},{"in":"Nnfnity","ok":false},{"in":".8t","ok":false},{"in":"/nullX/x","ok":false},{"in":"[.,]","ok":false},{"in":"ewtDate31)","ok":false},{"in":"null\\","ok":false},{"in":"0X8","ok":true,"out":"8"},{"in":"[1,N","ok":false},{"in":"bnul\\l/x","ok":false},{"in":"\"3","ok":false},{"in":"\"{\"a\":010}","ok":true,"out":"\"{\""},{"in":"['a'","ok":false},{"in":"undefiNeb","ok":false},{"in":"t[/1","ok":false},{"in":"XNaNt","ok":false},{"in":"-7","ok":true,"out":"-7"},{"in":"[010,0[8,-010]","ok":false},{"in":"ll","ok":false},{"in":"\\ew NNDate(1)","ok":false},{"in":"12,3]","ok":true,"out":"12"},{"in":"{\"a\":[1,\"b\":null}]}","ok":false},{"in":"N/*.c/1","ok":false},{"in":"{\"a\":[1,{\"\":null\"}]}","ok":false},{"in":".1e5","ok":true,"out":"10000"},{"in":"7/fa\"lse","ok":true,"out":"7"},{"in":"7c*/","ok":false},{"in":"7ew Nate(1)","ok":false},{"in":"Infinitby","ok":false},{"in":".[N","ok":false},{"in":"1N.N5e3","ok":false},{"in":"[1t,,2]","ok":false},{"in":".[0b0,08,-010]","ok":false},{"in":"[nll,true,falte]/","ok":false},{"in":"fa\"l/se","ok":false},{"in":"1.e-3","ok":true,"out":"0.001"},{"in":"Itfinity","ok":false},{"in":"[.]","ok":false},{"in":"0x.7F","ok":false},{"in":"ttrue","ok":false},{"in":"N[0.1e1","ok":false},{"in":"..t0.5","ok":false},{"in":"Infinitt","ok":false},{"in":"Infini3ty.","ok":false},{"in":"/\\,]","ok":false},{"in":"t\"0","ok":false},{"in":"unde7fined","ok":false},{"in":"b0.5","ok":false},{"in":"0.1eX","ok":false},{"in":"\\nfinit","ok":false},{"in":"\"null,rue,false]","ok":false},{"in":"et","ok":false},{"in":"0\\","ok":false},{"in":"] x/b","ok":false},{"in":"7t0e0","ok":false},{"in":"N/b}","ok":false},{"in":"[nu\\ll,true,f3alse]","ok":false},{"in":"t/.u/","ok":false},{"in":"\"\\ud800\"\"","ok":true,"out":"\"�\""},{"in":"5t3","ok":false},{"in":"{\"a\":1,\"A\":2}\\","ok":true,"out":"{\n \"a\": 2\n}"},{"in":".1eb1","ok":false},{"in":"N/N*c*/","ok":false},{"in":"aX1}","ok":false},{"in":"[330","ok":false},{"in":"-Inftinity","ok":false},{"in":"[null,true,false","ok":false},{"in":",t1","ok":true,"out":"\"\""},{"in":"1N+5","ok":false},{"in":"b.t/","ok":false},{"in":"]t","ok":false},{"in":".10X","ok":false},{"in":"/null,true,false]","ok":false},{"in":"7-1.5e-","ok":false},{"in":"3[,1","ok":false},{"in":"b\\1,,2]","ok":false},{"in":"[1N3","ok":false},{"in":"-07","ok":true,"out":"-7"},{"in":"3-00","ok":false},{"in":"t010","ok":false},{"in":"[2","ok":false},{"in":"[X \\x","ok":false},{"in":"tue","ok":false},{"in":"\"1b /2","ok":false},{"in":".15","ok":true,"out":"0.15"},{"in":"[]X","ok":true,"out":"[]"},{"in":"{\\\\","ok":false},{"in":"N0X1bF","ok":false},{"in":"\"\\1.5e-3","ok":false},{"in":"10[","ok":false},{"in":"{\"a[\":[1,{3\"b\":ull}]}","ok":false},{"in":"xF","ok":false},{"in":"[null,trNe,false]","ok":false},{"in":"a3","ok":false},{"in":"0N","ok":false},{"in":"01e","ok":false},{"in":"X1 2","ok":false},{"in":"new Dae1)","ok":false},{"in":"-I\\nfinNty","ok":false},{"in":"X*c*/1\"","ok":false},{"in":"\"a\":[1,b\"b\":null}]}","ok":true,"out":"\"a\""},{"in":"12345N678901234567890","ok":false},{"in":"1/\\3c*/,2]","ok":true,"out":"1"},{"in":".12","ok":true,"out":"0.12"},{"in":"[1\"/c*/,2]","ok":false},{"in":" 23","ok":true,"out":"23"}]⏎̸
A expr/testdata/languageservices/SOURCE +6 -0
@@ -0,0 +1,6 @@
1+The *.json files in this directory are copied unmodified from
2+https://github.com/actions/languageservices, directory expressions/testdata,
3+commit 4043eda158e16579cc5fb1b0b07a4bce2a76f0b5 (MIT, Copyright GitHub, Inc.;
4+see ../../../THIRD_PARTY.md). They are shared by the TypeScript and the C#
5+implementations of GitHub Actions expressions. ../../xlang_test.go replays
6+them the way expressions/src/xlang.test.ts of that repository does.
A expr/testdata/languageservices/basic.json +64 -0
@@ -0,0 +1,64 @@
1+{
2+ "empty_expression": [{
3+ "expr": "",
4+ "result": {
5+ "kind": "Null",
6+ "value": null
7+ }
8+ }],
9+ "equal_simple": [{
10+ "expr": "1 == 2",
11+ "result": {
12+ "kind": "Boolean",
13+ "value": false
14+ }
15+ }],
16+ "context_simple_access": [{
17+ "expr": "simple",
18+ "result": {
19+ "kind": "String",
20+ "value": "foo"
21+ },
22+ "contexts": {
23+ "simple": "foo"
24+ }
25+ }],
26+ "context_case-insensitive": [{
27+ "expr": "SIMple.TEst",
28+ "result": {
29+ "kind": "Number",
30+ "value": 123.0
31+ },
32+ "contexts": {
33+ "simPLE": {
34+ "teST": 123
35+ }
36+ }
37+ }],
38+ "context access with wildcard": [{
39+ "expr": "toJson(input.*.foo)",
40+ "result": {
41+ "kind": "String",
42+ "value": "[\n 32,\n 42,\n -10,\n 0,\n 2,\n 17\n]"
43+ },
44+ "contexts": {
45+ "input": {
46+ "test": { "foo": 32 },
47+ "test2": { "foo": 42 },
48+ "test3": { "foo": -10 },
49+ "test4": { "foo": 0 },
50+ "test5": { "foo": 2 },
51+ "test6": { "foo": 17 }
52+ }
53+ }
54+ }],
55+ "unknown context": [
56+ {
57+ "expr": "nosuchcontext.foo",
58+ "err": {
59+ "kind": "parsing",
60+ "value": "Unrecognized named-value: 'nosuchcontext'. Located at position 1 within expression: nosuchcontext.foo"
61+ }
62+ }
63+ ]
64+}
A expr/testdata/languageservices/case.json +157 -0
@@ -0,0 +1,157 @@
1+{
2+ "case": [
3+ {
4+ "expr": "case(true, 'first', 'default')",
5+ "result": { "kind": "String", "value": "first" }
6+ },
7+ {
8+ "expr": "case(false, 'first', 'default')",
9+ "result": { "kind": "String", "value": "default" }
10+ },
11+ {
12+ "expr": "case(true, 'first', false, 'second', 'default')",
13+ "result": { "kind": "String", "value": "first" }
14+ },
15+ {
16+ "expr": "case(false, 'first', true, 'second', 'default')",
17+ "result": { "kind": "String", "value": "second" }
18+ },
19+ {
20+ "expr": "case(false, 'first', false, 'second', 'default')",
21+ "result": { "kind": "String", "value": "default" }
22+ },
23+ {
24+ "expr": "case(1 == 1, 'equal', 'not equal')",
25+ "result": { "kind": "String", "value": "equal" }
26+ },
27+ {
28+ "expr": "case(1 == 2, 'equal', 'not equal')",
29+ "result": { "kind": "String", "value": "not equal" }
30+ },
31+ {
32+ "expr": "case(github.ref == 'refs/heads/main', 'main', github.event_name == 'pull_request', 'pr', 'other')",
33+ "contexts": {
34+ "github": {
35+ "ref": "refs/heads/main",
36+ "event_name": "push"
37+ }
38+ },
39+ "result": { "kind": "String", "value": "main" }
40+ },
41+ {
42+ "expr": "case(github.ref == 'refs/heads/main', 'main', github.event_name == 'pull_request', 'pr', 'other')",
43+ "contexts": {
44+ "github": {
45+ "ref": "refs/heads/develop",
46+ "event_name": "pull_request"
47+ }
48+ },
49+ "result": { "kind": "String", "value": "pr" }
50+ },
51+ {
52+ "expr": "case(github.ref == 'refs/heads/main', 'main', github.event_name == 'pull_request', 'pr', 'other')",
53+ "contexts": {
54+ "github": {
55+ "ref": "refs/heads/develop",
56+ "event_name": "push"
57+ }
58+ },
59+ "result": { "kind": "String", "value": "other" }
60+ },
61+ {
62+ "expr": "case(true, 123, 456)",
63+ "result": { "kind": "Number", "value": 123 }
64+ },
65+ {
66+ "expr": "case(false, 123, 456)",
67+ "result": { "kind": "Number", "value": 456 }
68+ },
69+ {
70+ "expr": "case(github.event == 'pull_request', 0, 1)",
71+ "contexts": {
72+ "github": {
73+ "event": "pull_request"
74+ }
75+ },
76+ "result": { "kind": "Number", "value": 0 }
77+ },
78+ {
79+ "expr": "case(false, 0, 1)",
80+ "result": { "kind": "Number", "value": 1 }
81+ },
82+ {
83+ "expr": "case(true, false, true)",
84+ "result": { "kind": "Boolean", "value": false }
85+ },
86+ {
87+ "expr": "case(false, false, true)",
88+ "result": { "kind": "Boolean", "value": true }
89+ },
90+ {
91+ "expr": "case(true, '', 'default')",
92+ "result": { "kind": "String", "value": "" }
93+ },
94+ {
95+ "expr": "case(false, 'first', '')",
96+ "result": { "kind": "String", "value": "" }
97+ },
98+ {
99+ "expr": "case(true, fromJSON('[1,2,3]'), 'default')",
100+ "result": { "kind": "Array", "value": [1, 2, 3] }
101+ },
102+ {
103+ "expr": "case(true, fromJSON('{\"key\":\"value\"}'), 'default')",
104+ "result": { "kind": "Object", "value": { "key": "value" } }
105+ },
106+ {
107+ "expr": "case(false, 'first', false, 'second', false, 'third', false, 'fourth', 'default')",
108+ "result": { "kind": "String", "value": "default" }
109+ },
110+ {
111+ "expr": "case(false, 'first', false, 'second', true, 'third', false, 'fourth', 'default')",
112+ "result": { "kind": "String", "value": "third" }
113+ },
114+ {
115+ "expr": "case('not a boolean', 'first', 'default')",
116+ "err": {
117+ "kind": "evaluation",
118+ "value": "case predicate must evaluate to a boolean value"
119+ }
120+ },
121+ {
122+ "expr": "case(1, 'first', 'default')",
123+ "err": {
124+ "kind": "evaluation",
125+ "value": "case predicate must evaluate to a boolean value"
126+ }
127+ },
128+ {
129+ "expr": "case(null, 'first', 'default')",
130+ "err": {
131+ "kind": "evaluation",
132+ "value": "case predicate must evaluate to a boolean value"
133+ }
134+ },
135+ {
136+ "expr": "case(fromJSON('[]'), 'first', 'default')",
137+ "err": {
138+ "kind": "evaluation",
139+ "value": "case predicate must evaluate to a boolean value"
140+ }
141+ },
142+ {
143+ "expr": "case(fromJSON('{}'), 'first', 'default')",
144+ "err": {
145+ "kind": "evaluation",
146+ "value": "case predicate must evaluate to a boolean value"
147+ }
148+ },
149+ {
150+ "expr": "case(true, 'first', false, 'second')",
151+ "err": {
152+ "kind": "parsing",
153+ "value": "Even number of parameters supplied, requires an odd number of parameters: 'case'. Located at position 1 within expression: case(true, 'first', false, 'second')"
154+ }
155+ }
156+ ]
157+}
A expr/testdata/languageservices/coerce_boolean.json +4 -0
@@ -0,0 +1,4 @@
1+{
2+ "refer ./operator_not.json": [
3+ ]
4+}⏎̸
A expr/testdata/languageservices/coerce_number.json +124 -0
@@ -0,0 +1,124 @@
1+{
2+ "null": [
3+ {
4+ "expr": "null == 0",
5+ "result": { "kind": "Boolean", "value": true }
6+ }
7+ ],
8+ "boolean": [
9+ {
10+ "expr": "true == 1",
11+ "result": { "kind": "Boolean", "value": true }
12+ },
13+ {
14+ "expr": "false == 0",
15+ "result": { "kind": "Boolean", "value": true }
16+ }
17+ ],
18+ "string": [
19+ {
20+ "expr": "'' == 0",
21+ "result": { "kind": "Boolean", "value": true }
22+ },
23+ {
24+ "expr": "' 123456.789 ' == 123456.789",
25+ "result": { "kind": "Boolean", "value": true }
26+ },
27+ {
28+ "expr": "' +123456.789 ' == 123456.789",
29+ "result": { "kind": "Boolean", "value": true }
30+ },
31+ {
32+ "expr": "' -123456.789 ' == -123456.789",
33+ "result": { "kind": "Boolean", "value": true }
34+ },
35+ {
36+ "expr": "' 0xff ' == 255",
37+ "result": { "kind": "Boolean", "value": true }
38+ },
39+ {
40+ "expr": "' 0xFF ' == 255",
41+ "result": { "kind": "Boolean", "value": true }
42+ },
43+ {
44+ "expr": "' 0o10 ' == 8",
45+ "result": { "kind": "Boolean", "value": true }
46+ },
47+ {
48+ "expr": "' Infinity ' == Infinity",
49+ "result": { "kind": "Boolean", "value": true }
50+ },
51+ {
52+ "expr": "' -Infinity ' == -Infinity",
53+ "result": { "kind": "Boolean", "value": true }
54+ },
55+ {
56+ "expr": "' NaN ' != NaN",
57+ "result": { "kind": "Boolean", "value": true }
58+ },
59+ {
60+ "expr": "' NaN ' == NaN",
61+ "result": { "kind": "Boolean", "value": false }
62+ },
63+ {
64+ "expr": "' NaN ' > NaN",
65+ "result": { "kind": "Boolean", "value": false }
66+ },
67+ {
68+ "expr": "' NaN ' < NaN",
69+ "result": { "kind": "Boolean", "value": false }
70+ },
71+ {
72+ "expr": "' abc ' != NaN",
73+ "result": { "kind": "Boolean", "value": true }
74+ },
75+ {
76+ "expr": "' abc ' == NaN",
77+ "result": { "kind": "Boolean", "value": false }
78+ },
79+ {
80+ "expr": "' abc ' > NaN",
81+ "result": { "kind": "Boolean", "value": false }
82+ },
83+ {
84+ "expr": "' abc ' < NaN",
85+ "result": { "kind": "Boolean", "value": false }
86+ }
87+ ],
88+ "array": [
89+ {
90+ "expr": "fromjson('[]') != NaN",
91+ "result": { "kind": "Boolean", "value": true }
92+ },
93+ {
94+ "expr": "fromjson('[]') == NaN",
95+ "result": { "kind": "Boolean", "value": false }
96+ },
97+ {
98+ "expr": "fromjson('[]') > NaN",
99+ "result": { "kind": "Boolean", "value": false }
100+ },
101+ {
102+ "expr": "fromjson('[]') < NaN",
103+ "result": { "kind": "Boolean", "value": false }
104+ }
105+ ],
106+ "object": [
107+ {
108+ "expr": "fromjson('[]') != NaN",
109+ "result": { "kind": "Boolean", "value": true }
110+ },
111+ {
112+ "expr": "fromjson('[]') == NaN",
113+ "result": { "kind": "Boolean", "value": false }
114+ },
115+ {
116+ "expr": "fromjson('[]') > NaN",
117+ "result": { "kind": "Boolean", "value": false }
118+ },
119+ {
120+ "expr": "fromjson('[]') < NaN",
121+ "result": { "kind": "Boolean", "value": false }
122+ }
123+ ]
124+}⏎̸
A expr/testdata/languageservices/coerce_string.json +178 -0
@@ -0,0 +1,178 @@
1+{
2+ "null": [
3+ {
4+ "expr": "format('{0}', null)",
5+ "result": { "kind": "String", "value": "" }
6+ }
7+ ],
8+ "boolean": [
9+ {
10+ "expr": "format('{0}', true)",
11+ "result": { "kind": "String", "value": "true" }
12+ },
13+ {
14+ "expr": "format('{0}', false)",
15+ "result": { "kind": "String", "value": "false" }
16+ }
17+ ],
18+ "number": [
19+ {
20+ "expr": "format('{0}', 1)",
21+ "result": { "kind": "String", "value": "1" }
22+ },
23+ {
24+ "expr": "format('{0}', .5)",
25+ "result": { "kind": "String", "value": "0.5" }
26+ },
27+ {
28+ "expr": "format('{0}', 0.5)",
29+ "result": { "kind": "String", "value": "0.5" }
30+ },
31+ {
32+ "expr": "format('{0}', 2)",
33+ "result": { "kind": "String", "value": "2" }
34+ },
35+ {
36+ "expr": "format('{0}', -1)",
37+ "result": { "kind": "String", "value": "-1" }
38+ },
39+ {
40+ "expr": "format('{0}', -.5)",
41+ "result": { "kind": "String", "value": "-0.5" }
42+ },
43+ {
44+ "expr": "format('{0}', -0.5)",
45+ "result": { "kind": "String", "value": "-0.5" }
46+ },
47+ {
48+ "expr": "format('{0}', -2.0)",
49+ "result": { "kind": "String", "value": "-2" }
50+ },
51+ {
52+ "expr": "format('{0}', 0)",
53+ "result": { "kind": "String", "value": "0" }
54+ },
55+ {
56+ "expr": "format('{0}', 0.0)",
57+ "result": { "kind": "String", "value": "0" }
58+ },
59+ {
60+ "expr": "format('{0}', -0)",
61+ "result": { "kind": "String", "value": "0" }
62+ },
63+ {
64+ "expr": "format('{0}', -0.0)",
65+ "result": { "kind": "String", "value": "0" }
66+ },
67+ {
68+ "expr": "format('{0}', 123456.789)",
69+ "result": { "kind": "String", "value": "123456.789" }
70+ },
71+ {
72+ "expr": "format('{0}', +123456.789)",
73+ "result": { "kind": "String", "value": "123456.789" }
74+ },
75+ {
76+ "expr": "format('{0}', -123456.789)",
77+ "result": { "kind": "String", "value": "-123456.789" }
78+ },
79+ {
80+ "expr": "format('{0}', 0.84551240822557006)",
81+ "result": { "kind": "String", "value": "0.84551240822557" }
82+ },
83+ {
84+ "expr": "format('{0}', 1.9)",
85+ "result": { "kind": "String", "value": "1.9" }
86+ },
87+ {
88+ "expr": "format('{0}', 1234.56)",
89+ "result": { "kind": "String", "value": "1234.56" }
90+ },
91+ {
92+ "expr": "format('{0}', 0xff)",
93+ "result": { "kind": "String", "value": "255" }
94+ },
95+ {
96+ "expr": "format('{0}', 0xFF)",
97+ "result": { "kind": "String", "value": "255" }
98+ },
99+ {
100+ "expr": "format('{0}', 0o10)",
101+ "result": { "kind": "String", "value": "8" }
102+ },
103+ {
104+ "expr": "format('{0}', 1.2e2)",
105+ "result": { "kind": "String", "value": "120" }
106+ },
107+ {
108+ "expr": "format('{0}', 1.2E2)",
109+ "result": { "kind": "String", "value": "120" }
110+ },
111+ {
112+ "expr": "format('{0}', 1.2e+2)",
113+ "result": { "kind": "String", "value": "120" }
114+ },
115+ {
116+ "expr": "format('{0}', 1.2E+2)",
117+ "result": { "kind": "String", "value": "120" }
118+ },
119+ {
120+ "expr": "format('{0}', 1.2e-2)",
121+ "result": { "kind": "String", "value": "0.012" }
122+ },
123+ {
124+ "expr": "format('{0}', 1.2E-2)",
125+ "result": { "kind": "String", "value": "0.012" }
126+ },
127+ {
128+ "expr": "format('{0}', Infinity)",
129+ "result": { "kind": "String", "value": "Infinity" }
130+ },
131+ {
132+ "expr": "format('{0}', -Infinity)",
133+ "result": { "kind": "String", "value": "-Infinity" }
134+ },
135+ {
136+ "expr": "format('{0}', NaN)",
137+ "result": { "kind": "String", "value": "NaN" }
138+ }
139+ ],
140+ "string": [
141+ {
142+ "expr": "format('{0}', 'string value')",
143+ "result": { "kind": "String", "value": "string value" }
144+ }
145+ ],
146+ "array": [
147+ {
148+ "expr": "format('{0}', context)",
149+ "contexts": {
150+ "context": []
151+ },
152+ "result": { "kind": "String", "value": "Array" }
153+ },
154+ {
155+ "expr": "format('{0}', context)",
156+ "contexts": {
157+ "context": [1, 2, 3]
158+ },
159+ "result": { "kind": "String", "value": "Array" }
160+ }
161+ ],
162+ "object": [
163+ {
164+ "expr": "format('{0}', context)",
165+ "contexts": {
166+ "context": {}
167+ },
168+ "result": { "kind": "String", "value": "Object" }
169+ },
170+ {
171+ "expr": "format('{0}', context)",
172+ "contexts": {
173+ "context": { "a": 1, "b": 2, "c": 3 }
174+ },
175+ "result": { "kind": "String", "value": "Object" }
176+ }
177+ ]
178+}⏎̸
A expr/testdata/languageservices/contains.json +122 -0
@@ -0,0 +1,122 @@
1+{
2+ "string": [
3+ {
4+ "expr": "contains('abcdef', 'cde')",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ {
8+ "expr": "contains('abCdEf', 'cDe')",
9+ "result": { "kind": "Boolean", "value": true }
10+ },
11+ {
12+ "expr": "contains('abcdef', '')",
13+ "result": { "kind": "Boolean", "value": true }
14+ },
15+ {
16+ "expr": "contains('1234', 23)",
17+ "result": { "kind": "Boolean", "value": true }
18+ },
19+ {
20+ "expr": "contains('true', true)",
21+ "result": { "kind": "Boolean", "value": true }
22+ },
23+ {
24+ "expr": "contains('false', false)",
25+ "result": { "kind": "Boolean", "value": true }
26+ },
27+ {
28+ "expr": "contains('asdf', null)",
29+ "result": { "kind": "Boolean", "value": true }
30+ },
31+ {
32+ "expr": "contains('true', false)",
33+ "result": { "kind": "Boolean", "value": false }
34+ }
35+ ],
36+
37+ "array": [
38+ {
39+ "expr": "contains(test, 'hello')",
40+ "result": { "kind": "Boolean", "value": true },
41+ "contexts": { "test": ["hello"] }
42+ },
43+ {
44+ "expr": "contains(test, 'world')",
45+ "result": { "kind": "Boolean", "value": true },
46+ "contexts": { "test": ["hello", "world"] }
47+ },
48+ {
49+ "expr": "contains(test, 'asdf')",
50+ "result": { "kind": "Boolean", "value": false },
51+ "contexts": { "test": ["hello"] }
52+ },
53+ {
54+ "expr": "contains(test, 123)",
55+ "result": { "kind": "Boolean", "value": true },
56+ "contexts": { "test": [123.0] }
57+ },
58+ {
59+ "expr": "contains(test, 123)",
60+ "result": { "kind": "Boolean", "value": true },
61+ "contexts": { "test": [123.0] }
62+ },
63+ {
64+ "expr": "contains(test, null)",
65+ "result": { "kind": "Boolean", "value": true },
66+ "contexts": { "test": [""] }
67+ },
68+ {
69+ "expr": "contains(test, false)",
70+ "result": { "kind": "Boolean", "value": true },
71+ "contexts": { "test": [""] }
72+ },
73+ {
74+ "expr": "contains(test, false)",
75+ "result": { "kind": "Boolean", "value": true },
76+ "contexts": { "test": [0] }
77+ }
78+ ],
79+
80+ "filtered_array": [
81+ {
82+ "expr": "contains(test.*, 'hello')",
83+ "result": { "kind": "Boolean", "value": true },
84+ "contexts": { "test": ["hello"] }
85+ },
86+ {
87+ "expr": "contains(test.*, 'world')",
88+ "result": { "kind": "Boolean", "value": true },
89+ "contexts": { "test": ["hello", "world"] }
90+ },
91+ {
92+ "expr": "contains(test.*, 'asdf')",
93+ "result": { "kind": "Boolean", "value": false },
94+ "contexts": { "test": ["hello"] }
95+ },
96+ {
97+ "expr": "contains(test.*, 123)",
98+ "result": { "kind": "Boolean", "value": true },
99+ "contexts": { "test": [123.0] }
100+ },
101+ {
102+ "expr": "contains(test.*, 123)",
103+ "result": { "kind": "Boolean", "value": true },
104+ "contexts": { "test": [123.0] }
105+ },
106+ {
107+ "expr": "contains(test.*, null)",
108+ "result": { "kind": "Boolean", "value": true },
109+ "contexts": { "test": [""] }
110+ },
111+ {
112+ "expr": "contains(test.*, false)",
113+ "result": { "kind": "Boolean", "value": true },
114+ "contexts": { "test": [""] }
115+ },
116+ {
117+ "expr": "contains(test.*, false)",
118+ "result": { "kind": "Boolean", "value": true },
119+ "contexts": { "test": [0] }
120+ }
121+ ]
122+}
A expr/testdata/languageservices/endsWith.json +129 -0
@@ -0,0 +1,129 @@
1+{
2+ "basics": [
3+ {
4+ "expr": "endsWith('abcdef', 'def')",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ {
8+ "expr": "endsWith('abcdef', 'de')",
9+ "result": { "kind": "Boolean", "value": false }
10+ },
11+ {
12+ "expr": "endsWith('abcdef', '')",
13+ "result": { "kind": "Boolean", "value": true }
14+ },
15+ {
16+ "expr": "endsWith('1234', 34)",
17+ "result": { "kind": "Boolean", "value": true }
18+ },
19+ {
20+ "expr": "endsWith('true', true)",
21+ "result": { "kind": "Boolean", "value": true }
22+ },
23+ {
24+ "expr": "endsWith('false', false)",
25+ "result": { "kind": "Boolean", "value": true }
26+ },
27+ {
28+ "expr": "endsWith('asdf', null)",
29+ "result": { "kind": "Boolean", "value": true }
30+ },
31+ {
32+ "expr": "endsWith('1234', 23)",
33+ "result": { "kind": "Boolean", "value": false }
34+ },
35+ {
36+ "expr": "endsWith('true', false)",
37+ "result": { "kind": "Boolean", "value": false }
38+ }
39+ ],
40+
41+ "case-insensitive-a-thru-z": [
42+ {
43+ "expr": "endsWith('asdf_ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')",
44+ "result": { "kind": "Boolean", "value": true }
45+ },
46+ {
47+ "expr": "endsWith('asdf_abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')",
48+ "result": { "kind": "Boolean", "value": true }
49+ }
50+ ],
51+
52+ "case-insensitive-ς-(final-lowercase-sigma)-Σ-(capital-sigma)-σ-(non-final-sigma)": [
53+ {
54+ "expr": "endsWith('asdf_ς', 'Σ')",
55+ "result": { "kind": "Boolean", "value": true }
56+ },
57+ {
58+ "expr": "endsWith('asdf_ς', 'σ')",
59+ "result": { "kind": "Boolean", "value": true }
60+ },
61+ {
62+ "expr": "endsWith('asdf_Σ', 'ς')",
63+ "result": { "kind": "Boolean", "value": true }
64+ },
65+ {
66+ "expr": "endsWith('asdf_Σ', 'σ')",
67+ "result": { "kind": "Boolean", "value": true }
68+ },
69+ {
70+ "expr": "endsWith('asdf_σ', 'ς')",
71+ "result": { "kind": "Boolean", "value": true }
72+ },
73+ {
74+ "expr": "endsWith('asdf_σ', 'Σ')",
75+ "result": { "kind": "Boolean", "value": true }
76+ }
77+ ],
78+
79+ "case-insensitive-ü-Ü": [
80+ {
81+ "expr": "endsWith('asdf_ü', 'Ü')",
82+ "result": { "kind": "Boolean", "value": true }
83+ },
84+ {
85+ "expr": "endsWith('asdf_Ü', 'ü')",
86+ "result": { "kind": "Boolean", "value": true }
87+ }
88+ ],
89+
90+ "case-insensitive-ç-Ç": [
91+ {
92+ "expr": "endsWith('asdf_ç', 'Ç')",
93+ "result": { "kind": "Boolean", "value": true }
94+ },
95+ {
96+ "expr": "endsWith('asdf_Ç', 'ç')",
97+ "result": { "kind": "Boolean", "value": true }
98+ }
99+ ],
100+
101+ "case-sensitive-i-İ": [
102+ {
103+ "expr": "endsWith('asdf_i', 'İ')",
104+ "result": { "kind": "Boolean", "value": false }
105+ },
106+ {
107+ "expr": "endsWith('asdf_İ', 'i')",
108+ "result": { "kind": "Boolean", "value": false }
109+ }
110+ ],
111+
112+ "case-sensitive-ı-I": [
113+ {
114+ "expr": "endsWith('asdf_ı', 'I')",
115+ "result": { "kind": "Boolean", "value": false }
116+ },
117+ {
118+ "expr": "endsWith('asdf_I', 'ı')",
119+ "result": { "kind": "Boolean", "value": false }
120+ }
121+ ],
122+
123+ "cyrillic-letters": [
124+ {
125+ "expr": "endsWith('asdf_АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЭЮЯ', 'абвгдежзийклмнопрстуфхцчшщьэюя')",
126+ "result": { "kind": "Boolean", "value": true }
127+ }
128+ ]
129+}
A expr/testdata/languageservices/format.json +207 -0
@@ -0,0 +1,207 @@
1+{
2+ "format": [
3+ {
4+ "expr": "format(null)",
5+ "result": { "kind": "String", "value": "" }
6+ },
7+ {
8+ "expr": "format(null, 'some arg')",
9+ "result": { "kind": "String", "value": "" }
10+ },
11+ {
12+ "expr": "format('')",
13+ "result": { "kind": "String", "value": "" }
14+ },
15+ {
16+ "expr": "format('', 'some arg')",
17+ "result": { "kind": "String", "value": "" }
18+ },
19+ {
20+ "expr": "format('123{0}456', 'abc')",
21+ "result": { "kind": "String", "value": "123abc456" }
22+ },
23+ {
24+ "expr": "format('123{0}456{0}789', 'abc')",
25+ "result": { "kind": "String", "value": "123abc456abc789" }
26+ },
27+ {
28+ "expr": "format('123{0}456{1}789', 'abc', 'def')",
29+ "result": { "kind": "String", "value": "123abc456def789" }
30+ },
31+ {
32+ "expr": "format('{0}123', 'abc')",
33+ "result": { "kind": "String", "value": "abc123" }
34+ },
35+ {
36+ "expr": "format('123{0}', 'abc')",
37+ "result": { "kind": "String", "value": "123abc" }
38+ },
39+ {
40+ "expr": "format('123{0}{1}456', 'abc', 'def')",
41+ "result": { "kind": "String", "value": "123abcdef456" }
42+ },
43+ {
44+ "expr": "format('{{0}}', 'abc')",
45+ "result": { "kind": "String", "value": "{0}" }
46+ },
47+ {
48+ "expr": "format('{{{{0}}}}', 'abc')",
49+ "result": { "kind": "String", "value": "{{0}}" }
50+ },
51+ {
52+ "expr": "format('}}', 'abc')",
53+ "result": { "kind": "String", "value": "}" }
54+ },
55+ {
56+ "expr": "format('{{', 'abc')",
57+ "result": { "kind": "String", "value": "{" }
58+ },
59+ {
60+ "expr": "format('}}{{', 'abc')",
61+ "result": { "kind": "String", "value": "}{" }
62+ },
63+ {
64+ "expr": "format('}}{{}}', 'abc')",
65+ "result": { "kind": "String", "value": "}{}" }
66+ },
67+ {
68+ "expr": "format('{0}', Infinity)",
69+ "result": { "kind": "String", "value": "Infinity" }
70+ },
71+ {
72+ "expr": "format('{0}', -Infinity)",
73+ "result": { "kind": "String", "value": "-Infinity" }
74+ },
75+ {
76+ "expr": "format('{0}', NaN)",
77+ "result": { "kind": "String", "value": "NaN" }
78+ },
79+ {
80+ "expr": "format('{0}', -0)",
81+ "result": { "kind": "String", "value": "0" }
82+ },
83+ {
84+ "expr": "format('{0}', -0.0)",
85+ "result": { "kind": "String", "value": "0" }
86+ },
87+ {
88+ "expr": "format('{0')",
89+ "err": {
90+ "kind": "evaluation",
91+ "value": "The following format string is invalid: {0"
92+ }
93+ },
94+ {
95+ "expr": "format('{0', '')",
96+ "err": {
97+ "kind": "evaluation",
98+ "value": "The following format string is invalid: {0"
99+ }
100+ },
101+ {
102+ "expr": "format('{0}}', '')",
103+ "err": {
104+ "kind": "evaluation",
105+ "value": "The following format string is invalid: {0}}"
106+ }
107+ },
108+ {
109+ "expr": "format('{0}}}}', '')",
110+ "err": {
111+ "kind": "evaluation",
112+ "value": "The following format string is invalid: {0}}}}"
113+ }
114+ },
115+ {
116+ "expr": "format('0}')",
117+ "err": {
118+ "kind": "evaluation",
119+ "value": "The following format string is invalid: 0}"
120+ }
121+ },
122+ {
123+ "expr": "format('0}', '')",
124+ "err": {
125+ "kind": "evaluation",
126+ "value": "The following format string is invalid: 0}"
127+ }
128+ },
129+ {
130+ "expr": "format('{{0}')",
131+ "err": {
132+ "kind": "evaluation",
133+ "value": "The following format string is invalid: {{0}"
134+ }
135+ },
136+ {
137+ "expr": "format('{{0}', '')",
138+ "err": {
139+ "kind": "evaluation",
140+ "value": "The following format string is invalid: {{0}"
141+ }
142+ },
143+ {
144+ "expr": "format('{{{{0}')",
145+ "err": {
146+ "kind": "evaluation",
147+ "value": "The following format string is invalid: {{{{0}"
148+ }
149+ },
150+ {
151+ "expr": "format('{{{{0}', '')",
152+ "err": {
153+ "kind": "evaluation",
154+ "value": "The following format string is invalid: {{{{0}"
155+ }
156+ },
157+ {
158+ "expr": "format('}0{')",
159+ "err": {
160+ "kind": "evaluation",
161+ "value": "The following format string is invalid: }0{"
162+ }
163+ },
164+ {
165+ "expr": "format('}0{', '')",
166+ "err": {
167+ "kind": "evaluation",
168+ "value": "The following format string is invalid: }0{"
169+ }
170+ },
171+ {
172+ "expr": "format('}{0}')",
173+ "err": {
174+ "kind": "evaluation",
175+ "value": "The following format string is invalid: }{0}"
176+ }
177+ },
178+ {
179+ "expr": "format('}{0}', '')",
180+ "err": {
181+ "kind": "evaluation",
182+ "value": "The following format string is invalid: }{0}"
183+ }
184+ },
185+ {
186+ "expr": "format('{0}{', '')",
187+ "err": {
188+ "kind": "evaluation",
189+ "value": "The following format string is invalid: {0}{"
190+ }
191+ },
192+ {
193+ "expr": "format('{0}')",
194+ "err": {
195+ "kind": "evaluation",
196+ "value": "The following format string references more arguments than were supplied: {0}"
197+ }
198+ },
199+ {
200+ "expr": "format('{0}{1}', 'abc')",
201+ "err": {
202+ "kind": "evaluation",
203+ "value": "The following format string references more arguments than were supplied: {0}{1}"
204+ }
205+ }
206+ ]
207+}
A expr/testdata/languageservices/fromJSON.json +67 -0
@@ -0,0 +1,67 @@
1+{
2+ "basics": [
3+ { "expr": "fromJSON('null')", "result": { "kind": "Null", "value": null } },
4+ { "expr": "fromJSON('true')", "result": { "kind": "Boolean", "value": true } },
5+ { "expr": "fromJSON('false')", "result": { "kind": "Boolean", "value": false } },
6+ { "expr": "fromJSON('0')", "result": { "kind": "Number", "value": 0 } },
7+ { "expr": "fromJSON('-0')", "result": { "kind": "Number", "value": -0 } },
8+ { "expr": "fromJSON('123456789')", "result": { "kind": "Number", "value": 123456789 } },
9+ { "expr": "fromJSON('-123456789')", "result": { "kind": "Number", "value": -123456789 } },
10+ { "expr": "fromJSON('1234.5')", "result": { "kind": "Number", "value": 1234.5 } },
11+ { "expr": "fromJSON('-1234.5')", "result": { "kind": "Number", "value": -1234.5 } },
12+ { "expr": "fromJSON('\"\"')", "result": { "kind": "String", "value": "" } },
13+ { "expr": "fromJSON('\"abc\"')", "result": { "kind": "String", "value": "abc" } },
14+ { "expr": "fromJSON('\"abc''def\"')", "result": { "kind": "String", "value": "abc'def" } },
15+ { "expr": "fromJSON('\"abc\\\\\\\"def\"')", "result": { "kind": "String", "value": "abc\\\"def" } }
16+ ],
17+ "array": [
18+ { "expr": "fromJSON('[]')", "result": { "kind": "Array", "value": [] } },
19+ { "expr": "fromJSON('[1, 2, 3]')", "result": { "kind": "Array", "value": [1, 2, 3] } },
20+ {
21+ "expr": "fromJSON('[[1, 2, 3], [\"abc\",\"def\",\"ghi\"], [true, false, null, [], {}]]')",
22+ "result": {
23+ "kind": "Array",
24+ "value": [[1, 2, 3], ["abc", "def", "ghi"], [true, false, null, [], {}]]
25+ }
26+ }
27+ ],
28+ "object": [
29+ { "expr": "fromJSON('{}')", "result": { "kind": "Object", "value": {} } },
30+ {
31+ "expr": "fromJSON('{\"one\": \"value one\", \"two\": \"value two\", \"three\": \"value three\"}')",
32+ "result": {
33+ "kind": "Object",
34+ "value": {
35+ "one": "value one",
36+ "two": "value two",
37+ "three": "value three"
38+ }
39+ }
40+ },
41+ {
42+ "expr": "fromJSON('{\"nested-one\": {\"one\": 1,\"two\": 2,\"three\": 3},\"nested-two\": {\"string one\": \"value one\",\"string two\": \"value two\",\"string three\": \"value three\"},\"nested-three\": {\"true\": true,\"false\": false,\"null\": null,\"array\": [],\"object\": {}}\n}')",
43+ "result": {
44+ "kind": "Object",
45+ "value": {
46+ "nested-one": {
47+ "one": 1,
48+ "two": 2,
49+ "three": 3
50+ },
51+ "nested-two": {
52+ "string one": "value one",
53+ "string two": "value two",
54+ "string three": "value three"
55+ },
56+ "nested-three": {
57+ "true": true,
58+ "false": false,
59+ "null": null,
60+ "array": [],
61+ "object": {}
62+ }
63+ }
64+ }
65+ }
66+ ]
67+}⏎̸
A expr/testdata/languageservices/join.json +113 -0
@@ -0,0 +1,113 @@
1+{
2+ "join": [
3+ {
4+ "expr": "join(github.event.issue.labels.*.name, ', ')",
5+ "contexts": {
6+ "github": {
7+ "event": {
8+ "issue": {
9+ "labels": [
10+ {
11+ "name": "bug"
12+ },
13+ {
14+ "name": "enhancement"
15+ },
16+ {
17+ "name": "help wanted"
18+ }
19+ ]
20+ }
21+ }
22+ }
23+ },
24+ "result": { "kind": "String", "value": "bug, enhancement, help wanted" }
25+ },
26+ {
27+ "expr": "join(null)",
28+ "result": { "kind": "String", "value": "" }
29+ },
30+ {
31+ "expr": "join(true)",
32+ "result": { "kind": "String", "value": "true" }
33+ },
34+ {
35+ "expr": "join(123.456)",
36+ "result": { "kind": "String", "value": "123.456" }
37+ },
38+ {
39+ "expr": "join('abc')",
40+ "result": { "kind": "String", "value": "abc" }
41+ },
42+ {
43+ "expr": "join(myArray)",
44+ "contexts": { "myArray": [] },
45+ "result": { "kind": "String", "value": "" }
46+ },
47+ {
48+ "expr": "join(myObject)",
49+ "contexts": { "myObject": {} },
50+ "result": { "kind": "String", "value": "" }
51+ },
52+ {
53+ "expr": "join(myObject)",
54+ "contexts": { "myObject": { "key1": "value1" } },
55+ "result": { "kind": "String", "value": "" }
56+ },
57+ {
58+ "expr": "join(myArray)",
59+ "contexts": { "myArray": [ null, true, 123.456, "abc", [], ["def"], {}, { "key1": "value1" } ] },
60+ "result": { "kind": "String", "value": ",true,123.456,abc,Array,Array,Object,Object" }
61+ },
62+ {
63+ "expr": "join(myArray, null)",
64+ "contexts": { "myArray": [ "_ITEM-1_", "_ITEM-2_", "_ITEM-3_" ] },
65+ "result": { "kind": "String", "value": "_ITEM-1__ITEM-2__ITEM-3_" }
66+ },
67+ {
68+ "expr": "join(myArray, true)",
69+ "contexts": { "myArray": [ "_ITEM-1_", "_ITEM-2_", "_ITEM-3_" ] },
70+ "result": { "kind": "String", "value": "_ITEM-1_true_ITEM-2_true_ITEM-3_" }
71+ },
72+ {
73+ "expr": "join(myArray, 123.456)",
74+ "contexts": { "myArray": [ "_ITEM-1_", "_ITEM-2_", "_ITEM-3_" ] },
75+ "result": { "kind": "String", "value": "_ITEM-1_123.456_ITEM-2_123.456_ITEM-3_" }
76+ },
77+ {
78+ "expr": "join(myArray, ' | ')",
79+ "contexts": { "myArray": [ "_ITEM-1_", "_ITEM-2_", "_ITEM-3_" ] },
80+ "result": { "kind": "String", "value": "_ITEM-1_ | _ITEM-2_ | _ITEM-3_" }
81+ },
82+ {
83+ "expr": "join(myArray, mySeparator)",
84+ "contexts": {
85+ "myArray": [ "_ITEM-1_", "_ITEM-2_", "_ITEM-3_" ],
86+ "mySeparator": []
87+ },
88+ "result": { "kind": "String", "value": "_ITEM-1_,_ITEM-2_,_ITEM-3_" }
89+ },
90+ {
91+ "expr": "join(myArray, mySeparator)",
92+ "contexts": {
93+ "myArray": [ "_ITEM-1_", "_ITEM-2_", "_ITEM-3_" ],
94+ "mySeparator": {}
95+ },
96+ "result": { "kind": "String", "value": "_ITEM-1_,_ITEM-2_,_ITEM-3_" }
97+ },
98+ {
99+ "expr": "join()",
100+ "err": {
101+ "kind": "parsing",
102+ "value": "Too few parameters supplied: "
103+ }
104+ },
105+ {
106+ "expr": "join(1, 2, 3)",
107+ "err": {
108+ "kind": "parsing",
109+ "value": "Too many parameters supplied: "
110+ }
111+ }
112+ ]
113+}
A expr/testdata/languageservices/number.json +51 -0
@@ -0,0 +1,51 @@
1+{
2+ "number": [
3+ { "expr": "1", "result": { "kind": "Number", "value": 1.0 } },
4+ { "expr": ".5", "result": { "kind": "Number", "value": 0.5 } },
5+ { "expr": "0.5", "result": { "kind": "Number", "value": 0.5 } },
6+ { "expr": "2", "result": { "kind": "Number", "value": 2.0 } },
7+ { "expr": "-1", "result": { "kind": "Number", "value": -1.0 } },
8+ { "expr": "+1", "result": { "kind": "Number", "value": 1.0 } },
9+ { "expr": "-.5", "result": { "kind": "Number", "value": -0.5 } },
10+ { "expr": "-2", "result": { "kind": "Number", "value": -2.0 } },
11+ { "expr": "format('{0}', -Infinity)", "result": { "kind": "String", "value": "-Infinity" } },
12+ { "expr": "format('{0}', Infinity)", "result": { "kind": "String", "value": "Infinity" } },
13+ { "expr": "format('{0}', +Infinity)", "result": { "kind": "String", "value": "Infinity" } },
14+ { "expr": "format('{0}', NaN)", "result": { "kind": "String", "value": "NaN" } },
15+ { "expr": "0", "result": { "kind": "Number", "value": 0.0 } },
16+ { "expr": "0.0", "result": { "kind": "Number", "value": 0.0 } },
17+ { "expr": "-0", "result": { "kind": "Number", "value": -0.0 } },
18+ { "expr": "-0.0", "result": { "kind": "Number", "value": -0.0 } },
19+ { "expr": "0x0", "result": { "kind": "Number", "value": 0.0 } },
20+ { "expr": "0x00", "result": { "kind": "Number", "value": 0.0 } },
21+ { "expr": "0xf", "result": { "kind": "Number", "value": 15.0 } },
22+ { "expr": "0xfF", "result": { "kind": "Number", "value": 255.0 } },
23+ { "expr": "0xfFf", "result": { "kind": "Number", "value": 4095.0 } },
24+ { "expr": "0o0", "result": { "kind": "Number", "value": 0.0 } },
25+ { "expr": "0o7", "result": { "kind": "Number", "value": 7.0 } },
26+ { "expr": "0o77", "result": { "kind": "Number", "value": 63.0 } },
27+ { "expr": "0o777", "result": { "kind": "Number", "value": 511.0 } },
28+ { "expr": "1e1", "result": { "kind": "Number", "value": 10.0 } },
29+ { "expr": "1e2", "result": { "kind": "Number", "value": 100.0 } },
30+ { "expr": "1E1", "result": { "kind": "Number", "value": 10.0 } },
31+ { "expr": "1E+1", "result": { "kind": "Number", "value": 10.0 } },
32+ { "expr": "1e-1", "result": { "kind": "Number", "value": 0.1 } },
33+ { "expr": "1E-1", "result": { "kind": "Number", "value": 0.1 } },
34+ {
35+ "expr": "0x01p2",
36+ "err": { "kind": "lexing", "value": "Unexpected symbol: '0x01p2'. Located at position 1 within expression: 0x01p2" }
37+ },
38+ {
39+ "expr": "-Inf",
40+ "err": { "kind": "lexing", "value": "Unexpected symbol: '-Inf'. Located at position 1 within expression: -Inf" }
41+ },
42+ {
43+ "expr": "-0xFF",
44+ "err": { "kind": "lexing", "value": "Unexpected symbol: '-0xFF'. Located at position 1 within expression: -0xFF" }
45+ },
46+ {
47+ "expr": "0xFZ",
48+ "err": { "kind": "lexing", "value": "Unexpected symbol: '0xFZ'. Located at position 1 within expression: 0xFZ" }
49+ }
50+ ]
51+}
A expr/testdata/languageservices/op_and.json +51 -0
@@ -0,0 +1,51 @@
1+{
2+ "operator_and": [
3+ {
4+ "expr": "true && true && true",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ { "expr": "true && true", "result": { "kind": "Boolean", "value": true } },
8+ {
9+ "expr": "true && true && false",
10+ "result": { "kind": "Boolean", "value": false }
11+ },
12+ {
13+ "expr": "true && false && true",
14+ "result": { "kind": "Boolean", "value": false }
15+ },
16+ {
17+ "expr": "false && true",
18+ "result": { "kind": "Boolean", "value": false }
19+ },
20+ {
21+ "expr": "false && false",
22+ "result": { "kind": "Boolean", "value": false }
23+ },
24+ { "expr": "true && 1 && 2", "result": { "kind": "Number", "value": 2.0 } },
25+ { "expr": "true && 0 && 2", "result": { "kind": "Number", "value": 0.0 } },
26+ {
27+ "expr": "true && 'a' && 'b'",
28+ "result": { "kind": "String", "value": "b" }
29+ },
30+ {
31+ "expr": "true && '' && 'asdf'",
32+ "result": { "kind": "String", "value": "" }
33+ },
34+ { "expr": "null && true", "result": { "kind": "Null", "value": null } },
35+ {
36+ "expr": "test && 123",
37+ "contexts": { "test": {} },
38+ "result": { "kind": "Number", "value": 123.0 }
39+ },
40+ {
41+ "expr": "test && 456",
42+ "contexts": { "test": {} },
43+ "result": { "kind": "Number", "value": 456.0 }
44+ },
45+ {
46+ "expr": "test && 789",
47+ "contexts": { "test": [] },
48+ "result": { "kind": "Number", "value": 789.0 }
49+ }
50+ ]
51+}
A expr/testdata/languageservices/op_dot.json +202 -0
@@ -0,0 +1,202 @@
1+{
2+ "property-basics": [
3+ {
4+ "expr": "foo.bar",
5+ "contexts": {
6+ "foo": {
7+ "bar": "baz"
8+ }
9+ },
10+ "result": { "kind": "String", "value": "baz" }
11+ },
12+ {
13+ "expr": "foo.Bar",
14+ "contexts": {
15+ "foo": {
16+ "Bar": "baz"
17+ }
18+ },
19+ "result": { "kind": "String", "value": "baz" }
20+ },
21+ {
22+ "expr": "foo.b",
23+ "contexts": {
24+ "foo": {
25+ "b": "baz"
26+ }
27+ },
28+ "result": { "kind": "String", "value": "baz" }
29+ },
30+ {
31+ "expr": "foo._",
32+ "contexts": {
33+ "foo": {
34+ "_": "baz"
35+ }
36+ },
37+ "result": { "kind": "String", "value": "baz" }
38+ },
39+ {
40+ "expr": "foo._bar",
41+ "contexts": {
42+ "foo": {
43+ "_bar": "baz"
44+ }
45+ },
46+ "result": { "kind": "String", "value": "baz" }
47+ },
48+ {
49+ "expr": "foo.b_ar",
50+ "contexts": {
51+ "foo": {
52+ "b_ar": "baz"
53+ }
54+ },
55+ "result": { "kind": "String", "value": "baz" }
56+ },
57+ {
58+ "expr": "foo.b-ar",
59+ "contexts": {
60+ "foo": {
61+ "b-ar": "baz"
62+ }
63+ },
64+ "result": { "kind": "String", "value": "baz" }
65+ },
66+ {
67+ "expr": "fromJson('{\"one\": \"one val\"}').one",
68+ "result": { "kind": "String", "value": "one val" }
69+ },
70+ {
71+ "expr": "(fromJson('{\"one\": \"one val\"}')).one",
72+ "result": { "kind": "String", "value": "one val" }
73+ },
74+ {
75+ "expr": "foo[*]",
76+ "contexts": {
77+ "foo": {
78+ "one": "one val",
79+ "two": "two val"
80+ }
81+ },
82+ "result": { "kind": "Array", "value": ["one val", "two val"] }
83+ }
84+ ],
85+ "property-case-insensitive": [
86+ {
87+ "expr": "foo.bar",
88+ "contexts": {
89+ "foo": {
90+ "BAR": "baz"
91+ }
92+ },
93+ "result": { "kind": "String", "value": "baz" }
94+ },
95+ {
96+ "expr": "foo.BAR",
97+ "contexts": {
98+ "foo": {
99+ "bar": "baz"
100+ }
101+ },
102+ "result": { "kind": "String", "value": "baz" }
103+ }
104+ ],
105+ "property-matches-const": [
106+ {
107+ "expr": "foo.true",
108+ "contexts": {
109+ "foo": {
110+ "true": "it's true"
111+ }
112+ },
113+ "result": { "kind": "String", "value": "it's true" }
114+ },
115+ {
116+ "expr": "foo.false",
117+ "contexts": {
118+ "foo": {
119+ "false": "it's false"
120+ }
121+ },
122+ "result": { "kind": "String", "value": "it's false" }
123+ },
124+ {
125+ "expr": "foo.Infinity",
126+ "contexts": {
127+ "foo": {
128+ "Infinity": "it's Infinity"
129+ }
130+ },
131+ "result": { "kind": "String", "value": "it's Infinity" }
132+ },
133+ {
134+ "expr": "foo.NaN",
135+ "contexts": {
136+ "foo": {
137+ "NaN": "it's NaN"
138+ }
139+ },
140+ "result": { "kind": "String", "value": "it's NaN" }
141+ },
142+ {
143+ "expr": "foo.null",
144+ "contexts": {
145+ "foo": {
146+ "null": "it's null"
147+ }
148+ },
149+ "result": { "kind": "String", "value": "it's null" }
150+ },
151+ {
152+ "expr": "foo.format",
153+ "contexts": {
154+ "foo": {
155+ "format": "it's format"
156+ }
157+ },
158+ "result": { "kind": "String", "value": "it's format" }
159+ }
160+ ],
161+ "property-errors": [
162+ {
163+ "expr": "foo.b@r",
164+ "contexts": {
165+ "foo": {
166+ "b@r": "baz"
167+ }
168+ },
169+ "err": {
170+ "kind": "lexing",
171+ "value": "Unexpected symbol: 'b@r'. Located at position 5 within expression: foo.b@r"
172+ }
173+ },
174+ {
175+ "expr": "foo.1",
176+ "contexts": {
177+ "foo": {}
178+ },
179+ "err": {
180+ "kind": "parsing",
181+ "value": "Unexpected symbol: '1'. Located at position 5 within expression: foo.1"
182+ }
183+ },
184+ {
185+ "expr": "fromjson('').1",
186+ "err": {
187+ "kind": "parsing",
188+ "value": "Unexpected symbol: '1'. Located at position 14 within expression: fromjson('').1"
189+ }
190+ },
191+ {
192+ "expr": "foo[1].2",
193+ "contexts": {
194+ "foo": {}
195+ },
196+ "err": {
197+ "kind": "parsing",
198+ "value": "Unexpected symbol: '2'. Located at position 8 within expression: foo[1].2"
199+ }
200+ }
201+ ]
202+}
A expr/testdata/languageservices/op_eq.json +430 -0
@@ -0,0 +1,430 @@
1+{
2+ "boolean": [
3+ { "expr": "true == true", "result": { "kind": "Boolean", "value": true } },
4+ {
5+ "expr": "false == false",
6+ "result": { "kind": "Boolean", "value": true }
7+ },
8+ {
9+ "expr": "false == true",
10+ "result": { "kind": "Boolean", "value": false }
11+ }
12+ ],
13+
14+ "number": [
15+ { "expr": "0 == -0", "result": { "kind": "Boolean", "value": true } },
16+ { "expr": "2 == 2", "result": { "kind": "Boolean", "value": true } },
17+ { "expr": "1.001 == 1.002", "result": { "kind": "Boolean", "value": false } },
18+ { "expr": "120 == 1.2e2", "result": { "kind": "Boolean", "value": true } },
19+ { "expr": "1 == 2", "result": { "kind": "Boolean", "value": false } },
20+ { "expr": "NaN == NaN", "result": { "kind": "Boolean", "value": false } },
21+ {
22+ "expr": "Infinity == Infinity",
23+ "result": { "kind": "Boolean", "value": true }
24+ },
25+ {
26+ "expr": "-Infinity == Infinity",
27+ "result": { "kind": "Boolean", "value": false }
28+ },
29+ {
30+ "expr": "0 == Infinity",
31+ "result": { "kind": "Boolean", "value": false }
32+ },
33+ {
34+ "expr": "1 == Infinity",
35+ "result": { "kind": "Boolean", "value": false }
36+ }
37+ ],
38+
39+ "string": [
40+ { "expr": "'a' == 'a'", "result": { "kind": "Boolean", "value": true } },
41+ { "expr": "'a' == 'b'", "result": { "kind": "Boolean", "value": false } }
42+ ],
43+
44+ "null": [
45+ { "expr": "null == null", "result": { "kind": "Boolean", "value": true } }
46+ ],
47+
48+ "object": [
49+ {
50+ "expr": "object1 == object1",
51+ "contexts": { "object1": {} },
52+ "result": { "kind": "Boolean", "value": true }
53+ },
54+ {
55+ "expr": "object1 == object2",
56+ "contexts": { "object1": {}, "object2": {} },
57+ "result": { "kind": "Boolean", "value": false }
58+ }
59+ ],
60+
61+ "array": [
62+ {
63+ "expr": "array1 == array1",
64+ "contexts": { "array1": [] },
65+ "result": { "kind": "Boolean", "value": true }
66+ },
67+ {
68+ "expr": "array1 == array2",
69+ "contexts": { "array1": [], "array2": [] },
70+ "result": { "kind": "Boolean", "value": false }
71+ }
72+ ],
73+
74+ "coerce_bool_number": [
75+ { "expr": "false == 0", "result": { "kind": "Boolean", "value": true } },
76+ { "expr": "true == 1", "result": { "kind": "Boolean", "value": true } },
77+ { "expr": "false == NaN", "result": { "kind": "Boolean", "value": false } },
78+ { "expr": "true == 2", "result": { "kind": "Boolean", "value": false } }
79+ ],
80+
81+ "coerce_bool_string": [
82+ { "expr": "false == ''", "result": { "kind": "Boolean", "value": true } },
83+ {
84+ "expr": "false == ' '",
85+ "result": { "kind": "Boolean", "value": true }
86+ },
87+ {
88+ "expr": "false == ' 0.0 '",
89+ "result": { "kind": "Boolean", "value": true }
90+ },
91+ {
92+ "expr": "true == ' 1.0 '",
93+ "result": { "kind": "Boolean", "value": true }
94+ },
95+ {
96+ "expr": "true == ' 1 '",
97+ "result": { "kind": "Boolean", "value": true }
98+ },
99+ {
100+ "expr": "false == '-1'",
101+ "result": { "kind": "Boolean", "value": false }
102+ },
103+ { "expr": "true == '2'", "result": { "kind": "Boolean", "value": false } }
104+ ],
105+
106+ "coerce_bool_null": [
107+ {
108+ "expr": "false == null",
109+ "result": { "kind": "Boolean", "value": true }
110+ },
111+ {
112+ "expr": "true == null",
113+ "result": { "kind": "Boolean", "value": false }
114+ }
115+ ],
116+
117+ "coerce_bool_object-array": [
118+ {
119+ "expr": "false == test",
120+ "contexts": { "test": {} },
121+ "result": { "kind": "Boolean", "value": false }
122+ },
123+ {
124+ "expr": "false == test",
125+ "contexts": { "test": [] },
126+ "result": { "kind": "Boolean", "value": false }
127+ },
128+ {
129+ "expr": "true == test",
130+ "contexts": { "test": {} },
131+ "result": { "kind": "Boolean", "value": false }
132+ },
133+ {
134+ "expr": "true == test",
135+ "contexts": { "test": [] },
136+ "result": { "kind": "Boolean", "value": false }
137+ }
138+ ],
139+
140+ "coerce_number_bool": [
141+ { "expr": "0 == false", "result": { "kind": "Boolean", "value": true } },
142+ { "expr": "1 == true", "result": { "kind": "Boolean", "value": true } },
143+ {
144+ "expr": "NaN == false",
145+ "result": { "kind": "Boolean", "value": false }
146+ },
147+ { "expr": "2 == true", "result": { "kind": "Boolean", "value": false } }
148+ ],
149+
150+ "coerce_number_string": [
151+ {
152+ "expr": "0 == ' -0.0 '",
153+ "result": { "kind": "Boolean", "value": true }
154+ },
155+ { "expr": "0 == ''", "result": { "kind": "Boolean", "value": true } },
156+ { "expr": "0 == ' '", "result": { "kind": "Boolean", "value": true } },
157+ {
158+ "expr": "120 == ' +1.2e2 '",
159+ "result": { "kind": "Boolean", "value": true }
160+ },
161+ {
162+ "expr": "120.0 == ' 1.2E2 '",
163+ "result": { "kind": "Boolean", "value": true }
164+ },
165+ {
166+ "expr": "120 == ' 1.2e+2 '",
167+ "result": { "kind": "Boolean", "value": true }
168+ },
169+ {
170+ "expr": "-120 == ' -1.2E+2 '",
171+ "result": { "kind": "Boolean", "value": true }
172+ },
173+ {
174+ "expr": "0.012 == ' 1.2e-2 '",
175+ "result": { "kind": "Boolean", "value": true }
176+ },
177+ {
178+ "expr": "0.012 == ' 1.2E-2 '",
179+ "result": { "kind": "Boolean", "value": true }
180+ },
181+ {
182+ "expr": "255.0 == ' 0xff '",
183+ "result": { "kind": "Boolean", "value": true }
184+ },
185+ {
186+ "expr": "8.0 == ' 0o10 '",
187+ "result": { "kind": "Boolean", "value": true }
188+ },
189+ {
190+ "expr": "Infinity == ' Infinity '",
191+ "result": { "kind": "Boolean", "value": true }
192+ },
193+ {
194+ "expr": "-Infinity == ' -Infinity '",
195+ "result": { "kind": "Boolean", "value": true }
196+ },
197+ {
198+ "expr": "NaN == 'NaN'",
199+ "result": { "kind": "Boolean", "value": false }
200+ },
201+ { "expr": "1 == '0'", "result": { "kind": "Boolean", "value": false } },
202+ { "expr": "1 == 'a'", "result": { "kind": "Boolean", "value": false } }
203+ ],
204+
205+ "coerce_number_null": [
206+ { "expr": "0 == null", "result": { "kind": "Boolean", "value": true } }
207+ ],
208+
209+ "coerce_number_object-array": [
210+ {
211+ "expr": "0 == test",
212+ "contexts": { "test": {} },
213+ "result": { "kind": "Boolean", "value": false }
214+ },
215+ {
216+ "expr": "1 == test",
217+ "contexts": { "test": {} },
218+ "result": { "kind": "Boolean", "value": false }
219+ },
220+ {
221+ "expr": "0 == test",
222+ "contexts": { "test": [] },
223+ "result": { "kind": "Boolean", "value": false }
224+ },
225+ {
226+ "expr": "1 == test",
227+ "contexts": { "test": [] },
228+ "result": { "kind": "Boolean", "value": false }
229+ }
230+ ],
231+
232+ "coerce_string_bool": [
233+ { "expr": "'' == false", "result": { "kind": "Boolean", "value": true } },
234+ {
235+ "expr": "' ' == false",
236+ "result": { "kind": "Boolean", "value": true }
237+ },
238+ {
239+ "expr": "' 0.0 ' == false",
240+ "result": { "kind": "Boolean", "value": true }
241+ },
242+ {
243+ "expr": "' 1.0 ' == true",
244+ "result": { "kind": "Boolean", "value": true }
245+ },
246+ {
247+ "expr": "' 1 ' == true",
248+ "result": { "kind": "Boolean", "value": true }
249+ },
250+ {
251+ "expr": "'-1' == false",
252+ "result": { "kind": "Boolean", "value": false }
253+ },
254+ { "expr": "'2' == true", "result": { "kind": "Boolean", "value": false } }
255+ ],
256+
257+ "coerce_string_number": [
258+ {
259+ "expr": "' -0.0 ' == 0",
260+ "result": { "kind": "Boolean", "value": true }
261+ },
262+ { "expr": "'' == 0", "result": { "kind": "Boolean", "value": true } },
263+ { "expr": "' ' == 0", "result": { "kind": "Boolean", "value": true } },
264+ {
265+ "expr": "' +1.2e2 ' == 120",
266+ "result": { "kind": "Boolean", "value": true }
267+ },
268+ {
269+ "expr": "' 1.2E2 ' == 120.0",
270+ "result": { "kind": "Boolean", "value": true }
271+ },
272+ {
273+ "expr": "' 1.2e+2 ' == 120",
274+ "result": { "kind": "Boolean", "value": true }
275+ },
276+ {
277+ "expr": "' -1.2E+2 ' == -120",
278+ "result": { "kind": "Boolean", "value": true }
279+ },
280+ {
281+ "expr": "' 1.2e-2 ' == 0.012",
282+ "result": { "kind": "Boolean", "value": true }
283+ },
284+ {
285+ "expr": "' 1.2E-2 ' == 0.012",
286+ "result": { "kind": "Boolean", "value": true }
287+ },
288+ {
289+ "expr": "' 0xff ' == 255.0",
290+ "result": { "kind": "Boolean", "value": true }
291+ },
292+ {
293+ "expr": "' 0o10 ' == 8.0",
294+ "result": { "kind": "Boolean", "value": true }
295+ },
296+ {
297+ "expr": "' Infinity ' == Infinity",
298+ "result": { "kind": "Boolean", "value": true }
299+ },
300+ {
301+ "expr": "' -Infinity ' == -Infinity",
302+ "result": { "kind": "Boolean", "value": true }
303+ },
304+ {
305+ "expr": "'NaN' == NaN",
306+ "result": { "kind": "Boolean", "value": false }
307+ },
308+ { "expr": "'0' == 1", "result": { "kind": "Boolean", "value": false } },
309+ { "expr": "'a' == 1", "result": { "kind": "Boolean", "value": false } }
310+ ],
311+
312+ "coerce_string_null": [
313+ { "expr": "'' == null", "result": { "kind": "Boolean", "value": true } },
314+ {
315+ "expr": "' ' == null",
316+ "result": { "kind": "Boolean", "value": true }
317+ },
318+ { "expr": "'0' == null", "result": { "kind": "Boolean", "value": true } },
319+ {
320+ "expr": "'false' == null",
321+ "result": { "kind": "Boolean", "value": false }
322+ },
323+ {
324+ "expr": "'null' == null",
325+ "result": { "kind": "Boolean", "value": false }
326+ }
327+ ],
328+
329+ "coerce_string_object": [
330+ {
331+ "expr": "'' == test",
332+ "contexts": { "test": {} },
333+ "result": { "kind": "Boolean", "value": false }
334+ },
335+ {
336+ "expr": "'false' == test",
337+ "contexts": { "test": {} },
338+ "result": { "kind": "Boolean", "value": false }
339+ },
340+ {
341+ "expr": "'0' == test",
342+ "contexts": { "test": {} },
343+ "result": { "kind": "Boolean", "value": false }
344+ },
345+ {
346+ "expr": "'null' == test",
347+ "contexts": { "test": {} },
348+ "result": { "kind": "Boolean", "value": false }
349+ },
350+ {
351+ "expr": "'Object' == test",
352+ "contexts": { "test": {} },
353+ "result": { "kind": "Boolean", "value": false }
354+ }
355+ ],
356+
357+ "coerce_string_array": [
358+ {
359+ "expr": "'' == test",
360+ "contexts": { "test": [] },
361+ "result": { "kind": "Boolean", "value": false }
362+ },
363+ {
364+ "expr": "'false' == test",
365+ "contexts": { "test": [] },
366+ "result": { "kind": "Boolean", "value": false }
367+ },
368+ {
369+ "expr": "'0' == test",
370+ "contexts": { "test": [] },
371+ "result": { "kind": "Boolean", "value": false }
372+ },
373+ {
374+ "expr": "'null' == test",
375+ "contexts": { "test": [] },
376+ "result": { "kind": "Boolean", "value": false }
377+ },
378+ {
379+ "expr": "'Array' == test",
380+ "contexts": { "test": [] },
381+ "result": { "kind": "Boolean", "value": false }
382+ }
383+ ],
384+
385+ "coerce_null_bool": [
386+ {
387+ "expr": "null == false",
388+ "result": { "kind": "Boolean", "value": true }
389+ },
390+ {
391+ "expr": "null == true",
392+ "result": { "kind": "Boolean", "value": false }
393+ }
394+ ],
395+
396+ "coerce_null_number": [
397+ { "expr": "null == 0", "result": { "kind": "Boolean", "value": true } },
398+ { "expr": "false == 1", "result": { "kind": "Boolean", "value": false } }
399+ ],
400+
401+ "coerce_null_string": [
402+ { "expr": "null == ''", "result": { "kind": "Boolean", "value": true } },
403+ {
404+ "expr": "null == ' '",
405+ "result": { "kind": "Boolean", "value": true }
406+ },
407+ { "expr": "null == '0'", "result": { "kind": "Boolean", "value": true } },
408+ {
409+ "expr": "null == 'false'",
410+ "result": { "kind": "Boolean", "value": false }
411+ },
412+ {
413+ "expr": "null == 'null'",
414+ "result": { "kind": "Boolean", "value": false }
415+ }
416+ ],
417+
418+ "coerce_null_object-array": [
419+ {
420+ "expr": "null == test",
421+ "contexts": { "test": {} },
422+ "result": { "kind": "Boolean", "value": false }
423+ },
424+ {
425+ "expr": "null == test",
426+ "contexts": { "test": [] },
427+ "result": { "kind": "Boolean", "value": false }
428+ }
429+ ]
430+}
A expr/testdata/languageservices/op_gt.json +376 -0
@@ -0,0 +1,376 @@
1+{
2+ "bool": [
3+ {
4+ "expr": "false > true",
5+ "result": { "kind": "Boolean", "value": false }
6+ },
7+ {
8+ "expr": "false > false",
9+ "result": { "kind": "Boolean", "value": false }
10+ },
11+ {
12+ "expr": "true > false",
13+ "result": { "kind": "Boolean", "value": true }
14+ },
15+ {
16+ "expr": "true > true",
17+ "result": { "kind": "Boolean", "value": false }
18+ }
19+ ],
20+ "number": [
21+ {
22+ "expr": "1 > 2",
23+ "result": { "kind": "Boolean", "value": false }
24+ },
25+ {
26+ "expr": "1 > 1",
27+ "result": { "kind": "Boolean", "value": false }
28+ },
29+ {
30+ "expr": "2 > 1",
31+ "result": { "kind": "Boolean", "value": true }
32+ },
33+ {
34+ "expr": "1.002 > 1.001",
35+ "result": { "kind": "Boolean", "value": true }
36+ }
37+ ],
38+ "string": [
39+ {
40+ "expr": "'def' > 'abc'",
41+ "result": { "kind": "Boolean", "value": true }
42+ },
43+ {
44+ "expr": "'a' > 'b'",
45+ "result": { "kind": "Boolean", "value": false }
46+ }
47+ ],
48+
49+ "array": [
50+ {
51+ "expr": "test > test",
52+ "contexts": { "test": [] },
53+ "result": { "kind": "Boolean", "value": false }
54+ }
55+ ],
56+
57+ "object": [
58+ {
59+ "expr": "test > test",
60+ "contexts": { "test": {} },
61+ "result": { "kind": "Boolean", "value": false }
62+ }
63+ ],
64+
65+ "coerce_bool_number": [
66+ {
67+ "expr": "false > 0",
68+ "result": { "kind": "Boolean", "value": false }
69+ },
70+ {
71+ "expr": "false > -11",
72+ "result": { "kind": "Boolean", "value": true }
73+ },
74+ {
75+ "expr": "true > 1",
76+ "result": { "kind": "Boolean", "value": false }
77+ },
78+ {
79+ "expr": "true > 0",
80+ "result": { "kind": "Boolean", "value": true }
81+ },
82+ {
83+ "expr": "false > NaN",
84+ "result": { "kind": "Boolean", "value": false }
85+ },
86+ {
87+ "expr": "true > NaN",
88+ "result": { "kind": "Boolean", "value": false }
89+ },
90+ {
91+ "expr": "false > Infinity",
92+ "result": { "kind": "Boolean", "value": false }
93+ },
94+ {
95+ "expr": "true > Infinity",
96+ "result": { "kind": "Boolean", "value": false }
97+ },
98+ {
99+ "expr": "false > -Infinity",
100+ "result": { "kind": "Boolean", "value": true }
101+ },
102+ {
103+ "expr": "true > -Infinity",
104+ "result": { "kind": "Boolean", "value": true }
105+ }
106+ ],
107+
108+ "coerce_bool_string": [
109+ {
110+ "expr": "false > '0'",
111+ "result": { "kind": "Boolean", "value": false }
112+ },
113+ {
114+ "expr": "false > '-1'",
115+ "result": { "kind": "Boolean", "value": true }
116+ },
117+ {
118+ "expr": "true > '1'",
119+ "result": { "kind": "Boolean", "value": false }
120+ },
121+ {
122+ "expr": "true > '0'",
123+ "result": { "kind": "Boolean", "value": true }
124+ }
125+ ],
126+
127+ "coerce_bool_null": [
128+ {
129+ "expr": "false > null",
130+ "result": { "kind": "Boolean", "value": false }
131+ },
132+ {
133+ "expr": "true > null",
134+ "result": { "kind": "Boolean", "value": true }
135+ }
136+ ],
137+
138+ "coerce_bool_object": [
139+ {
140+ "expr": "false > test",
141+ "contexts": { "test": {} },
142+ "result": { "kind": "Boolean", "value": false }
143+ },
144+ {
145+ "expr": "true > test",
146+ "contexts": { "test": {} },
147+ "result": { "kind": "Boolean", "value": false }
148+ }
149+ ],
150+
151+ "coerce_bool_array": [
152+ {
153+ "expr": "false > test",
154+ "contexts": { "test": [] },
155+ "result": { "kind": "Boolean", "value": false }
156+ },
157+ {
158+ "expr": "true > test",
159+ "contexts": { "test": [] },
160+ "result": { "kind": "Boolean", "value": false }
161+ }
162+ ],
163+
164+ "coerce_number_bool": [
165+ {
166+ "expr": "1 > false",
167+ "result": { "kind": "Boolean", "value": true }
168+ },
169+ {
170+ "expr": "0 > false",
171+ "result": { "kind": "Boolean", "value": false }
172+ },
173+ {
174+ "expr": "2 > true",
175+ "result": { "kind": "Boolean", "value": true }
176+ },
177+ {
178+ "expr": "1 > true",
179+ "result": { "kind": "Boolean", "value": false }
180+ },
181+ {
182+ "expr": "NaN > false",
183+ "result": { "kind": "Boolean", "value": false }
184+ },
185+ {
186+ "expr": "NaN > true",
187+ "result": { "kind": "Boolean", "value": false }
188+ },
189+ {
190+ "expr": "Infinity > false",
191+ "result": { "kind": "Boolean", "value": true }
192+ },
193+ {
194+ "expr": "Infinity > true",
195+ "result": { "kind": "Boolean", "value": true }
196+ },
197+ {
198+ "expr": "-Infinity > false",
199+ "result": { "kind": "Boolean", "value": false }
200+ },
201+ {
202+ "expr": "-Infinity > true",
203+ "result": { "kind": "Boolean", "value": false }
204+ }
205+ ],
206+
207+ "coerce_number_string": [
208+ {
209+ "expr": "0 > ' 0 '",
210+ "result": { "kind": "Boolean", "value": false }
211+ },
212+ {
213+ "expr": "0 > ' -1 '",
214+ "result": { "kind": "Boolean", "value": true }
215+ },
216+ {
217+ "expr": "1 > ' 1 '",
218+ "result": { "kind": "Boolean", "value": false }
219+ },
220+ {
221+ "expr": "1 > ' 0 '",
222+ "result": { "kind": "Boolean", "value": true }
223+ }
224+ ],
225+
226+ "coerce_number_null": [
227+ {
228+ "expr": "1 > null",
229+ "result": { "kind": "Boolean", "value": true }
230+ },
231+ {
232+ "expr": "0 > null",
233+ "result": { "kind": "Boolean", "value": false }
234+ }
235+ ],
236+
237+ "coerce_number_object": [
238+ {
239+ "expr": "0 > test",
240+ "contexts": { "test": {} },
241+ "result": { "kind": "Boolean", "value": false }
242+ },
243+ {
244+ "expr": "1 > test",
245+ "contexts": { "test": {} },
246+ "result": { "kind": "Boolean", "value": false }
247+ }
248+ ],
249+
250+ "coerce_number_array": [
251+ {
252+ "expr": "0 > test",
253+ "contexts": { "test": [] },
254+ "result": { "kind": "Boolean", "value": false }
255+ },
256+ {
257+ "expr": "1 > test",
258+ "contexts": { "test": [] },
259+ "result": { "kind": "Boolean", "value": false }
260+ }
261+ ],
262+
263+ "coerce_string_bool": [
264+ {
265+ "expr": "'0' > false",
266+ "result": { "kind": "Boolean", "value": false }
267+ },
268+ {
269+ "expr": "'1' > false",
270+ "result": { "kind": "Boolean", "value": true }
271+ },
272+ {
273+ "expr": "'1' > true",
274+ "result": { "kind": "Boolean", "value": false }
275+ },
276+ {
277+ "expr": "'2' > true",
278+ "result": { "kind": "Boolean", "value": true }
279+ }
280+ ],
281+
282+ "coerce_string_number": [
283+ {
284+ "expr": "' 0 ' > -1",
285+ "result": { "kind": "Boolean", "value": true }
286+ },
287+ {
288+ "expr": "' 0 ' > 0",
289+ "result": { "kind": "Boolean", "value": false }
290+ },
291+ {
292+ "expr": "' 1 ' > 0",
293+ "result": { "kind": "Boolean", "value": true }
294+ },
295+ {
296+ "expr": "' 1 ' > 1",
297+ "result": { "kind": "Boolean", "value": false }
298+ }
299+ ],
300+
301+ "coerce_string_null": [
302+ {
303+ "expr": "'' > null",
304+ "result": { "kind": "Boolean", "value": false }
305+ },
306+ {
307+ "expr": "'1' > null",
308+ "result": { "kind": "Boolean", "value": true }
309+ }
310+ ],
311+
312+ "coerce_string_object": [
313+ {
314+ "expr": "'' > test",
315+ "contexts": { "test": {} },
316+ "result": { "kind": "Boolean", "value": false }
317+ }
318+ ],
319+
320+ "coerce_string_array": [
321+ {
322+ "expr": "'' > test",
323+ "contexts": { "test": [] },
324+ "result": { "kind": "Boolean", "value": false }
325+ }
326+ ],
327+
328+ "coerce_null_bool": [
329+ {
330+ "expr": "null > false",
331+ "result": { "kind": "Boolean", "value": false }
332+ },
333+ {
334+ "expr": "null > true",
335+ "result": { "kind": "Boolean", "value": false }
336+ }
337+ ],
338+
339+ "coerce_null_number": [
340+ {
341+ "expr": "null > 0",
342+ "result": { "kind": "Boolean", "value": false }
343+ },
344+ {
345+ "expr": "null > -1",
346+ "result": { "kind": "Boolean", "value": true }
347+ }
348+ ],
349+
350+ "coerce_null_string": [
351+ {
352+ "expr": "null > ' 0 '",
353+ "result": { "kind": "Boolean", "value": false }
354+ },
355+ {
356+ "expr": "null > ' -1 '",
357+ "result": { "kind": "Boolean", "value": true }
358+ }
359+ ],
360+
361+ "coerce_null_object": [
362+ {
363+ "expr": "null > test",
364+ "contexts": { "test": {} },
365+ "result": { "kind": "Boolean", "value": false }
366+ }
367+ ],
368+
369+ "coerce_null_array": [
370+ {
371+ "expr": "null > test",
372+ "contexts": { "test": [] },
373+ "result": { "kind": "Boolean", "value": false }
374+ }
375+ ]
376+}⏎̸
A expr/testdata/languageservices/op_gte.json +20 -0
@@ -0,0 +1,20 @@
1+{
2+ "bool": [
3+ {
4+ "expr": "false >= true",
5+ "result": { "kind": "Boolean", "value": false }
6+ },
7+ {
8+ "expr": "false >= false",
9+ "result": { "kind": "Boolean", "value": true }
10+ },
11+ {
12+ "expr": "true >= false",
13+ "result": { "kind": "Boolean", "value": true }
14+ },
15+ {
16+ "expr": "true >= true",
17+ "result": { "kind": "Boolean", "value": true }
18+ }
19+ ]
20+}⏎̸
A expr/testdata/languageservices/op_idx.json +354 -0
@@ -0,0 +1,354 @@
1+{
2+ "array-index-coercion": [
3+ {
4+ "expr": "foo[null]",
5+ "contexts": {
6+ "foo": [
7+ "zero",
8+ "one",
9+ "two"
10+ ]
11+ },
12+ "result": { "kind": "String", "value": "zero" }
13+ },
14+ {
15+ "expr": "foo[false]",
16+ "contexts": {
17+ "foo": [
18+ "zero",
19+ "one",
20+ "two"
21+ ]
22+ },
23+ "result": { "kind": "String", "value": "zero" }
24+ },
25+ {
26+ "expr": "foo[true]",
27+ "contexts": {
28+ "foo": [
29+ "zero",
30+ "one",
31+ "two"
32+ ]
33+ },
34+ "result": { "kind": "String", "value": "one" }
35+ },
36+ {
37+ "expr": "foo[' 0.0 ']",
38+ "contexts": {
39+ "foo": [
40+ "zero",
41+ "one",
42+ "two"
43+ ]
44+ },
45+ "result": { "kind": "String", "value": "zero" }
46+ },
47+ {
48+ "expr": "foo[' 0x02 ']",
49+ "contexts": {
50+ "foo": [
51+ "zero",
52+ "one",
53+ "two"
54+ ]
55+ },
56+ "result": { "kind": "String", "value": "two" }
57+ },
58+ {
59+ "expr": "foo[' asdf ']",
60+ "contexts": {
61+ "foo": [
62+ "zero",
63+ "one",
64+ "two"
65+ ]
66+ },
67+ "result": { "kind": "Null", "value": null }
68+ },
69+ {
70+ "expr": "foo[fromjson('[]')]",
71+ "contexts": {
72+ "foo": [
73+ "zero",
74+ "one",
75+ "two"
76+ ]
77+ },
78+ "result": { "kind": "Null", "value": null }
79+ },
80+ {
81+ "expr": "foo[fromjson('{}')]",
82+ "contexts": {
83+ "foo": [
84+ "zero",
85+ "one",
86+ "two"
87+ ]
88+ },
89+ "result": { "kind": "Null", "value": null }
90+ }
91+ ],
92+ "array-index-out-of-range": [
93+ {
94+ "expr": "foo[-1]",
95+ "contexts": {
96+ "foo": [
97+ "zero",
98+ "one",
99+ "two"
100+ ]
101+ },
102+ "result": { "kind": "Null", "value": null }
103+ },
104+ {
105+ "expr": "foo[3]",
106+ "contexts": {
107+ "foo": [
108+ "zero",
109+ "one",
110+ "two"
111+ ]
112+ },
113+ "result": { "kind": "Null", "value": null }
114+ },
115+ {
116+ "expr": "foo[-Infinity]",
117+ "contexts": {
118+ "foo": [
119+ "zero",
120+ "one",
121+ "two"
122+ ]
123+ },
124+ "result": { "kind": "Null", "value": null }
125+ },
126+ {
127+ "expr": "foo[Infinity]",
128+ "contexts": {
129+ "foo": [
130+ "zero",
131+ "one",
132+ "two"
133+ ]
134+ },
135+ "result": { "kind": "Null", "value": null }
136+ },
137+ {
138+ "expr": "foo[NaN]",
139+ "contexts": {
140+ "foo": [
141+ "zero",
142+ "one",
143+ "two"
144+ ]
145+ },
146+ "result": { "kind": "Null", "value": null }
147+ }
148+ ],
149+ "object-case-insensitive": [
150+ {
151+ "expr": "foo['bAr']",
152+ "contexts": {
153+ "foo": {
154+ "BaR": "baz"
155+ }
156+ },
157+ "result": { "kind": "String", "value": "baz" }
158+ }
159+ ],
160+ "object-index-coercion": [
161+ {
162+ "expr": "foo[5]",
163+ "contexts": {
164+ "foo": {
165+ "5": "it's 5"
166+ }
167+ },
168+ "result": { "kind": "String", "value": "it's 5" }
169+ },
170+ {
171+ "expr": "foo[.5]",
172+ "contexts": {
173+ "foo": {
174+ "0.5": "it's 0.5"
175+ }
176+ },
177+ "result": { "kind": "String", "value": "it's 0.5" }
178+ },
179+ {
180+ "expr": "foo[-.5]",
181+ "contexts": {
182+ "foo": {
183+ "-0.5": "it's -0.5"
184+ }
185+ },
186+ "result": { "kind": "String", "value": "it's -0.5" }
187+ },
188+ {
189+ "expr": "foo[0x0f]",
190+ "contexts": {
191+ "foo": {
192+ "15": "it's 15"
193+ }
194+ },
195+ "result": { "kind": "String", "value": "it's 15" }
196+ },
197+ {
198+ "expr": "foo[NaN]",
199+ "contexts": {
200+ "foo": {
201+ "NaN": "it's NaN"
202+ }
203+ },
204+ "result": { "kind": "String", "value": "it's NaN" }
205+ },
206+ {
207+ "expr": "foo[Infinity]",
208+ "contexts": {
209+ "foo": {
210+ "Infinity": "it's Infinity"
211+ }
212+ },
213+ "result": { "kind": "String", "value": "it's Infinity" }
214+ },
215+ {
216+ "expr": "foo[-Infinity]",
217+ "contexts": {
218+ "foo": {
219+ "-Infinity": "it's -Infinity"
220+ }
221+ },
222+ "result": { "kind": "String", "value": "it's -Infinity" }
223+ },
224+ {
225+ "expr": "foo[null]",
226+ "contexts": {
227+ "foo": {
228+ "": "it's null"
229+ }
230+ },
231+ "result": { "kind": "String", "value": "it's null" }
232+ },
233+ {
234+ "expr": "foo[true]",
235+ "contexts": {
236+ "foo": {
237+ "true": "it's true"
238+ }
239+ },
240+ "result": { "kind": "String", "value": "it's true" }
241+ },
242+ {
243+ "expr": "foo[false]",
244+ "contexts": {
245+ "foo": {
246+ "false": "it's false"
247+ }
248+ },
249+ "result": { "kind": "String", "value": "it's false" }
250+ },
251+ {
252+ "expr": "foo[fromJson('{}')]",
253+ "contexts": {
254+ "foo": {
255+ "Object": "It's Object"
256+ }
257+ },
258+ "result": { "kind": "Null", "value": null }
259+ },
260+ {
261+ "expr": "foo[format('{0}', fromJson('{}'))]",
262+ "contexts": {
263+ "foo": {
264+ "Object": "It's Object"
265+ }
266+ },
267+ "result": { "kind": "String", "value": "It's Object" }
268+ },
269+ {
270+ "expr": "foo[fromJson('[]')]",
271+ "contexts": {
272+ "foo": {
273+ "Array": "It's Array"
274+ }
275+ },
276+ "result": { "kind": "Null", "value": null }
277+ },
278+ {
279+ "expr": "foo[format('{0}', fromJson('[]'))]",
280+ "contexts": {
281+ "foo": {
282+ "Array": "It's Array"
283+ }
284+ },
285+ "result": { "kind": "String", "value": "It's Array" }
286+ }
287+ ],
288+ "object-index-out-of-range": [
289+ {
290+ "expr": "foo['']",
291+ "contexts": {
292+ "foo": {
293+ "bar": "baz"
294+ }
295+ },
296+ "result": { "kind": "Null", "value": null }
297+ },
298+ {
299+ "expr": "foo['asdf']",
300+ "contexts": {
301+ "foo": {
302+ "bar": "baz"
303+ }
304+ },
305+ "result": { "kind": "Null", "value": null }
306+ }
307+ ],
308+ "index-following-function": [
309+ {
310+ "expr": "fromJson('[\"one\", \"two\"]')[1]",
311+ "result": { "kind": "String", "value": "two" }
312+ }
313+ ],
314+ "index-following-group": [
315+ {
316+ "expr": "(fromJson('[\"one\", \"two\"]'))[1]",
317+ "result": { "kind": "String", "value": "two" }
318+ }
319+ ],
320+ "index-star": [
321+ {
322+ "expr": "foo[*]",
323+ "contexts": {
324+ "foo": {
325+ "one": "one val",
326+ "two": "two val",
327+ "*": "star val"
328+ }
329+ },
330+ "result": { "kind": "Array", "value": ["one val", "two val", "star val"] }
331+ },
332+ {
333+ "expr": "foo['*']",
334+ "contexts": {
335+ "foo": {
336+ "one": "one val",
337+ "two": "two val",
338+ "*": "star val"
339+ }
340+ },
341+ "result": { "kind": "String", "value": "star val" }
342+ },
343+ {
344+ "expr": "foo[*]",
345+ "contexts": {
346+ "foo": [
347+ "one",
348+ "two"
349+ ]
350+ },
351+ "result": { "kind": "Array", "value": ["one", "two"] }
352+ }
353+ ]
354+}
A expr/testdata/languageservices/op_idx_star.json +556 -0
@@ -0,0 +1,556 @@
1+{
2+ "filtered-array-from-literal": [
3+ {
4+ "expr": "foo.*",
5+ "contexts": {
6+ "foo": null
7+ },
8+ "result": { "kind": "Array", "value": [] }
9+ },
10+ {
11+ "expr": "foo.*",
12+ "contexts": {
13+ "foo": false
14+ },
15+ "result": { "kind": "Array", "value": [] }
16+ },
17+ {
18+ "expr": "foo.*",
19+ "contexts": {
20+ "foo": true
21+ },
22+ "result": { "kind": "Array", "value": [] }
23+ },
24+ {
25+ "expr": "foo.*",
26+ "contexts": {
27+ "foo": 123
28+ },
29+ "result": { "kind": "Array", "value": [] }
30+ },
31+ {
32+ "expr": "foo.*",
33+ "contexts": {
34+ "foo": "abc"
35+ },
36+ "result": { "kind": "Array", "value": [] }
37+ }
38+ ],
39+ "filtered-array-from-array": [
40+ {
41+ "expr": "foo.*",
42+ "contexts": {
43+ "foo": [
44+ "zero",
45+ "one"
46+ ]
47+ },
48+ "result": { "kind": "Array", "value": ["zero", "one"] }
49+ }
50+ ],
51+ "filtered-array-from-object": [
52+ {
53+ "expr": "foo.*",
54+ "contexts": {
55+ "foo": {
56+ "one": "one val",
57+ "two": "two val"
58+ }
59+ },
60+ "result": { "kind": "Array", "value": ["one val", "two val"] }
61+ }
62+ ],
63+ "nested-object": [
64+ {
65+ "expr": "foo.*.one",
66+ "contexts": {
67+ "foo": [
68+ {
69+ "one": "obj_1_prop_1",
70+ "two": "obj_1_prop_2"
71+ },
72+ {
73+ "one": "obj_2_prop_1",
74+ "two": "obj_2_prop_2"
75+ }
76+ ]
77+ },
78+ "result": { "kind": "Array", "value": ["obj_1_prop_1", "obj_2_prop_1"] }
79+ },
80+ {
81+ "expr": "foo[*]['']",
82+ "contexts": {
83+ "foo": [
84+ {
85+ "": "empty string"
86+ }
87+ ]
88+ },
89+ "result": { "kind": "Array", "value": ["empty string"] }
90+ }
91+ ],
92+ "nested-object-property-case-insensitive": [
93+ {
94+ "expr": "foo.*.thePROPERTY",
95+ "contexts": {
96+ "foo": [
97+ {
98+ "THEproperty": "the property"
99+ }
100+ ]
101+ },
102+ "result": { "kind": "Array", "value": ["the property"] }
103+ }
104+ ],
105+ "nested-object-property-not-found": [
106+ {
107+ "expr": "foo.*.one",
108+ "contexts": {
109+ "foo": [
110+ {
111+ "one": "obj_1_prop_1",
112+ "two": "obj_1_prop_2"
113+ },
114+ {
115+ "zero": "obj_2_prop_0",
116+ "three": "obj_2_prop_3"
117+ },
118+ {
119+ "one": "obj_3_prop_1",
120+ "two": "obj_3_prop_2"
121+ }
122+ ]
123+ },
124+ "result": { "kind": "Array", "value": ["obj_1_prop_1", "obj_3_prop_1"] }
125+ }
126+ ],
127+ "nested-object-property-coercion": [
128+ {
129+ "expr": "foo[*][null]",
130+ "contexts": {
131+ "foo": [
132+ {
133+ "": "empty string"
134+ }
135+ ]
136+ },
137+ "result": { "kind": "Array", "value": ["empty string"] }
138+ },
139+ {
140+ "expr": "foo[*][false]",
141+ "contexts": {
142+ "foo": [
143+ {
144+ "false": "It's false"
145+ }
146+ ]
147+ },
148+ "result": { "kind": "Array", "value": ["It's false"] }
149+ },
150+ {
151+ "expr": "foo[*][true]",
152+ "contexts": {
153+ "foo": [
154+ {
155+ "true": "It's true"
156+ }
157+ ]
158+ },
159+ "result": { "kind": "Array", "value": ["It's true"] }
160+ },
161+ {
162+ "expr": "foo[*][0x0f]",
163+ "contexts": {
164+ "foo": [
165+ {
166+ "15": "fifteen"
167+ }
168+ ]
169+ },
170+ "result": { "kind": "Array", "value": ["fifteen"] }
171+ },
172+ {
173+ "expr": "foo[*][NaN]",
174+ "contexts": {
175+ "foo": [
176+ {
177+ "NaN": "It's NaN"
178+ }
179+ ]
180+ },
181+ "result": { "kind": "Array", "value": ["It's NaN"] }
182+ },
183+ {
184+ "expr": "foo[*][Infinity]",
185+ "contexts": {
186+ "foo": [
187+ {
188+ "Infinity": "It's Infinity"
189+ }
190+ ]
191+ },
192+ "result": { "kind": "Array", "value": ["It's Infinity"] }
193+ },
194+ {
195+ "expr": "foo[*][-Infinity]",
196+ "contexts": {
197+ "foo": [
198+ {
199+ "-Infinity": "It's -Infinity"
200+ }
201+ ]
202+ },
203+ "result": { "kind": "Array", "value": ["It's -Infinity"] }
204+ },
205+ {
206+ "expr": "foo[*][fromjson('[]')]",
207+ "contexts": {
208+ "foo": [
209+ {
210+ "": "empty string",
211+ "Array": "It's array"
212+ }
213+ ]
214+ },
215+ "result": { "kind": "Array", "value": [] }
216+ },
217+ {
218+ "expr": "foo[*][fromjson('{}')]",
219+ "contexts": {
220+ "foo": [
221+ {
222+ "": "empty string",
223+ "Object": "It's object"
224+ }
225+ ]
226+ },
227+ "result": { "kind": "Array", "value": [] }
228+ }
229+ ],
230+ "nested-array": [
231+ {
232+ "expr": "foo[*][1]",
233+ "contexts": {
234+ "foo": [
235+ [
236+ "ary_0_idx_0",
237+ "ary_0_idx_1",
238+ "ary_0_idx_2"
239+ ],
240+ [
241+ "ary_1_idx_0",
242+ "ary_1_idx_1",
243+ "ary_1_idx_2"
244+ ]
245+ ]
246+ },
247+ "result": { "kind": "Array", "value": ["ary_0_idx_1", "ary_1_idx_1"] }
248+ }
249+ ],
250+ "nested-array-index-not-found": [
251+ {
252+ "expr": "foo[*][1]",
253+ "contexts": {
254+ "foo": [
255+ [
256+ "ary_0_idx_0",
257+ "ary_0_idx_1",
258+ "ary_0_idx_2"
259+ ],
260+ [
261+ "ary_1_idx_0"
262+ ],
263+ [
264+ "ary_2_idx_0",
265+ "ary_2_idx_1",
266+ "ary_2_idx_2"
267+ ]
268+ ]
269+ },
270+ "result": { "kind": "Array", "value": ["ary_0_idx_1", "ary_2_idx_1"] }
271+ },
272+ {
273+ "expr": "foo[*][-1]",
274+ "contexts": {
275+ "foo": [
276+ [
277+ "zero",
278+ "one",
279+ "two"
280+ ]
281+ ]
282+ },
283+ "result": { "kind": "Array", "value": [] }
284+ },
285+ {
286+ "expr": "foo[*][NaN]",
287+ "contexts": {
288+ "foo": [
289+ [
290+ "zero",
291+ "one",
292+ "two"
293+ ]
294+ ]
295+ },
296+ "result": { "kind": "Array", "value": [] }
297+ },
298+ {
299+ "expr": "foo[*][Infinity]",
300+ "contexts": {
301+ "foo": [
302+ [
303+ "zero",
304+ "one",
305+ "two"
306+ ]
307+ ]
308+ },
309+ "result": { "kind": "Array", "value": [] }
310+ },
311+ {
312+ "expr": "foo[*][Infinity]",
313+ "contexts": {
314+ "foo": [
315+ [
316+ "zero",
317+ "one",
318+ "two"
319+ ]
320+ ]
321+ },
322+ "result": { "kind": "Array", "value": [] }
323+ }
324+ ],
325+ "nested-array-index-coercion": [
326+ {
327+ "expr": "foo[*][null]",
328+ "contexts": {
329+ "foo": [
330+ [
331+ "zero",
332+ "one",
333+ "two"
334+ ]
335+ ]
336+ },
337+ "result": { "kind": "Array", "value": ["zero"] }
338+ },
339+ {
340+ "expr": "foo[*][false]",
341+ "contexts": {
342+ "foo": [
343+ [
344+ "zero",
345+ "one",
346+ "two"
347+ ]
348+ ]
349+ },
350+ "result": { "kind": "Array", "value": ["zero"] }
351+ },
352+ {
353+ "expr": "foo[*][true]",
354+ "contexts": {
355+ "foo": [
356+ [
357+ "zero",
358+ "one",
359+ "two"
360+ ]
361+ ]
362+ },
363+ "result": { "kind": "Array", "value": ["one"] }
364+ },
365+ {
366+ "expr": "foo[*]['']",
367+ "contexts": {
368+ "foo": [
369+ [
370+ "zero",
371+ "one",
372+ "two"
373+ ]
374+ ]
375+ },
376+ "result": { "kind": "Array", "value": ["zero"] }
377+ },
378+ {
379+ "expr": "foo[*]['abc']",
380+ "contexts": {
381+ "foo": [
382+ [
383+ "zero",
384+ "one",
385+ "two"
386+ ]
387+ ]
388+ },
389+ "result": { "kind": "Array", "value": [] }
390+ },
391+ {
392+ "expr": "foo[*][' 0x01 ']",
393+ "contexts": {
394+ "foo": [
395+ [
396+ "zero",
397+ "one",
398+ "two"
399+ ]
400+ ]
401+ },
402+ "result": { "kind": "Array", "value": ["one"] }
403+ },
404+ {
405+ "expr": "foo[*][fromjson('[]')]",
406+ "contexts": {
407+ "foo": [
408+ [
409+ "zero",
410+ "one",
411+ "two"
412+ ]
413+ ]
414+ },
415+ "result": { "kind": "Array", "value": [] }
416+ },
417+ {
418+ "expr": "foo[*][fromjson('{}')]",
419+ "contexts": {
420+ "foo": [
421+ [
422+ "zero",
423+ "one",
424+ "two"
425+ ]
426+ ]
427+ },
428+ "result": { "kind": "Array", "value": [] }
429+ }
430+ ],
431+ "double-star": [
432+ {
433+ "expr": "foo.*.*",
434+ "contexts": {
435+ "foo": [
436+ null,
437+ false,
438+ true,
439+ 123,
440+ "hello",
441+ [
442+ "zero",
443+ "one",
444+ "two"
445+ ],
446+ {
447+ "p1": "v1",
448+ "p2": "v2",
449+ "p3": "v3"
450+ }
451+ ]
452+ },
453+ "result": { "kind": "Array", "value": ["zero", "one", "two", "v1", "v2", "v3"] }
454+ }
455+ ],
456+ "double-property": [
457+ {
458+ "expr": "foo.*.p.nestedp",
459+ "contexts": {
460+ "foo": [
461+ {
462+ "p": {
463+ "nestedp": "val 1"
464+ }
465+ },
466+ {
467+ "p": {
468+ "nestedp": "val 2"
469+ }
470+ }
471+ ]
472+ },
473+ "result": { "kind": "Array", "value": ["val 1", "val 2"] }
474+ }
475+ ],
476+ "double-index": [
477+ {
478+ "expr": "foo[*][1][2]",
479+ "contexts": {
480+ "foo": [
481+ [
482+ [
483+ "0_0_0",
484+ "0_0_1",
485+ "0_0_2",
486+ "0_0_3"
487+ ],
488+ [
489+ "0_1_0",
490+ "0_1_1",
491+ "0_1_2",
492+ "0_1_3"
493+ ],
494+ [
495+ "0_2_0",
496+ "0_2_1",
497+ "0_2_2",
498+ "0_2_3"
499+ ]
500+ ],
501+ [
502+ [
503+ "1_0_0",
504+ "1_0_1",
505+ "1_0_2",
506+ "1_0_3"
507+ ],
508+ [
509+ "1_1_0",
510+ "1_1_1",
511+ "1_1_2",
512+ "1_1_3"
513+ ],
514+ [
515+ "1_2_0",
516+ "1_2_1",
517+ "1_2_2",
518+ "1_2_3"
519+ ]
520+ ]
521+ ]
522+ },
523+ "result": { "kind": "Array", "value": ["0_1_2", "1_1_2"] }
524+ }
525+ ],
526+ "not-found-always-returns-empty-array": [
527+ {
528+ "expr": "foo[*][1]",
529+ "contexts": {
530+ "foo": []
531+ },
532+ "result": { "kind": "Array", "value": [] }
533+ },
534+ {
535+ "expr": "foo[*][1][2]",
536+ "contexts": {
537+ "foo": []
538+ },
539+ "result": { "kind": "Array", "value": [] }
540+ },
541+ {
542+ "expr": "foo.*.bar",
543+ "contexts": {
544+ "foo": {}
545+ },
546+ "result": { "kind": "Array", "value": [] }
547+ },
548+ {
549+ "expr": "foo.*.bar.baz",
550+ "contexts": {
551+ "foo": []
552+ },
553+ "result": { "kind": "Array", "value": [] }
554+ }
555+ ]
556+}
A expr/testdata/languageservices/op_lt.json +378 -0
@@ -0,0 +1,378 @@
1+{
2+ "bool": [
3+ {
4+ "expr": "false < true",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ {
8+ "expr": "false < false",
9+ "result": { "kind": "Boolean", "value": false }
10+ },
11+ {
12+ "expr": "true < false",
13+ "result": { "kind": "Boolean", "value": false }
14+ },
15+ {
16+ "expr": "true < true",
17+ "result": { "kind": "Boolean", "value": false }
18+ }
19+ ],
20+
21+ "number": [
22+ {
23+ "expr": "1 < 2",
24+ "result": { "kind": "Boolean", "value": true }
25+ },
26+ {
27+ "expr": "1 < 1",
28+ "result": { "kind": "Boolean", "value": false }
29+ },
30+ {
31+ "expr": "2 < 1",
32+ "result": { "kind": "Boolean", "value": false }
33+ },
34+ {
35+ "expr": "1.001 < 1.002",
36+ "result": { "kind": "Boolean", "value": true }
37+ }
38+ ],
39+
40+ "string": [
41+ {
42+ "expr": "'abc' < 'def'",
43+ "result": { "kind": "Boolean", "value": true }
44+ },
45+ {
46+ "expr": "'b' < 'a'",
47+ "result": { "kind": "Boolean", "value": false }
48+ }
49+ ],
50+
51+ "array": [
52+ {
53+ "expr": "test < test",
54+ "contexts": { "test": [] },
55+ "result": { "kind": "Boolean", "value": false }
56+ }
57+ ],
58+
59+ "object": [
60+ {
61+ "expr": "test < test",
62+ "contexts": { "test": {} },
63+ "result": { "kind": "Boolean", "value": false }
64+ }
65+ ],
66+
67+ "coerce_bool_number": [
68+ {
69+ "expr": "false < 0",
70+ "result": { "kind": "Boolean", "value": false }
71+ },
72+ {
73+ "expr": "false < 1",
74+ "result": { "kind": "Boolean", "value": true }
75+ },
76+ {
77+ "expr": "true < 1",
78+ "result": { "kind": "Boolean", "value": false }
79+ },
80+ {
81+ "expr": "true < 2",
82+ "result": { "kind": "Boolean", "value": true }
83+ },
84+ {
85+ "expr": "false < NaN",
86+ "result": { "kind": "Boolean", "value": false }
87+ },
88+ {
89+ "expr": "true < NaN",
90+ "result": { "kind": "Boolean", "value": false }
91+ },
92+ {
93+ "expr": "false < Infinity",
94+ "result": { "kind": "Boolean", "value": true }
95+ },
96+ {
97+ "expr": "true < Infinity",
98+ "result": { "kind": "Boolean", "value": true }
99+ },
100+ {
101+ "expr": "false < -Infinity",
102+ "result": { "kind": "Boolean", "value": false }
103+ },
104+ {
105+ "expr": "true < -Infinity",
106+ "result": { "kind": "Boolean", "value": false }
107+ }
108+ ],
109+
110+ "coerce_bool_string": [
111+ {
112+ "expr": "false < '0'",
113+ "result": { "kind": "Boolean", "value": false }
114+ },
115+ {
116+ "expr": "false < '1'",
117+ "result": { "kind": "Boolean", "value": true }
118+ },
119+ {
120+ "expr": "true < '1'",
121+ "result": { "kind": "Boolean", "value": false }
122+ },
123+ {
124+ "expr": "true < '2'",
125+ "result": { "kind": "Boolean", "value": true }
126+ }
127+ ],
128+
129+ "coerce_bool_null": [
130+ {
131+ "expr": "false < null",
132+ "result": { "kind": "Boolean", "value": false }
133+ },
134+ {
135+ "expr": "true < null",
136+ "result": { "kind": "Boolean", "value": false }
137+ }
138+ ],
139+
140+ "coerce_bool_object": [
141+ {
142+ "expr": "false < test",
143+ "contexts": { "test": {} },
144+ "result": { "kind": "Boolean", "value": false }
145+ },
146+ {
147+ "expr": "true < test",
148+ "contexts": { "test": {} },
149+ "result": { "kind": "Boolean", "value": false }
150+ }
151+ ],
152+
153+ "coerce_bool_array": [
154+ {
155+ "expr": "false < test",
156+ "contexts": { "test": [] },
157+ "result": { "kind": "Boolean", "value": false }
158+ },
159+ {
160+ "expr": "true < test",
161+ "contexts": { "test": [] },
162+ "result": { "kind": "Boolean", "value": false }
163+ }
164+ ],
165+
166+ "coerce_number_bool": [
167+ {
168+ "expr": "-1 < false",
169+ "result": { "kind": "Boolean", "value": true }
170+ },
171+ {
172+ "expr": "0 < false",
173+ "result": { "kind": "Boolean", "value": false }
174+ },
175+ {
176+ "expr": "0 < true",
177+ "result": { "kind": "Boolean", "value": true }
178+ },
179+ {
180+ "expr": "1 < true",
181+ "result": { "kind": "Boolean", "value": false }
182+ },
183+ {
184+ "expr": "NaN < false",
185+ "result": { "kind": "Boolean", "value": false }
186+ },
187+ {
188+ "expr": "NaN < true",
189+ "result": { "kind": "Boolean", "value": false }
190+ },
191+ {
192+ "expr": "Infinity < false",
193+ "result": { "kind": "Boolean", "value": false }
194+ },
195+ {
196+ "expr": "Infinity < true",
197+ "result": { "kind": "Boolean", "value": false }
198+ },
199+ {
200+ "expr": "-Infinity < false",
201+ "result": { "kind": "Boolean", "value": true }
202+ },
203+ {
204+ "expr": "-Infinity < true",
205+ "result": { "kind": "Boolean", "value": true }
206+ }
207+ ],
208+
209+ "coerce_number_string": [
210+ {
211+ "expr": "0 < ' 0 '",
212+ "result": { "kind": "Boolean", "value": false }
213+ },
214+ {
215+ "expr": "0 < ' 1 '",
216+ "result": { "kind": "Boolean", "value": true }
217+ },
218+ {
219+ "expr": "1 < ' 1 '",
220+ "result": { "kind": "Boolean", "value": false }
221+ },
222+ {
223+ "expr": "1 < ' 2 '",
224+ "result": { "kind": "Boolean", "value": true }
225+ }
226+ ],
227+
228+ "coerce_number_null": [
229+ {
230+ "expr": "-1 < null",
231+ "result": { "kind": "Boolean", "value": true }
232+ },
233+ {
234+ "expr": "0 < null",
235+ "result": { "kind": "Boolean", "value": false }
236+ }
237+ ],
238+
239+ "coerce_number_object": [
240+ {
241+ "expr": "0 < test",
242+ "contexts": { "test": {} },
243+ "result": { "kind": "Boolean", "value": false }
244+ },
245+ {
246+ "expr": "-1 < test",
247+ "contexts": { "test": {} },
248+ "result": { "kind": "Boolean", "value": false }
249+ }
250+ ],
251+
252+ "coerce_number_array": [
253+ {
254+ "expr": "0 < test",
255+ "contexts": { "test": [] },
256+ "result": { "kind": "Boolean", "value": false }
257+ },
258+ {
259+ "expr": "-1 < test",
260+ "contexts": { "test": [] },
261+ "result": { "kind": "Boolean", "value": false }
262+ }
263+ ],
264+
265+ "coerce_string_bool": [
266+ {
267+ "expr": "'0' < false",
268+ "result": { "kind": "Boolean", "value": false }
269+ },
270+ {
271+ "expr": "'-1' < false",
272+ "result": { "kind": "Boolean", "value": true }
273+ },
274+ {
275+ "expr": "'1' < true",
276+ "result": { "kind": "Boolean", "value": false }
277+ },
278+ {
279+ "expr": "'0' < true",
280+ "result": { "kind": "Boolean", "value": true }
281+ }
282+ ],
283+
284+ "coerce_string_number": [
285+ {
286+ "expr": "' 0 ' < 1",
287+ "result": { "kind": "Boolean", "value": true }
288+ },
289+ {
290+ "expr": "' 0 ' < 0",
291+ "result": { "kind": "Boolean", "value": false }
292+ },
293+ {
294+ "expr": "' 1 ' < 2",
295+ "result": { "kind": "Boolean", "value": true }
296+ },
297+ {
298+ "expr": "' 1 ' < 1",
299+ "result": { "kind": "Boolean", "value": false }
300+ }
301+ ],
302+
303+ "coerce_string_null": [
304+ {
305+ "expr": "'' < null",
306+ "result": { "kind": "Boolean", "value": false }
307+ },
308+ {
309+ "expr": "'-1' < null",
310+ "result": { "kind": "Boolean", "value": true }
311+ }
312+ ],
313+
314+ "coerce_string_object": [
315+ {
316+ "expr": "'' < test",
317+ "contexts": { "test": {} },
318+ "result": { "kind": "Boolean", "value": false }
319+ }
320+ ],
321+
322+ "coerce_string_array": [
323+ {
324+ "expr": "'' < test",
325+ "contexts": { "test": [] },
326+ "result": { "kind": "Boolean", "value": false }
327+ }
328+ ],
329+
330+ "coerce_null_bool": [
331+ {
332+ "expr": "null < false",
333+ "result": { "kind": "Boolean", "value": false }
334+ },
335+ {
336+ "expr": "null < true",
337+ "result": { "kind": "Boolean", "value": true }
338+ }
339+ ],
340+
341+ "coerce_null_number": [
342+ {
343+ "expr": "null < 0",
344+ "result": { "kind": "Boolean", "value": false }
345+ },
346+ {
347+ "expr": "null < 1",
348+ "result": { "kind": "Boolean", "value": true }
349+ }
350+ ],
351+
352+ "coerce_null_string": [
353+ {
354+ "expr": "null < ' 0 '",
355+ "result": { "kind": "Boolean", "value": false }
356+ },
357+ {
358+ "expr": "null < ' 1 '",
359+ "result": { "kind": "Boolean", "value": true }
360+ }
361+ ],
362+
363+ "coerce_null_object": [
364+ {
365+ "expr": "null < test",
366+ "contexts": { "test": {} },
367+ "result": { "kind": "Boolean", "value": false }
368+ }
369+ ],
370+
371+ "coerce_null_array": [
372+ {
373+ "expr": "null < test",
374+ "contexts": { "test": [] },
375+ "result": { "kind": "Boolean", "value": false }
376+ }
377+ ]
378+}⏎̸
A expr/testdata/languageservices/op_lte.json +20 -0
@@ -0,0 +1,20 @@
1+{
2+ "bool": [
3+ {
4+ "expr": "false <= true",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ {
8+ "expr": "false <= false",
9+ "result": { "kind": "Boolean", "value": true }
10+ },
11+ {
12+ "expr": "true <= false",
13+ "result": { "kind": "Boolean", "value": false }
14+ },
15+ {
16+ "expr": "true <= true",
17+ "result": { "kind": "Boolean", "value": true }
18+ }
19+ ]
20+}⏎̸
A expr/testdata/languageservices/op_ne.json +597 -0
@@ -0,0 +1,597 @@
1+{
2+ "boolean": [
3+ {
4+ "expr": "true != true",
5+ "result": { "kind": "Boolean", "value": false }
6+ },
7+ {
8+ "expr": "false != false",
9+ "result": { "kind": "Boolean", "value": false }
10+ },
11+ {
12+ "expr": "true != true",
13+ "result": { "kind": "Boolean", "value": false }
14+ }
15+ ],
16+
17+ "number": [
18+ {
19+ "expr": "0 != -0",
20+ "result": { "kind": "Boolean", "value": false }
21+ },
22+ {
23+ "expr": "2 != 2",
24+ "result": { "kind": "Boolean", "value": false }
25+ },
26+ {
27+ "expr": "120 != 1.2e2",
28+ "result": { "kind": "Boolean", "value": false }
29+ },
30+ {
31+ "expr": "1 != 2",
32+ "result": { "kind": "Boolean", "value": true }
33+ },
34+ {
35+ "expr": "1.001 != 1.002",
36+ "result": { "kind": "Boolean", "value": true }
37+ },
38+ {
39+ "expr": "NaN != NaN",
40+ "result": { "kind": "Boolean", "value": true }
41+ },
42+ {
43+ "expr": "0 != NaN",
44+ "result": { "kind": "Boolean", "value": true }
45+ },
46+ {
47+ "expr": "1 != NaN",
48+ "result": { "kind": "Boolean", "value": true }
49+ },
50+ {
51+ "expr": "Infinity != Infinity",
52+ "result": { "kind": "Boolean", "value": false }
53+ },
54+ {
55+ "expr": "-Infinity != Infinity",
56+ "result": { "kind": "Boolean", "value": true }
57+ },
58+ {
59+ "expr": "0 != Infinity",
60+ "result": { "kind": "Boolean", "value": true }
61+ },
62+ {
63+ "expr": "1 != Infinity",
64+ "result": { "kind": "Boolean", "value": true }
65+ }
66+ ],
67+
68+ "string": [
69+ {
70+ "expr": "'a' != 'a'",
71+ "result": { "kind": "Boolean", "value": false }
72+ },
73+ {
74+ "expr": "'a' != 'b'",
75+ "result": { "kind": "Boolean", "value": true }
76+ }
77+ ],
78+
79+ "null": [
80+ { "expr": "null != null", "result": { "kind": "Boolean", "value": false } }
81+ ],
82+
83+ "object": [
84+ {
85+ "expr": "object1 != object1",
86+ "contexts": { "object1": {} },
87+ "result": { "kind": "Boolean", "value": false }
88+ },
89+ {
90+ "expr": "object1 != object2",
91+ "contexts": { "object1": {}, "object2": {} },
92+ "result": { "kind": "Boolean", "value": true }
93+ }
94+ ],
95+
96+ "array": [
97+ {
98+ "expr": "array1 != array1",
99+ "contexts": { "array1": [] },
100+ "result": { "kind": "Boolean", "value": false }
101+ },
102+ {
103+ "expr": "array1 != array2",
104+ "contexts": { "array1": [], "array2": [] },
105+ "result": { "kind": "Boolean", "value": true }
106+ }
107+ ],
108+
109+ "coerce_bool_number": [
110+ {
111+ "expr": "false != 0",
112+ "result": { "kind": "Boolean", "value": false }
113+ },
114+ {
115+ "expr": "false != 1",
116+ "result": { "kind": "Boolean", "value": true }
117+ },
118+ {
119+ "expr": "true != 1",
120+ "result": { "kind": "Boolean", "value": false }
121+ },
122+ {
123+ "expr": "false != NaN",
124+ "result": { "kind": "Boolean", "value": true }
125+ },
126+ {
127+ "expr": "true != 2",
128+ "result": { "kind": "Boolean", "value": true }
129+ }
130+ ],
131+
132+ "coerce_bool_string": [
133+ {
134+ "expr": "false != ''",
135+ "result": { "kind": "Boolean", "value": false }
136+ },
137+ {
138+ "expr": "false != ' '",
139+ "result": { "kind": "Boolean", "value": false }
140+ },
141+ {
142+ "expr": "false != ' 0.0 '",
143+ "result": { "kind": "Boolean", "value": false }
144+ },
145+ {
146+ "expr": "false != ' 1.0 '",
147+ "result": { "kind": "Boolean", "value": true }
148+ },
149+ {
150+ "expr": "true != ' 1.0 '",
151+ "result": { "kind": "Boolean", "value": false }
152+ },
153+ {
154+ "expr": "true != ' 1 '",
155+ "result": { "kind": "Boolean", "value": false }
156+ },
157+ {
158+ "expr": "false != '-1'",
159+ "result": { "kind": "Boolean", "value": true }
160+ },
161+ {
162+ "expr": "true != '2'",
163+ "result": { "kind": "Boolean", "value": true }
164+ }
165+ ],
166+
167+ "coerce_bool_null": [
168+ {
169+ "expr": "false != null",
170+ "result": { "kind": "Boolean", "value": false }
171+ },
172+ {
173+ "expr": "true != null",
174+ "result": { "kind": "Boolean", "value": true }
175+ }
176+ ],
177+
178+ "coerce_bool_object-array": [
179+ {
180+ "expr": "false != test",
181+ "contexts": { "test": {} },
182+ "result": { "kind": "Boolean", "value": true }
183+ },
184+ {
185+ "expr": "false != test",
186+ "contexts": { "test": [] },
187+ "result": { "kind": "Boolean", "value": true }
188+ },
189+ {
190+ "expr": "true != test",
191+ "contexts": { "test": {} },
192+ "result": { "kind": "Boolean", "value": true }
193+ },
194+ {
195+ "expr": "true != test",
196+ "contexts": { "test": [] },
197+ "result": { "kind": "Boolean", "value": true }
198+ }
199+ ],
200+
201+ "coerce_number_bool": [
202+ {
203+ "expr": "0 != false",
204+ "result": { "kind": "Boolean", "value": false }
205+ },
206+ {
207+ "expr": "1 != false",
208+ "result": { "kind": "Boolean", "value": true }
209+ },
210+ {
211+ "expr": "1 != true",
212+ "result": { "kind": "Boolean", "value": false }
213+ },
214+ {
215+ "expr": "2 != true",
216+ "result": { "kind": "Boolean", "value": true }
217+ },
218+ {
219+ "expr": "NaN != false",
220+ "result": { "kind": "Boolean", "value": true }
221+ }
222+ ],
223+
224+ "coerce_number_string": [
225+ {
226+ "expr": "0 != ' -0.0 '",
227+ "result": { "kind": "Boolean", "value": false }
228+ },
229+ {
230+ "expr": "0 != ''",
231+ "result": { "kind": "Boolean", "value": false }
232+ },
233+ {
234+ "expr": "0 != ' '",
235+ "result": { "kind": "Boolean", "value": false }
236+ },
237+ {
238+ "expr": "120 != ' +1.2e2 '",
239+ "result": { "kind": "Boolean", "value": false }
240+ },
241+ {
242+ "expr": "120.0 != ' 1.2E2 '",
243+ "result": { "kind": "Boolean", "value": false }
244+ },
245+ {
246+ "expr": "120 != ' 1.2e+2 '",
247+ "result": { "kind": "Boolean", "value": false }
248+ },
249+ {
250+ "expr": "-120 != ' -1.2E+2 '",
251+ "result": { "kind": "Boolean", "value": false }
252+ },
253+ {
254+ "expr": "0.012 != ' 1.2e-2 '",
255+ "result": { "kind": "Boolean", "value": false }
256+ },
257+ {
258+ "expr": "0.012 != ' 1.2E-2 '",
259+ "result": { "kind": "Boolean", "value": false }
260+ },
261+ {
262+ "expr": "255.0 != ' 0xff '",
263+ "result": { "kind": "Boolean", "value": false }
264+ },
265+ {
266+ "expr": "8.0 != ' 0o10 '",
267+ "result": { "kind": "Boolean", "value": false }
268+ },
269+ {
270+ "expr": "Infinity != ' Infinity '",
271+ "result": { "kind": "Boolean", "value": false }
272+ },
273+ {
274+ "expr": "-Infinity != ' -Infinity '",
275+ "result": { "kind": "Boolean", "value": false }
276+ },
277+ {
278+ "expr": "NaN != 'NaN'",
279+ "result": { "kind": "Boolean", "value": true }
280+ },
281+ {
282+ "expr": "1 != '0'",
283+ "result": { "kind": "Boolean", "value": true }
284+ },
285+ {
286+ "expr": "1 != '1'",
287+ "result": { "kind": "Boolean", "value": false }
288+ },
289+ {
290+ "expr": "1 != 'a'",
291+ "result": { "kind": "Boolean", "value": true }
292+ },
293+ {
294+ "expr": "0 != 'a'",
295+ "result": { "kind": "Boolean", "value": true }
296+ }
297+ ],
298+
299+ "coerce_number_null": [
300+ {
301+ "expr": "0 != null",
302+ "result": { "kind": "Boolean", "value": false }
303+ },
304+ {
305+ "expr": "1 != null",
306+ "result": { "kind": "Boolean", "value": true }
307+ }
308+ ],
309+
310+ "coerce_number_object-array": [
311+ {
312+ "expr": "0 != test",
313+ "contexts": { "test": {} },
314+ "result": { "kind": "Boolean", "value": true }
315+ },
316+ {
317+ "expr": "1 != test",
318+ "contexts": { "test": {} },
319+ "result": { "kind": "Boolean", "value": true }
320+ },
321+ {
322+ "expr": "0 != test",
323+ "contexts": { "test": [] },
324+ "result": { "kind": "Boolean", "value": true }
325+ },
326+ {
327+ "expr": "1 != test",
328+ "contexts": { "test": [] },
329+ "result": { "kind": "Boolean", "value": true }
330+ }
331+ ],
332+
333+ "coerce_string_bool": [
334+ {
335+ "expr": "'' != false",
336+ "result": { "kind": "Boolean", "value": false }
337+ },
338+ {
339+ "expr": "' ' != false",
340+ "result": { "kind": "Boolean", "value": false }
341+ },
342+ {
343+ "expr": "' 0.0 ' != false",
344+ "result": { "kind": "Boolean", "value": false }
345+ },
346+ {
347+ "expr": "' 1.0 ' != false",
348+ "result": { "kind": "Boolean", "value": true }
349+ },
350+ {
351+ "expr": "' 1.0 ' != true",
352+ "result": { "kind": "Boolean", "value": false }
353+ },
354+ {
355+ "expr": "' 1 ' != true",
356+ "result": { "kind": "Boolean", "value": false }
357+ },
358+ {
359+ "expr": "'-1' != true",
360+ "result": { "kind": "Boolean", "value": true }
361+ },
362+ {
363+ "expr": "'2' != true",
364+ "result": { "kind": "Boolean", "value": true }
365+ }
366+ ],
367+
368+ "coerce_string_number": [
369+ {
370+ "expr": "' -0.0 ' != 0",
371+ "result": { "kind": "Boolean", "value": false }
372+ },
373+ {
374+ "expr": "'' != 0",
375+ "result": { "kind": "Boolean", "value": false }
376+ },
377+ {
378+ "expr": "' ' != 0",
379+ "result": { "kind": "Boolean", "value": false }
380+ },
381+ {
382+ "expr": "' +1.2e2 ' != 120",
383+ "result": { "kind": "Boolean", "value": false }
384+ },
385+ {
386+ "expr": "' 1.2E2 ' != 120.0",
387+ "result": { "kind": "Boolean", "value": false }
388+ },
389+ {
390+ "expr": "' 1.2e+2 ' != 120",
391+ "result": { "kind": "Boolean", "value": false }
392+ },
393+ {
394+ "expr": "' -1.2E+2 ' != -120",
395+ "result": { "kind": "Boolean", "value": false }
396+ },
397+ {
398+ "expr": "' 1.2e-2 ' != 0.012",
399+ "result": { "kind": "Boolean", "value": false }
400+ },
401+ {
402+ "expr": "' 1.2E-2 ' != 0.012",
403+ "result": { "kind": "Boolean", "value": false }
404+ },
405+ {
406+ "expr": "' 0xff ' != 255.0",
407+ "result": { "kind": "Boolean", "value": false }
408+ },
409+ {
410+ "expr": "' 0o10 ' != 8.0",
411+ "result": { "kind": "Boolean", "value": false }
412+ },
413+ {
414+ "expr": "' Infinity ' != Infinity",
415+ "result": { "kind": "Boolean", "value": false }
416+ },
417+ {
418+ "expr": "' -Infinity ' != -Infinity",
419+ "result": { "kind": "Boolean", "value": false }
420+ },
421+ {
422+ "expr": "'NaN' != NaN",
423+ "result": { "kind": "Boolean", "value": true }
424+ },
425+ {
426+ "expr": "'0' != 1",
427+ "result": { "kind": "Boolean", "value": true }
428+ },
429+ {
430+ "expr": "'1' != 1",
431+ "result": { "kind": "Boolean", "value": false }
432+ },
433+ {
434+ "expr": "'a' != 1",
435+ "result": { "kind": "Boolean", "value": true }
436+ },
437+ {
438+ "expr": "'a' != 0",
439+ "result": { "kind": "Boolean", "value": true }
440+ }
441+ ],
442+
443+ "coerce_string_null": [
444+ {
445+ "expr": "'' != null",
446+ "result": { "kind": "Boolean", "value": false }
447+ },
448+ {
449+ "expr": "' ' != null",
450+ "result": { "kind": "Boolean", "value": false }
451+ },
452+ {
453+ "expr": "'0' != null",
454+ "result": { "kind": "Boolean", "value": false }
455+ },
456+ {
457+ "expr": "'1' != null",
458+ "result": { "kind": "Boolean", "value": true }
459+ },
460+ {
461+ "expr": "'false' != null",
462+ "result": { "kind": "Boolean", "value": true }
463+ },
464+ {
465+ "expr": "'null' != null",
466+ "result": { "kind": "Boolean", "value": true }
467+ }
468+ ],
469+
470+ "coerce_string_object": [
471+ {
472+ "expr": "'' != test",
473+ "contexts": { "test": {} },
474+ "result": { "kind": "Boolean", "value": true }
475+ },
476+ {
477+ "expr": "'false' != test",
478+ "contexts": { "test": {} },
479+ "result": { "kind": "Boolean", "value": true }
480+ },
481+ {
482+ "expr": "'0' != test",
483+ "contexts": { "test": {} },
484+ "result": { "kind": "Boolean", "value": true }
485+ },
486+ {
487+ "expr": "'1' != test",
488+ "contexts": { "test": {} },
489+ "result": { "kind": "Boolean", "value": true }
490+ },
491+ {
492+ "expr": "'null' != test",
493+ "contexts": { "test": {} },
494+ "result": { "kind": "Boolean", "value": true }
495+ },
496+ {
497+ "expr": "'Object' != test",
498+ "contexts": { "test": {} },
499+ "result": { "kind": "Boolean", "value": true }
500+ }
501+ ],
502+
503+ "coerce_string_array": [
504+ {
505+ "expr": "'' != test",
506+ "contexts": { "test": [] },
507+ "result": { "kind": "Boolean", "value": true }
508+ },
509+ {
510+ "expr": "'false' != test",
511+ "contexts": { "test": [] },
512+ "result": { "kind": "Boolean", "value": true }
513+ },
514+ {
515+ "expr": "'0' != test",
516+ "contexts": { "test": [] },
517+ "result": { "kind": "Boolean", "value": true }
518+ },
519+ {
520+ "expr": "'0' != test",
521+ "contexts": { "test": [] },
522+ "result": { "kind": "Boolean", "value": true }
523+ },
524+ {
525+ "expr": "'null' != test",
526+ "contexts": { "test": [] },
527+ "result": { "kind": "Boolean", "value": true }
528+ },
529+ {
530+ "expr": "'Array' != test",
531+ "contexts": { "test": [] },
532+ "result": { "kind": "Boolean", "value": true }
533+ }
534+ ],
535+
536+ "coerce_null_bool": [
537+ {
538+ "expr": "null != false",
539+ "result": { "kind": "Boolean", "value": false }
540+ },
541+ {
542+ "expr": "null != true",
543+ "result": { "kind": "Boolean", "value": true }
544+ }
545+ ],
546+
547+ "coerce_null_number": [
548+ {
549+ "expr": "null != 0",
550+ "result": { "kind": "Boolean", "value": false }
551+ },
552+ {
553+ "expr": "null != 1",
554+ "result": { "kind": "Boolean", "value": true }
555+ }
556+ ],
557+
558+ "coerce_null_string": [
559+ {
560+ "expr": "null != ''",
561+ "result": { "kind": "Boolean", "value": false }
562+ },
563+ {
564+ "expr": "null != ' '",
565+ "result": { "kind": "Boolean", "value": false }
566+ },
567+ {
568+ "expr": "null != '0'",
569+ "result": { "kind": "Boolean", "value": false }
570+ },
571+ {
572+ "expr": "null != '1'",
573+ "result": { "kind": "Boolean", "value": true }
574+ },
575+ {
576+ "expr": "null != 'false'",
577+ "result": { "kind": "Boolean", "value": true }
578+ },
579+ {
580+ "expr": "null != 'null'",
581+ "result": { "kind": "Boolean", "value": true }
582+ }
583+ ],
584+
585+ "coerce_null_object-array": [
586+ {
587+ "expr": "null != test",
588+ "contexts": { "test": {} },
589+ "result": { "kind": "Boolean", "value": true }
590+ },
591+ {
592+ "expr": "null != test",
593+ "contexts": { "test": [] },
594+ "result": { "kind": "Boolean", "value": true }
595+ }
596+ ]
597+}
A expr/testdata/languageservices/op_not.json +28 -0
@@ -0,0 +1,28 @@
1+{
2+ "not": [
3+ { "expr": "!!true", "result": { "kind": "Boolean", "value": true } },
4+ { "expr": "!!false", "result": { "kind": "Boolean", "value": false } },
5+ { "expr": "!!1", "result": { "kind": "Boolean", "value": true } },
6+ { "expr": "!!.5", "result": { "kind": "Boolean", "value": true } },
7+ { "expr": "!!0.5", "result": { "kind": "Boolean", "value": true } },
8+ { "expr": "!!2", "result": { "kind": "Boolean", "value": true } },
9+ { "expr": "!!-1", "result": { "kind": "Boolean", "value": true } },
10+ { "expr": "!!-.5", "result": { "kind": "Boolean", "value": true } },
11+ { "expr": "!!-2", "result": { "kind": "Boolean", "value": true } },
12+ { "expr": "!!-Infinity", "result": { "kind": "Boolean", "value": true } },
13+ { "expr": "!!Infinity", "result": { "kind": "Boolean", "value": true } },
14+ { "expr": "!!NaN", "result": { "kind": "Boolean", "value": false } },
15+ { "expr": "!!0", "result": { "kind": "Boolean", "value": false } },
16+ { "expr": "!!0.0", "result": { "kind": "Boolean", "value": false } },
17+ { "expr": "!!-0", "result": { "kind": "Boolean", "value": false } },
18+ { "expr": "!!-0.0", "result": { "kind": "Boolean", "value": false } },
19+ { "expr": "!!'a'", "result": { "kind": "Boolean", "value": true } },
20+ { "expr": "!!'false'", "result": { "kind": "Boolean", "value": true } },
21+ { "expr": "!!'0'", "result": { "kind": "Boolean", "value": true } },
22+ { "expr": "!!' '", "result": { "kind": "Boolean", "value": true } },
23+ { "expr": "!!''", "result": { "kind": "Boolean", "value": false } },
24+ { "expr": "!!fromJson('[]')", "result": { "kind": "Boolean", "value": true } },
25+ { "expr": "!!fromJson('{}')", "result": { "kind": "Boolean", "value": true } },
26+ { "expr": "!!null", "result": { "kind": "Boolean", "value": false } }
27+ ]
28+}
A expr/testdata/languageservices/op_or.json +31 -0
@@ -0,0 +1,31 @@
1+{
2+ "or": [
3+ {
4+ "expr": "true || true || true",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ { "expr": "true || true", "result": { "kind": "Boolean", "value": true } },
8+ {
9+ "expr": "true || true || false",
10+ "result": { "kind": "Boolean", "value": true }
11+ },
12+ { "expr": "true || false", "result": { "kind": "Boolean", "value": true } },
13+ { "expr": "false || true", "result": { "kind": "Boolean", "value": true } },
14+ {
15+ "expr": "false || false",
16+ "result": { "kind": "Boolean", "value": false }
17+ },
18+ {
19+ "expr": "0 || 0 || 2 || 3",
20+ "result": { "kind": "Number", "value": 2.0 }
21+ },
22+ { "expr": "false || 0", "result": { "kind": "Number", "value": 0.0 } },
23+ {
24+ "expr": "false || '' || 'a' || 'b'",
25+ "result": { "kind": "String", "value": "a" }
26+ },
27+ { "expr": "false || ''", "result": { "kind": "String", "value": "" } },
28+ { "expr": "null || true", "result": { "kind": "Boolean", "value": true } },
29+ { "expr": "false || null", "result": { "kind": "Null", "value": null } }
30+ ]
31+}
A expr/testdata/languageservices/operators_case_insensitive.json +376 -0
@@ -0,0 +1,376 @@
1+{
2+ "lower-vs-upper": [
3+ {
4+ "expr": "'abcdefghijklmnopqrstuvwxyz' == 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ {
8+ "expr": "'abcdefghijklmnopqrstuvwxyz' != 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
9+ "result": { "kind": "Boolean", "value": false }
10+ },
11+ {
12+ "expr": "'abcdefghijklmnopqrstuvwxyz' < 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
13+ "result": { "kind": "Boolean", "value": false }
14+ },
15+ {
16+ "expr": "'abcdefghijklmnopqrstuvwxyz' <= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
17+ "result": { "kind": "Boolean", "value": true }
18+ },
19+ {
20+ "expr": "'abcdefghijklmnopqrstuvwxyz' > 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
21+ "result": { "kind": "Boolean", "value": false }
22+ },
23+ {
24+ "expr": "'abcdefghijklmnopqrstuvwxyz' >= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'",
25+ "result": { "kind": "Boolean", "value": true }
26+ }
27+ ],
28+
29+ "upper-vs-lower": [
30+ {
31+ "expr": "'ABCDEFGHIJKLMNOPQRSTUVWXYZ' == 'abcdefghijklmnopqrstuvwxyz'",
32+ "result": { "kind": "Boolean", "value": true }
33+ },
34+ {
35+ "expr": "'ABCDEFGHIJKLMNOPQRSTUVWXYZ' != 'abcdefghijklmnopqrstuvwxyz'",
36+ "result": { "kind": "Boolean", "value": false }
37+ },
38+ {
39+ "expr": "'ABCDEFGHIJKLMNOPQRSTUVWXYZ' < 'abcdefghijklmnopqrstuvwxyz'",
40+ "result": { "kind": "Boolean", "value": false }
41+ },
42+ {
43+ "expr": "'ABCDEFGHIJKLMNOPQRSTUVWXYZ' <= 'abcdefghijklmnopqrstuvwxyz'",
44+ "result": { "kind": "Boolean", "value": true }
45+ },
46+ {
47+ "expr": "'ABCDEFGHIJKLMNOPQRSTUVWXYZ' > 'abcdefghijklmnopqrstuvwxyz'",
48+ "result": { "kind": "Boolean", "value": false }
49+ },
50+ {
51+ "expr": "'ABCDEFGHIJKLMNOPQRSTUVWXYZ' >= 'abcdefghijklmnopqrstuvwxyz'",
52+ "result": { "kind": "Boolean", "value": true }
53+ }
54+ ],
55+
56+ "a-z-equivalent-to-A-Z-wrt-chars-between-Z-and-a": [
57+ {
58+ "expr": "'A' < '['",
59+ "result": { "kind": "Boolean", "value": true }
60+ },
61+ {
62+ "expr": "'A' <= '['",
63+ "result": { "kind": "Boolean", "value": true }
64+ },
65+ {
66+ "expr": "'A' > '['",
67+ "result": { "kind": "Boolean", "value": false }
68+ },
69+ {
70+ "expr": "'A' >= '['",
71+ "result": { "kind": "Boolean", "value": false }
72+ },
73+ {
74+ "expr": "'a' < '['",
75+ "result": { "kind": "Boolean", "value": true }
76+ },
77+ {
78+ "expr": "'a' <= '['",
79+ "result": { "kind": "Boolean", "value": true }
80+ },
81+ {
82+ "expr": "'a' > '['",
83+ "result": { "kind": "Boolean", "value": false }
84+ },
85+ {
86+ "expr": "'a' >= '['",
87+ "result": { "kind": "Boolean", "value": false }
88+ }
89+ ],
90+
91+ "case-insensitive-ς-(final-lowercase-sigma)-Σ-(capital-sigma)-σ-(non-final-sigma)": [
92+ {
93+ "expr": "'ς' == 'Σ'",
94+ "result": { "kind": "Boolean", "value": true }
95+ },
96+ {
97+ "expr": "'ς' != 'Σ'",
98+ "result": { "kind": "Boolean", "value": false }
99+ },
100+ {
101+ "expr": "'ς' < 'Σ'",
102+ "result": { "kind": "Boolean", "value": false }
103+ },
104+ {
105+ "expr": "'ς' <= 'Σ'",
106+ "result": { "kind": "Boolean", "value": true }
107+ },
108+ {
109+ "expr": "'ς' > 'Σ'",
110+ "result": { "kind": "Boolean", "value": false }
111+ },
112+ {
113+ "expr": "'ς' >= 'Σ'",
114+ "result": { "kind": "Boolean", "value": true }
115+ },
116+ {
117+ "expr": "'ς' == 'σ'",
118+ "result": { "kind": "Boolean", "value": true }
119+ },
120+ {
121+ "expr": "'ς' != 'σ'",
122+ "result": { "kind": "Boolean", "value": false }
123+ },
124+ {
125+ "expr": "'ς' < 'σ'",
126+ "result": { "kind": "Boolean", "value": false }
127+ },
128+ {
129+ "expr": "'ς' <= 'σ'",
130+ "result": { "kind": "Boolean", "value": true }
131+ },
132+ {
133+ "expr": "'ς' > 'σ'",
134+ "result": { "kind": "Boolean", "value": false }
135+ },
136+ {
137+ "expr": "'ς' >= 'σ'",
138+ "result": { "kind": "Boolean", "value": true }
139+ },
140+ {
141+ "expr": "'Σ' == 'σ'",
142+ "result": { "kind": "Boolean", "value": true }
143+ },
144+ {
145+ "expr": "'Σ' != 'σ'",
146+ "result": { "kind": "Boolean", "value": false }
147+ },
148+ {
149+ "expr": "'Σ' < 'σ'",
150+ "result": { "kind": "Boolean", "value": false }
151+ },
152+ {
153+ "expr": "'Σ' <= 'σ'",
154+ "result": { "kind": "Boolean", "value": true }
155+ },
156+ {
157+ "expr": "'Σ' > 'σ'",
158+ "result": { "kind": "Boolean", "value": false }
159+ },
160+ {
161+ "expr": "'Σ' >= 'σ'",
162+ "result": { "kind": "Boolean", "value": true }
163+ }
164+ ],
165+
166+ "case-insensitive-ü-Ü": [
167+ {
168+ "expr": "'ü' == 'Ü'",
169+ "result": { "kind": "Boolean", "value": true }
170+ },
171+ {
172+ "expr": "'ü' != 'Ü'",
173+ "result": { "kind": "Boolean", "value": false }
174+ },
175+ {
176+ "expr": "'ü' < 'Ü'",
177+ "result": { "kind": "Boolean", "value": false }
178+ },
179+ {
180+ "expr": "'ü' <= 'Ü'",
181+ "result": { "kind": "Boolean", "value": true }
182+ },
183+ {
184+ "expr": "'ü' > 'Ü'",
185+ "result": { "kind": "Boolean", "value": false }
186+ },
187+ {
188+ "expr": "'ü' >= 'Ü'",
189+ "result": { "kind": "Boolean", "value": true }
190+ },
191+ {
192+ "expr": "'Ü' == 'ü'",
193+ "result": { "kind": "Boolean", "value": true }
194+ },
195+ {
196+ "expr": "'Ü' != 'ü'",
197+ "result": { "kind": "Boolean", "value": false }
198+ },
199+ {
200+ "expr": "'Ü' < 'ü'",
201+ "result": { "kind": "Boolean", "value": false }
202+ },
203+ {
204+ "expr": "'Ü' <= 'ü'",
205+ "result": { "kind": "Boolean", "value": true }
206+ },
207+ {
208+ "expr": "'Ü' > 'ü'",
209+ "result": { "kind": "Boolean", "value": false }
210+ },
211+ {
212+ "expr": "'Ü' >= 'ü'",
213+ "result": { "kind": "Boolean", "value": true }
214+ }
215+ ],
216+
217+ "case-insensitive-ç-Ç": [
218+ {
219+ "expr": "'ç' == 'Ç'",
220+ "result": { "kind": "Boolean", "value": true }
221+ },
222+ {
223+ "expr": "'ç' != 'Ç'",
224+ "result": { "kind": "Boolean", "value": false }
225+ },
226+ {
227+ "expr": "'ç' < 'Ç'",
228+ "result": { "kind": "Boolean", "value": false }
229+ },
230+ {
231+ "expr": "'ç' <= 'Ç'",
232+ "result": { "kind": "Boolean", "value": true }
233+ },
234+ {
235+ "expr": "'ç' > 'Ç'",
236+ "result": { "kind": "Boolean", "value": false }
237+ },
238+ {
239+ "expr": "'ç' >= 'Ç'",
240+ "result": { "kind": "Boolean", "value": true }
241+ },
242+ {
243+ "expr": "'Ç' == 'ç'",
244+ "result": { "kind": "Boolean", "value": true }
245+ },
246+ {
247+ "expr": "'Ç' != 'ç'",
248+ "result": { "kind": "Boolean", "value": false }
249+ },
250+ {
251+ "expr": "'Ç' < 'ç'",
252+ "result": { "kind": "Boolean", "value": false }
253+ },
254+ {
255+ "expr": "'Ç' <= 'ç'",
256+ "result": { "kind": "Boolean", "value": true }
257+ },
258+ {
259+ "expr": "'Ç' > 'ç'",
260+ "result": { "kind": "Boolean", "value": false }
261+ },
262+ {
263+ "expr": "'Ç' >= 'ç'",
264+ "result": { "kind": "Boolean", "value": true }
265+ }
266+ ],
267+
268+ "case-sensitive-i-İ": [
269+ {
270+ "expr": "'i' == 'İ'",
271+ "result": { "kind": "Boolean", "value": false }
272+ },
273+ {
274+ "expr": "'i' != 'İ'",
275+ "result": { "kind": "Boolean", "value": true }
276+ },
277+ {
278+ "expr": "'i' < 'İ'",
279+ "result": { "kind": "Boolean", "value": true }
280+ },
281+ {
282+ "expr": "'i' <= 'İ'",
283+ "result": { "kind": "Boolean", "value": true }
284+ },
285+ {
286+ "expr": "'i' > 'İ'",
287+ "result": { "kind": "Boolean", "value": false }
288+ },
289+ {
290+ "expr": "'i' >= 'İ'",
291+ "result": { "kind": "Boolean", "value": false }
292+ },
293+ {
294+ "expr": "'İ' == 'i'",
295+ "result": { "kind": "Boolean", "value": false }
296+ },
297+ {
298+ "expr": "'İ' != 'i'",
299+ "result": { "kind": "Boolean", "value": true }
300+ },
301+ {
302+ "expr": "'İ' < 'i'",
303+ "result": { "kind": "Boolean", "value": false }
304+ },
305+ {
306+ "expr": "'İ' <= 'i'",
307+ "result": { "kind": "Boolean", "value": false }
308+ },
309+ {
310+ "expr": "'İ' > 'i'",
311+ "result": { "kind": "Boolean", "value": true }
312+ },
313+ {
314+ "expr": "'İ' >= 'i'",
315+ "result": { "kind": "Boolean", "value": true }
316+ }
317+ ],
318+
319+ "case-sensitive-ı-I": [
320+ {
321+ "expr": "'ı' == 'I'",
322+ "result": { "kind": "Boolean", "value": false }
323+ },
324+ {
325+ "expr": "'ı' != 'I'",
326+ "result": { "kind": "Boolean", "value": true }
327+ },
328+ {
329+ "expr": "'ı' < 'I'",
330+ "result": { "kind": "Boolean", "value": false }
331+ },
332+ {
333+ "expr": "'ı' <= 'I'",
334+ "result": { "kind": "Boolean", "value": false }
335+ },
336+ {
337+ "expr": "'ı' > 'I'",
338+ "result": { "kind": "Boolean", "value": true }
339+ },
340+ {
341+ "expr": "'ı' >= 'I'",
342+ "result": { "kind": "Boolean", "value": true }
343+ },
344+ {
345+ "expr": "'I' == 'ı'",
346+ "result": { "kind": "Boolean", "value": false }
347+ },
348+ {
349+ "expr": "'I' != 'ı'",
350+ "result": { "kind": "Boolean", "value": true }
351+ },
352+ {
353+ "expr": "'I' < 'ı'",
354+ "result": { "kind": "Boolean", "value": true }
355+ },
356+ {
357+ "expr": "'I' <= 'ı'",
358+ "result": { "kind": "Boolean", "value": true }
359+ },
360+ {
361+ "expr": "'I' > 'ı'",
362+ "result": { "kind": "Boolean", "value": false }
363+ },
364+ {
365+ "expr": "'I' >= 'ı'",
366+ "result": { "kind": "Boolean", "value": false }
367+ }
368+ ],
369+
370+ "cyrillic-letters": [
371+ {
372+ "expr": "'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЭЮЯ' == 'абвгдежзийклмнопрстуфхцчшщьэюя'",
373+ "result": { "kind": "Boolean", "value": true }
374+ }
375+ ]
376+}
A expr/testdata/languageservices/operators_precedence.json +171 -0
@@ -0,0 +1,171 @@
1+{
2+ "operator_precedence_._and_._evaluate_left_to_right": [
3+ {
4+ "expr": "foo.bar.baz",
5+ "contexts": { "foo": { "bar": { "baz": 2 } } },
6+ "result": { "kind": "Number", "value": 2.0 }
7+ }
8+ ],
9+ "operator_precedence_._is_higher_than_!": [
10+ {
11+ "expr": "!foo.bar",
12+ "contexts": { "foo": { "bar": true } },
13+ "result": { "kind": "Boolean", "value": false }
14+ },
15+ {
16+ "expr": "(!foo).bar",
17+ "contexts": { "foo": { "bar": true } },
18+ "result": { "kind": "Null", "value": null }
19+ }
20+ ],
21+ "operator_precedence_._is_higher_than_>": [
22+ {
23+ "expr": "3 > foo.bar",
24+ "contexts": { "foo": { "bar": 2 } },
25+ "result": { "kind": "Boolean", "value": true }
26+ },
27+ {
28+ "expr": "foo.bar > 2",
29+ "contexts": { "foo": { "bar": 3 } },
30+ "result": { "kind": "Boolean", "value": true }
31+ }
32+ ],
33+ "operator_precedence_[]_is_higher_than_>": [
34+ {
35+ "expr": "3 > foo['bar']",
36+ "contexts": { "foo": { "bar": 2 } },
37+ "result": { "kind": "Boolean", "value": true }
38+ },
39+ {
40+ "expr": "foo['bar'] > 2",
41+ "contexts": { "foo": { "bar": 3 } },
42+ "result": { "kind": "Boolean", "value": true }
43+ }
44+ ],
45+ "operator_precedence_!_is_higher_than_<": [
46+ {
47+ "expr": "!2 < 3",
48+ "result": { "kind": "Boolean", "value": true }
49+ },
50+ {
51+ "expr": "!(2 < 3)",
52+ "result": { "kind": "Boolean", "value": false }
53+ }
54+ ],
55+ "operator_precedence_<_and_<_evaluate_left_to_right": [
56+ {
57+ "expr": "3 < 2 < 1",
58+ "result": { "kind": "Boolean", "value": true }
59+ },
60+ {
61+ "expr": "3 < (2 < 1)",
62+ "result": { "kind": "Boolean", "value": false }
63+ }
64+ ],
65+ "operator_precedence_<_and_<=_evaluate_left_to_right": [
66+ {
67+ "expr": "3 < 2 <= 1",
68+ "result": { "kind": "Boolean", "value": true }
69+ },
70+ {
71+ "expr": "3 < (2 <= 1)",
72+ "result": { "kind": "Boolean", "value": false }
73+ }
74+ ],
75+ "operator_precedence_<=_and_<_evaluate_left_to_right": [
76+ {
77+ "expr": "3 <= 2 < 1",
78+ "result": { "kind": "Boolean", "value": true }
79+ },
80+ {
81+ "expr": "3 <= (2 < 1)",
82+ "result": { "kind": "Boolean", "value": false }
83+ }
84+ ],
85+ "operator_precedence_>_and_<_evaluate_left_to_right": [
86+ {
87+ "expr": "0 > 0 < 1",
88+ "result": { "kind": "Boolean", "value": true }
89+ },
90+ {
91+ "expr": "0 > (0 < 1)",
92+ "result": { "kind": "Boolean", "value": false }
93+ }
94+ ],
95+ "operator_precedence_>_and_>_evaluate_left_to_right": [
96+ {
97+ "expr": "2 > 2 > 0",
98+ "result": { "kind": "Boolean", "value": false }
99+ },
100+ {
101+ "expr": "2 > (2 > 0)",
102+ "result": { "kind": "Boolean", "value": true }
103+ }
104+ ],
105+ "operator_precedence_<=_is_higher_than_==": [
106+ {
107+ "expr": "2 <= 3 == true",
108+ "result": { "kind": "Boolean", "value": true }
109+ },
110+ {
111+ "expr": "2 <= (3 == true)",
112+ "result": { "kind": "Boolean", "value": false }
113+ }
114+ ],
115+ "operator_precedence_==_and_==_evaluate_left_to_right": [
116+ {
117+ "expr": "2 == 2 == 1",
118+ "result": { "kind": "Boolean", "value": true }
119+ },
120+ {
121+ "expr": "2 == (2 == 1)",
122+ "result": { "kind": "Boolean", "value": false }
123+ }
124+ ],
125+ "operator_precedence_==_and_!=_evaluate_left_to_right": [
126+ {
127+ "expr": "2 == 2 != 0",
128+ "result": { "kind": "Boolean", "value": true }
129+ },
130+ {
131+ "expr": "2 == (2 != 0)",
132+ "result": { "kind": "Boolean", "value": false }
133+ }
134+ ],
135+ "operator_precedence_==_is_higher_than_&&": [
136+ {
137+ "expr": "1 == 1 && 2",
138+ "result": { "kind": "Number", "value": 2.0 }
139+ },
140+ {
141+ "expr": "1 == (1 && 2)",
142+ "result": { "kind": "Boolean", "value": false }
143+ },
144+ {
145+ "expr": "'a' == 'a' && 'b'",
146+ "result": { "kind": "String", "value": "b" }
147+ },
148+ {
149+ "expr": "'a' == ('a' && 'b')",
150+ "result": { "kind": "Boolean", "value": false }
151+ }
152+ ],
153+ "operator_precedence_&&_is_higher_than_||": [
154+ {
155+ "expr": "false && 0 || null",
156+ "result": { "kind": "Null", "value": null }
157+ },
158+ {
159+ "expr": "false && (0 || null)",
160+ "result": { "kind": "Boolean", "value": false }
161+ },
162+ {
163+ "expr": "1 || 2 && 3",
164+ "result": { "kind": "Number", "value": 1.0 }
165+ },
166+ {
167+ "expr": "(1 || 2) && 3",
168+ "result": { "kind": "Number", "value": 3.0 }
169+ }
170+ ]
171+}
A expr/testdata/languageservices/startsWith.json +143 -0
@@ -0,0 +1,143 @@
1+{
2+ "basics": [
3+ {
4+ "expr": "startsWith('abcdef', 'abc')",
5+ "result": { "kind": "Boolean", "value": true }
6+ },
7+ {
8+ "expr": "startsWith('abcdef', 'bc')",
9+ "result": { "kind": "Boolean", "value": false }
10+ },
11+ {
12+ "expr": "startsWith('abcdef', '')",
13+ "result": { "kind": "Boolean", "value": true }
14+ },
15+ {
16+ "expr": "startsWith('1234', 12)",
17+ "result": { "kind": "Boolean", "value": true }
18+ },
19+ {
20+ "expr": "startsWith('true', true)",
21+ "result": { "kind": "Boolean", "value": true }
22+ },
23+ {
24+ "expr": "startsWith('false', false)",
25+ "result": { "kind": "Boolean", "value": true }
26+ },
27+ {
28+ "expr": "startsWith('asdf', null)",
29+ "result": { "kind": "Boolean", "value": true }
30+ },
31+ {
32+ "expr": "startsWith('1234', 23)",
33+ "result": { "kind": "Boolean", "value": false }
34+ },
35+ {
36+ "expr": "startsWith('true', false)",
37+ "result": { "kind": "Boolean", "value": false }
38+ },
39+ {
40+ "expr": "startsWith('true', false, true)",
41+ "err": {
42+ "kind": "parsing",
43+ "value": "Too many parameters supplied: "
44+ }
45+ },
46+ {
47+ "expr": "startsWith('true')",
48+ "err": {
49+ "kind": "parsing",
50+ "value": "Too few parameters supplied: "
51+ }
52+ }
53+ ],
54+
55+ "case-insensitive-a-thru-z": [
56+ {
57+ "expr": "startsWith('ABCDEFGHIJKLMNOPQRSTUVWXYZ_asdf', 'abcdefghijklmnopqrstuvwxyz')",
58+ "result": { "kind": "Boolean", "value": true }
59+ },
60+ {
61+ "expr": "startsWith('abcdefghijklmnopqrstuvwxyz_asdf', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')",
62+ "result": { "kind": "Boolean", "value": true }
63+ }
64+ ],
65+
66+ "case-insensitive-ς-(final-lowercase-sigma)-Σ-(capital-sigma)-σ-(non-final-sigma)": [
67+ {
68+ "expr": "startsWith('ς_asdf', 'Σ')",
69+ "result": { "kind": "Boolean", "value": true }
70+ },
71+ {
72+ "expr": "startsWith('ς_asdf', 'σ')",
73+ "result": { "kind": "Boolean", "value": true }
74+ },
75+ {
76+ "expr": "startsWith('Σ_asdf', 'ς')",
77+ "result": { "kind": "Boolean", "value": true }
78+ },
79+ {
80+ "expr": "startsWith('Σ_asdf', 'σ')",
81+ "result": { "kind": "Boolean", "value": true }
82+ },
83+ {
84+ "expr": "startsWith('σ_asdf', 'ς')",
85+ "result": { "kind": "Boolean", "value": true }
86+ },
87+ {
88+ "expr": "startsWith('σ_asdf', 'Σ')",
89+ "result": { "kind": "Boolean", "value": true }
90+ }
91+ ],
92+
93+ "case-insensitive-ü-Ü": [
94+ {
95+ "expr": "startsWith('ü_asdf', 'Ü')",
96+ "result": { "kind": "Boolean", "value": true }
97+ },
98+ {
99+ "expr": "startsWith('Ü_asdf', 'ü')",
100+ "result": { "kind": "Boolean", "value": true }
101+ }
102+ ],
103+
104+ "case-insensitive-ç-Ç": [
105+ {
106+ "expr": "startsWith('ç_asdf', 'Ç')",
107+ "result": { "kind": "Boolean", "value": true }
108+ },
109+ {
110+ "expr": "startsWith('Ç_asdf', 'ç')",
111+ "result": { "kind": "Boolean", "value": true }
112+ }
113+ ],
114+
115+ "case-sensitive-i-İ": [
116+ {
117+ "expr": "startsWith('i_asdf', 'İ')",
118+ "result": { "kind": "Boolean", "value": false }
119+ },
120+ {
121+ "expr": "startsWith('İ_asdf', 'i')",
122+ "result": { "kind": "Boolean", "value": false }
123+ }
124+ ],
125+
126+ "case-sensitive-ı-I": [
127+ {
128+ "expr": "startsWith('ı_asdf', 'I')",
129+ "result": { "kind": "Boolean", "value": false }
130+ },
131+ {
132+ "expr": "startsWith('I_asdf', 'ı')",
133+ "result": { "kind": "Boolean", "value": false }
134+ }
135+ ],
136+
137+ "cyrillic-letters": [
138+ {
139+ "expr": "startsWith('АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЭЮЯ_asdf', 'абвгдежзийклмнопрстуфхцчшщьэюя')",
140+ "result": { "kind": "Boolean", "value": true }
141+ }
142+ ]
143+}
A expr/testdata/languageservices/syntax-errors.json +605 -0
@@ -0,0 +1,605 @@
1+{
2+ "parsing-errors": [
3+ {
4+ "expr": "1 <",
5+ "err": {
6+ "kind": "parsing",
7+ "value": "Unexpected end of expression: '<'. Located at position 3 within expression: 1 <"
8+ }
9+ },
10+ {
11+ "expr": "1 < )",
12+ "err": {
13+ "kind": "parsing",
14+ "value": "Unexpected symbol: ')'. Located at position 5 within expression: 1 < )"
15+ }
16+ },
17+ {
18+ "expr": "> 1",
19+ "err": {
20+ "kind": "parsing",
21+ "value": "Unexpected symbol: '>'. Located at position 1 within expression: > 1"
22+ }
23+ },
24+ {
25+ "expr": "github.()",
26+ "contexts": { "github": { "sha": "abc123" } },
27+ "err": {
28+ "kind": "parsing",
29+ "value": "Unexpected symbol: '('. Located at position 8 within expression: github.()"
30+ }
31+ },
32+ {
33+ "expr": "endswith(1, 2 a)",
34+ "err": {
35+ "kind": "parsing",
36+ "value": "Unexpected symbol: 'a'. Located at position 15 within expression: endswith(1, 2 a)"
37+ }
38+ },
39+ {
40+ "expr": "(1,",
41+ "err": {
42+ "kind": "parsing",
43+ "value": "Unexpected symbol: ','. Located at position 3 within expression: (1,"
44+ }
45+ },
46+ {
47+ "expr": "1 2",
48+ "err": {
49+ "kind": "parsing",
50+ "value": "Unexpected symbol: '2'. Located at position 3 within expression: 1 2"
51+ }
52+ },
53+ {
54+ "expr": "(1)2",
55+ "err": {
56+ "kind": "parsing",
57+ "value": "Unexpected symbol: '2'. Located at position 4 within expression: (1)2"
58+ }
59+ },
60+ {
61+ "expr": "tojson(1)2",
62+ "err": {
63+ "kind": "parsing",
64+ "value": "Unexpected symbol: '2'. Located at position 10 within expression: tojson(1)2"
65+ }
66+ },
67+ {
68+ "expr": "foo 2",
69+ "contexts": { "foo": "bar" },
70+ "err": {
71+ "kind": "parsing",
72+ "value": "Unexpected symbol: '2'. Located at position 5 within expression: foo 2"
73+ }
74+ },
75+ {
76+ "expr": "foo.bar 2",
77+ "contexts": { "foo": "bar" },
78+ "err": {
79+ "kind": "parsing",
80+ "value": "Unexpected symbol: '2'. Located at position 9 within expression: foo.bar 2"
81+ }
82+ },
83+ {
84+ "expr": "foo['bar']2",
85+ "contexts": { "foo": "bar" },
86+ "err": {
87+ "kind": "parsing",
88+ "value": "Unexpected symbol: '2'. Located at position 11 within expression: foo['bar']2"
89+ }
90+ },
91+ {
92+ "expr": "nonExistentFunction()",
93+ "err": {
94+ "kind": "parsing",
95+ "value": "Unrecognized function: 'nonExistentFunction'. Located at position 1"
96+ }
97+ },
98+ {
99+ "expr": "false && nonExistentFunction()",
100+ "err": {
101+ "kind": "parsing",
102+ "value": "Unrecognized function: 'nonExistentFunction'. Located at position 1"
103+ }
104+ },
105+ {
106+ "expr": "1 = < 2",
107+ "err": {
108+ "kind": "lexing",
109+ "value": "Unexpected symbol: '='. Located at position 3 within expression: 1 = < 2"
110+ }
111+ },
112+ {
113+ "expr": "1 & < 2",
114+ "err": {
115+ "kind": "lexing",
116+ "value": "Unexpected symbol: '&'. Located at position 3 within expression: 1 & < 2"
117+ }
118+ },
119+ {
120+ "expr": "1 | < 2",
121+ "err": {
122+ "kind": "lexing",
123+ "value": "Unexpected symbol: '|'. Located at position 3 within expression: 1 | < 2"
124+ }
125+ },
126+ {
127+ "expr": "1 =2 =3",
128+ "err": {
129+ "kind": "lexing",
130+ "value": "Unexpected symbol: '=2'. Located at position 3 within expression: 1 =2 =3"
131+ }
132+ },
133+ {
134+ "expr": "1 &2 &3",
135+ "err": {
136+ "kind": "lexing",
137+ "value": "Unexpected symbol: '&2'. Located at position 3 within expression: 1 &2 &3"
138+ }
139+ },
140+ {
141+ "expr": "1 |2 |3",
142+ "err": {
143+ "kind": "lexing",
144+ "value": "Unexpected symbol: '|2'. Located at position 3 within expression: 1 |2 |3"
145+ }
146+ },
147+ {
148+ "expr": "1 @abc 2",
149+ "err": {
150+ "kind": "lexing",
151+ "value": "Unexpected symbol: '@abc'. Located at position 3 within expression: 1 @abc 2"
152+ }
153+ },
154+ {
155+ "expr": "'foo",
156+ "err": {
157+ "kind": "lexing",
158+ "value": "Unexpected symbol: ''foo'. Located at position 1 within expression: 'foo"
159+ }
160+ },
161+ {
162+ "expr": "fromjson(a,b ",
163+ "err": {
164+ "kind": "parsing",
165+ "value": "Unrecognized named-value: 'a'. Located at position 10 within expression: fromjson(a,b "
166+ }
167+ },
168+ {
169+ "expr": " true ||( false && false",
170+ "err": {
171+ "kind": "parsing",
172+ "value": "Unexpected end of expression: 'false'. Located at position 20 within expression: true ||( false && false"
173+ }
174+ },
175+ {
176+ "expr": "😊 Emoji!! (unicode)",
177+ "err": {
178+ "kind": "lexing",
179+ "value": "Unexpected symbol: '😊'. Located at position 1 within expression: 😊 Emoji!! (unicode)"
180+ }
181+ },
182+ {
183+ "expr": "github.event_name == 'push'\n && !contains(env.COMMIT_MESSAGES)",
184+ "contexts": { "github": { "event_name": "push" }},
185+ "err": {
186+ "kind": "parsing",
187+ "value": "Unrecognized named-value: 'env'. Located at position 43 within expression: github.event_name == 'push'\n && !contains(env.COMMIT_MESSAGES)"
188+ },
189+ "options": {
190+ "skip": ["typescript"]
191+ }
192+ },
193+ {
194+ "expr": "github.event_name == 'push' \n&& true\n&& !contains(env.COMMIT_MESSAGES)",
195+ "contexts": { "github": { "event_name": "push" }},
196+ "err": {
197+ "kind": "parsing",
198+ "value": "Unrecognized named-value: 'env'. Located at position 51 within expression: github.event_name == 'push' \n&& true\n&& !contains(env.COMMIT_MESSAGES)"
199+ },
200+ "options": {
201+ "skip": ["typescript"]
202+ }
203+ },
204+ {
205+ "expr": "github.event_name == 'push' \n&& true\n&& true\n&& !contains(env.COMMIT_MESSAGES)",
206+ "contexts": { "github": { "event_name": "push" }},
207+ "err": {
208+ "kind": "parsing",
209+ "value": "Unrecognized named-value: 'env'. Located at position 59 within expression: github.event_name == 'push' \n&& true\n&& true\n&& !contains(env.COMMIT_MESSAGES)"
210+ },
211+ "options": {
212+ "skip": ["typescript"]
213+ }
214+ },
215+ {
216+ "expr": "github.event_name == 'push' \n&& true\n&& true\n&& true\n&& !contains(env.COMMIT_MESSAGES)",
217+ "contexts": { "github": { "event_name": "push" }},
218+ "err": {
219+ "kind": "parsing",
220+ "value": "Unrecognized named-value: 'env'. Located at position 67 within expression: github.event_name == 'push' \n&& true\n&& true\n&& true\n&& !contains(env.COMMIT_MESSAGES)"
221+ },
222+ "options": {
223+ "skip": ["typescript"]
224+ }
225+ },
226+ {
227+ "expr": "1 && ..github/workflow.md",
228+ "err": {
229+ "kind": "lexing",
230+ "value": "Unexpected symbol: '..github/workflow.md'. Located at position 6 within expression: 1 && ..github/workflow.md"
231+ }
232+ },
233+ {
234+ "expr": "..github/workflow.md",
235+ "err": {
236+ "kind": "lexing",
237+ "value": "Unexpected symbol: '..github/workflow.md'. Located at position 1 within expression: ..github/workflow.md"
238+ }
239+ },
240+ {
241+ "expr": ".github/workflow.md",
242+ "err": {
243+ "kind": "lexing",
244+ "value": "Unexpected symbol: '.github/workflow.md'. Located at position 1 within expression: .github/workflow.md"
245+ }
246+ },
247+ {
248+ "expr": ".github/workflow",
249+ "err": {
250+ "kind": "lexing",
251+ "value": "Unexpected symbol: '.github/workflow'. Located at position 1 within expression: .github/workflow"
252+ }
253+ },
254+ {
255+ "expr": "github/workflow.md",
256+ "err": {
257+ "kind": "lexing",
258+ "value": "Unexpected symbol: 'github/workflow'. Located at position 1 within expression: github/workflow.md"
259+ }
260+ },
261+ {
262+ "expr": "..github/workflow.md && 1",
263+ "err": {
264+ "kind": "lexing",
265+ "value": "Unexpected symbol: '..github/workflow.md'. Located at position 1 within expression: ..github/workflow.md && 1"
266+ }
267+ }
268+ ],
269+ "depth-errors": [
270+ {
271+ "expr": "!!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! false",
272+ "err": {
273+ "kind": "parsing",
274+ "value": "Exceeded max expression depth 50"
275+ }
276+ },
277+ {
278+ "expr": "!!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!!! !!!! false",
279+ "result": { "kind": "Boolean", "value": true }
280+ },
281+ {
282+ "expr": "foo._1._2._3._4._5._6._7._8._9._10._11._12._13._14._15._16._17._18._19._20._21._22._23._24._25._26._27._28._29._30._31._32._33._34._35._36._37._38._39._40._41._42._43._44._45._46._47._48._49._50",
283+ "contexts": { "foo": null },
284+ "err": {
285+ "kind": "parsing",
286+ "value": "Exceeded max expression depth 50"
287+ }
288+ },
289+ {
290+ "expr": "foo._1._2._3._4._5._6._7._8._9._10._11._12._13._14._15._16._17._18._19._20._21._22._23._24._25._26._27._28._29._30._31._32._33._34._35._36._37._38._39._40._41._42._43._44._45._46._47._48._49",
291+ "contexts": { "foo": null },
292+ "result": { "kind": "Null", "value": null }
293+ },
294+ {
295+ "expr": "foo .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.*",
296+ "contexts": { "foo": null },
297+ "err": {
298+ "kind": "parsing",
299+ "value": "Exceeded max expression depth 50"
300+ }
301+ },
302+ {
303+ "expr": "foo .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*.* .*.*.*.*",
304+ "contexts": { "foo": null },
305+ "result": { "kind": "Array", "value": [] }
306+ },
307+ {
308+ "expr": "foo[1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][22][23][24][25][26][27][28][29][30][31][32][33][34][35][36][37][38][39][40][41][42][43][44][45][46][47][48][49][50]",
309+ "contexts": { "foo": null },
310+ "err": {
311+ "kind": "parsing",
312+ "value": "Exceeded max expression depth 50"
313+ }
314+ },
315+ {
316+ "expr": "foo[1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16][17][18][19][20][21][22][23][24][25][26][27][28][29][30][31][32][33][34][35][36][37][38][39][40][41][42][43][44][45][46][47][48][49]",
317+ "contexts": { "foo": null },
318+ "result": { "kind": "Null", "value": null }
319+ },
320+ {
321+ "expr": "foo [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*]",
322+ "contexts": { "foo": null },
323+ "err": {
324+ "kind": "parsing",
325+ "value": "Exceeded max expression depth 50"
326+ }
327+ },
328+ {
329+ "expr": "foo [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*][*] [*][*][*][*]",
330+ "contexts": { "foo": null },
331+ "result": { "kind": "Array", "value": [] }
332+ },
333+ {
334+ "expr": "fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( 1 )))))))))) )))))))))) )))))))))) )))))))))) ))))))))))",
335+ "err": {
336+ "kind": "parsing",
337+ "value": "Exceeded max expression depth 50"
338+ }
339+ },
340+ {
341+ "expr": "fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson( fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson(tojson(fromjson( '1' ))))))))) )))))))))) )))))))))) )))))))))) ))))))))))",
342+ "result": { "kind": "Number", "value": 1 }
343+ },
344+ {
345+ "expr": "format('depth3'||format('depth5'||format('depth7'||format('depth9'||format('depth11'||format('depth13'||format('depth15'||format('depth17'||format('depth19'||format('depth21'||format('depth23'||format('depth25'||format('depth27'||format('depth29'||format('depth31'||format('depth33'||format('depth35'||format('depth37'||format('depth39'||format('depth41'||format('depth43'||format('depth45'||format('depth47'||format('depth49'||format('depth51'||'depth51')))))))))) )))))))))) )))))",
346+ "err": {
347+ "kind": "parsing",
348+ "value": "Exceeded max expression depth 50"
349+ }
350+ },
351+ {
352+ "expr": "format('depth3'||format('depth5'||format('depth7'||format('depth9'||format('depth11'||format('depth13'||format('depth15'||format('depth17'||format('depth19'||format('depth21'||format('depth23'||format('depth25'||format('depth27'||format('depth29'||format('depth31'||format('depth33'||format('depth35'||format('depth37'||format('depth39'||format('depth41'||format('depth43'||format('depth45'||format('depth47'||format('depth49'||format('depth50')))))))))) )))))))))) )))))",
353+ "result": { "kind": "String", "value": "depth3" }
354+ },
355+ {
356+ "expr": "format('depth3'&&format('depth5'&&format('depth7'&&format('depth9'&&format('depth11'&&format('depth13'&&format('depth15'&&format('depth17'&&format('depth19'&&format('depth21'&&format('depth23'&&format('depth25'&&format('depth27'&&format('depth29'&&format('depth31'&&format('depth33'&&format('depth35'&&format('depth37'&&format('depth39'&&format('depth41'&&format('depth43'&&format('depth45'&&format('depth47'&&format('depth49'&&format('depth51'&&'depth51')))))))))) )))))))))) )))))",
357+ "err": {
358+ "kind": "parsing",
359+ "value": "Exceeded max expression depth 50"
360+ }
361+ },
362+ {
363+ "expr": "format('depth3'&&format('depth5'&&format('depth7'&&format('depth9'&&format('depth11'&&format('depth13'&&format('depth15'&&format('depth17'&&format('depth19'&&format('depth21'&&format('depth23'&&format('depth25'&&format('depth27'&&format('depth29'&&format('depth31'&&format('depth33'&&format('depth35'&&format('depth37'&&format('depth39'&&format('depth41'&&format('depth43'&&format('depth45'&&format('depth47'&&format('depth49'&&format('depth50')))))))))) )))))))))) )))))",
364+ "result": { "kind": "String", "value": "depth50" }
365+ },
366+ {
367+ "expr": "1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22 || 23 || 24 || 25 || 26 || 27 || 28 || 29 || 30 || 31 || 32 || 33 || 34 || 35 || 36 || 37 || 38 || 39 || 40 || 41 || 42 || 43 || 44 || 45 || 46 || 47 || 48 || 49 || 50 || 51 || 52 || 53 || 54 || 55 || 56 || 57 || 58 || 59 || 60",
368+ "result": { "kind": "Number", "value": 1 }
369+ },
370+ {
371+ "expr": "1 && 2 && 3 && 4 && 5 && 6 && 7 && 8 && 9 && 10 && 11 && 12 && 13 && 14 && 15 && 16 && 17 && 18 && 19 && 20 && 21 && 22 && 23 && 24 && 25 && 26 && 27 && 28 && 29 && 30 && 31 && 32 && 33 && 34 && 35 && 36 && 37 && 38 && 39 && 40 && 41 && 42 && 43 && 44 && 45 && 46 && 47 && 48 && 49 && 50 && 51 && 52 && 53 && 54 && 55 && 56 && 57 && 58 && 59 && 60",
372+ "result": { "kind": "Number", "value": 60 }
373+ },
374+ {
375+ "expr": "1 && 2 || 3 && 4 || 5 && 6 || 7 && 8 || 9 && 10 || 11 && 12 || 13 && 14 || 15 && 16 || 17 && 18 || 19 && 20 || 21 && 22 || 23 && 24 || 25 && 26 || 27 && 28 || 29 && 30 || 31 && 32 || 33 && 34 || 35 && 36 || 37 && 38 || 39 && 40 || 41 && 42 || 43 && 44 || 45 && 46 || 47 && 48 || 49 && 50 || 51 && 52 || 53 && 54 || 55 && 56 || 57 && 58 || 59 && 60",
376+ "result": { "kind": "Number", "value": 2 }
377+ }
378+ ],
379+ "memory-errors": [
380+ {
381+ "options": {
382+ "skip": ["typescript"]
383+ },
384+ "expr": "startswith(format('{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}', tojson(github)), format('{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}{0}', tojson(github)))",
385+ "contexts": {
386+ "github": {
387+ "ref": "refs/heads/main",
388+ "sha": "2ce6b095f4cceb616efed52266e3e3c0367ba795",
389+ "repository": "monalisa/testing",
390+ "repository_owner": "monalisa",
391+ "repository_owner_id": "12102068",
392+ "repositoryUrl": "git://github.com/monalisa/testing.git",
393+ "run_id": "2827197126",
394+ "run_number": "845",
395+ "retention_days": "90",
396+ "run_attempt": "1",
397+ "artifact_cache_size_limit": "10",
398+ "repository_visibility": "public",
399+ "repository_id": "1",
400+ "actor_id": "1",
401+ "actor": "monalisa",
402+ "triggering_actor": "monalisa",
403+ "workflow": "CI",
404+ "head_ref": "",
405+ "base_ref": "",
406+ "event_name": "push",
407+ "event": {
408+ "after": "2ce6b095f4cceb616efed52266e3e3c0367ba795",
409+ "base_ref": null,
410+ "before": "bbe29778fdad33232cd67bd84205d334b94412d7",
411+ "commits": [
412+ {
413+ "author": {
414+ "email": "monalisa@users.noreply.github.com",
415+ "name": "mona",
416+ "username": "monalisa"
417+ },
418+ "committer": {
419+ "email": "noreply@github.com",
420+ "name": "GitHub",
421+ "username": "web-flow"
422+ },
423+ "distinct": true,
424+ "id": "2ce6b095f4cceb616efed52266e3e3c0367ba795",
425+ "message": "Update main.yml",
426+ "timestamp": "2022-08-09T12:40:12-05:00",
427+ "tree_id": "0dfd49aedd0cc103865d00587df3d23aa6f571f7",
428+ "url": "https://github.com/monalisa/testing/commit/2ce6b095f4cceb616efed52266e3e3c0367ba795"
429+ }
430+ ],
431+ "compare": "https://github.com/monalisa/testing/compare/bbe29778fdad...2ce6b095f4cc",
432+ "created": false,
433+ "deleted": false,
434+ "forced": false,
435+ "head_commit": {
436+ "author": {
437+ "email": "monalisa@users.noreply.github.com",
438+ "name": "mona",
439+ "username": "monalisa"
440+ },
441+ "committer": {
442+ "email": "noreply@github.com",
443+ "name21": "GitHub",
444+ "username": "web-flow"
445+ },
446+ "distinct": true,
447+ "id": "2ce6b095f4cceb616efed52266e3e3c0367ba795",
448+ "message": "Update main.yml",
449+ "timestamp": "2022-08-09T12:40:12-05:00",
450+ "tree_id": "0dfd49aedd0cc103865d00587df3d23aa6f571f7",
451+ "url": "https://github.com/monalisa/testing/commit/2ce6b095f4cceb616efed52266e3e3c0367ba795"
452+ },
453+ "pusher": {
454+ "email": "monalisa@users.noreply.github.com",
455+ "name": "monalisa"
456+ },
457+ "ref": "refs/heads/main",
458+ "repository": {
459+ "allow_forking": true,
460+ "archive_url": "https://api.github.com/repos/monalisa/testing/{archive_format}{/ref}",
461+ "archived": false,
462+ "assignees_url": "https://api.github.com/repos/monalisa/testing/assignees{/user}",
463+ "blobs_url": "https://api.github.com/repos/monalisa/testing/git/blobs{/sha}",
464+ "branches_url": "https://api.github.com/repos/monalisa/testing/branches{/branch}",
465+ "clone_url": "https://github.com/monalisa/testing.git",
466+ "collaborators_url": "https://api.github.com/repos/monalisa/testing/collaborators{/collaborator}",
467+ "comments_url": "https://api.github.com/repos/monalisa/testing/comments{/number}",
468+ "commits_url": "https://api.github.com/repos/monalisa/testing/commits{/sha}",
469+ "compare_url": "https://api.github.com/repos/monalisa/testing/compare/{base}...{head}",
470+ "contents_url": "https://api.github.com/repos/monalisa/testing/contents/{+path}",
471+ "contributors_url": "https://api.github.com/repos/monalisa/testing/contributors",
472+ "created_at": 1516230842,
473+ "default_branch": "main",
474+ "deployments_url": "https://api.github.com/repos/monalisa/testing/deployments",
475+ "description": null,
476+ "disabled": false,
477+ "downloads_url": "https://api.github.com/repos/monalisa/testing/downloads",
478+ "events_url": "https://api.github.com/repos/monalisa/testing/events",
479+ "fork": false,
480+ "forks": 1,
481+ "forks_count": 1,
482+ "forks_url": "https://api.github.com/repos/monalisa/testing/forks",
483+ "full_name": "monalisa/testing",
484+ "git_commits_url": "https://api.github.com/repos/monalisa/testing/git/commits{/sha}",
485+ "git_refs_url": "https://api.github.com/repos/monalisa/testing/git/refs{/sha}",
486+ "git_tags_url": "https://api.github.com/repos/monalisa/testing/git/tags{/sha}",
487+ "git_url": "git://github.com/monalisa/testing.git",
488+ "has_downloads": true,
489+ "has_issues": true,
490+ "has_pages": false,
491+ "has_projects": true,
492+ "has_wiki": true,
493+ "homepage": null,
494+ "hooks_url": "https://api.github.com/repos/monalisa/testing/hooks",
495+ "html_url": "https://github.com/monalisa/testing",
496+ "id": 117904191,
497+ "is_template": false,
498+ "issue_comment_url": "https://api.github.com/repos/monalisa/testing/issues/comments{/number}",
499+ "issue_events_url": "https://api.github.com/repos/monalisa/testing/issues/events{/number}",
500+ "issues_url": "https://api.github.com/repos/monalisa/testing/issues{/number}",
501+ "keys_url": "https://api.github.com/repos/monalisa/testing/keys{/key_id}",
502+ "labels_url": "https://api.github.com/repos/monalisa/testing/labels{/name}",
503+ "language": "Shell",
504+ "languages_url": "https://api.github.com/repos/monalisa/testing/languages",
505+ "license": null,
506+ "master_branch": "main",
507+ "merges_url": "https://api.github.com/repos/monalisa/testing/merges",
508+ "milestones_url": "https://api.github.com/repos/monalisa/testing/milestones{/number}",
509+ "mirror_url": null,
510+ "name": "testing",
511+ "node_id": "MDEwOlJlcG9zaXRvcnkxMTc5MDQxOTE=",
512+ "notifications_url": "https://api.github.com/repos/monalisa/testing/notifications{?since,all,participating}",
513+ "open_issues": 5,
514+ "open_issues_count": 5,
515+ "owner": {
516+ "avatar_url": "https://avatars.githubusercontent.com/u/12102068?v=4",
517+ "email": "monalisa@users.noreply.github.com",
518+ "events_url": "https://api.github.com/users/monalisa/events{/privacy}",
519+ "followers_url": "https://api.github.com/users/monalisa/followers",
520+ "following_url": "https://api.github.com/users/monalisa/following{/other_user}",
521+ "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}",
522+ "gravatar_id": "",
523+ "html_url": "https://github.com/monalisa",
524+ "id": 12102068,
525+ "login": "monalisa",
526+ "name": "monalisa",
527+ "node_id": "MDQ6VXNlcjEyMTAyMDY4",
528+ "organizations_url": "https://api.github.com/users/monalisa/orgs",
529+ "received_events_url": "https://api.github.com/users/monalisa/received_events",
530+ "repos_url": "https://api.github.com/users/monalisa/repos",
531+ "site_admin": true,
532+ "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}",
533+ "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions",
534+ "type": "User",
535+ "url": "https://api.github.com/users/monalisa"
536+ },
537+ "private": false,
538+ "pulls_url": "https://api.github.com/repos/monalisa/testing/pulls{/number}",
539+ "pushed_at": 1660066812,
540+ "releases_url": "https://api.github.com/repos/monalisa/testing/releases{/id}",
541+ "size": 611,
542+ "ssh_url": "git@github.com:monalisa/testing.git",
543+ "stargazers": 1,
544+ "stargazers_count": 1,
545+ "stargazers_url": "https://api.github.com/repos/monalisa/testing/stargazers",
546+ "statuses_url": "https://api.github.com/repos/monalisa/testing/statuses/{sha}",
547+ "subscribers_url": "https://api.github.com/repos/monalisa/testing/subscribers",
548+ "subscription_url": "https://api.github.com/repos/monalisa/testing/subscription",
549+ "svn_url": "https://github.com/monalisa/testing",
550+ "tags_url": "https://api.github.com/repos/monalisa/testing/tags",
551+ "teams_url": "https://api.github.com/repos/monalisa/testing/teams",
552+ "topics": [],
553+ "trees_url": "https://api.github.com/repos/monalisa/testing/git/trees{/sha}",
554+ "updated_at": "2022-01-05T02:14:32Z",
555+ "url": "https://github.com/monalisa/testing",
556+ "visibility": "public",
557+ "watchers": 1,
558+ "watchers_count": 1,
559+ "web_commit_signoff_required": false
560+ },
561+ "sender": {
562+ "avatar_url": "https://avatars.githubusercontent.com/u/12102068?v=4",
563+ "events_url": "https://api.github.com/users/monalisa/events{/privacy}",
564+ "followers_url": "https://api.github.com/users/monalisa/followers",
565+ "following_url": "https://api.github.com/users/monalisa/following{/other_user}",
566+ "gists_url": "https://api.github.com/users/monalisa/gists{/gist_id}",
567+ "gravatar_id": "",
568+ "html_url": "https://github.com/monalisa",
569+ "id": 12102068,
570+ "login": "monalisa",
571+ "node_id": "MDQ6VXNlcjEyMTAyMDY4",
572+ "organizations_url": "https://api.github.com/users/monalisa/orgs",
573+ "received_events_url": "https://api.github.com/users/monalisa/received_events",
574+ "repos_url": "https://api.github.com/users/monalisa/repos",
575+ "site_admin": true,
576+ "starred_url": "https://api.github.com/users/monalisa/starred{/owner}{/repo}",
577+ "subscriptions_url": "https://api.github.com/users/monalisa/subscriptions",
578+ "type": "User",
579+ "url": "https://api.github.com/users/monalisa"
580+ }
581+ },
582+ "server_url": "https://github.com",
583+ "api_url": "https://api.github.com",
584+ "graphql_url": "https://api.github.com/graphql",
585+ "ref_name": "main",
586+ "ref_protected": false,
587+ "ref_type": "branch",
588+ "secret_source": "Actions",
589+ "workspace": "/home/runner/work/testing/testing",
590+ "action": "__run_2",
591+ "event_path": "/home/runner/work/_temp/_github_workflow/event.json",
592+ "action_repository": "",
593+ "action_ref": "",
594+ "path": "/home/runner/work/_temp/_runner_file_commands/add_path_04146e5b-5a26-490d-b886-886641aa5651",
595+ "env": "/home/runner/work/_temp/_runner_file_commands/set_env_04146e5b-5a26-490d-b886-886641aa5651",
596+ "step_summary": "/home/runner/work/_temp/_runner_file_commands/step_summary_04146e5b-5a26-490d-b886-886641aa5651"
597+ }
598+ },
599+ "err": {
600+ "kind": "evaluation",
601+ "value": "The maximum allowed memory size was exceeded"
602+ }
603+ }
604+ ]
605+}
A expr/testdata/languageservices/tojson.json +223 -0
@@ -0,0 +1,223 @@
1+{
2+ "basics": [
3+ {
4+ "expr": "tojson(null)",
5+ "result": {
6+ "kind": "String",
7+ "value": "null"
8+ }
9+ },
10+ {
11+ "expr": "tojson(true)",
12+ "result": {
13+ "kind": "String",
14+ "value": "true"
15+ }
16+ },
17+ {
18+ "expr": "tojson(false)",
19+ "result": {
20+ "kind": "String",
21+ "value": "false"
22+ }
23+ },
24+ {
25+ "expr": "tojson(0)",
26+ "result": {
27+ "kind": "String",
28+ "value": "0"
29+ }
30+ },
31+ {
32+ "expr": "tojson(-0)",
33+ "result": {
34+ "kind": "String",
35+ "value": "0"
36+ }
37+ },
38+ {
39+ "expr": "tojson(123456789)",
40+ "result": {
41+ "kind": "String",
42+ "value": "123456789"
43+ }
44+ },
45+ {
46+ "expr": "tojson(-123456789)",
47+ "result": {
48+ "kind": "String",
49+ "value": "-123456789"
50+ }
51+ },
52+ {
53+ "expr": "tojson(1234.5)",
54+ "result": {
55+ "kind": "String",
56+ "value": "1234.5"
57+ }
58+ },
59+ {
60+ "expr": "tojson(-1234.5)",
61+ "result": {
62+ "kind": "String",
63+ "value": "-1234.5"
64+ }
65+ },
66+ {
67+ "expr": "tojson('')",
68+ "result": {
69+ "kind": "String",
70+ "value": "\"\""
71+ }
72+ },
73+ {
74+ "expr": "tojson('abc')",
75+ "result": {
76+ "kind": "String",
77+ "value": "\"abc\""
78+ }
79+ },
80+ {
81+ "expr": "tojson('abc''def')",
82+ "result": {
83+ "kind": "String",
84+ "value": "\"abc'def\""
85+ }
86+ },
87+ {
88+ "expr": "tojson('abc\\\"def')",
89+ "result": {
90+ "kind": "String",
91+ "value": "\"abc\\\\\\\"def\""
92+ }
93+ },
94+ {
95+ "expr": "tojson(emptyArray)",
96+ "contexts": {
97+ "emptyArray": []
98+ },
99+ "result": {
100+ "kind": "String",
101+ "value": "[]"
102+ }
103+ },
104+ {
105+ "expr": "tojson(emptyObject)",
106+ "contexts": {
107+ "emptyObject": {}
108+ },
109+ "result": {
110+ "kind": "String",
111+ "value": "{}"
112+ }
113+ }
114+ ],
115+ "arrays": [
116+ {
117+ "expr": "tojson(myArray)",
118+ "contexts": {
119+ "myArray": []
120+ },
121+ "result": {
122+ "kind": "String",
123+ "value": "[]"
124+ }
125+ },
126+ {
127+ "expr": "tojson(myArray)",
128+ "contexts": {
129+ "myArray": [
130+ 1,
131+ 2,
132+ 3
133+ ]
134+ },
135+ "result": {
136+ "kind": "String",
137+ "value": "[\n 1,\n 2,\n 3\n]"
138+ }
139+ },
140+ {
141+ "expr": "tojson(myArray)",
142+ "contexts": {
143+ "myArray": [
144+ [
145+ 1,
146+ 2,
147+ 3
148+ ],
149+ [
150+ "abc",
151+ "def",
152+ "ghi"
153+ ],
154+ [
155+ true,
156+ false,
157+ null,
158+ [],
159+ {}
160+ ]
161+ ]
162+ },
163+ "result": {
164+ "kind": "String",
165+ "value": "[\n [\n 1,\n 2,\n 3\n ],\n [\n \"abc\",\n \"def\",\n \"ghi\"\n ],\n [\n true,\n false,\n null,\n [],\n {}\n ]\n]"
166+ }
167+ }
168+ ],
169+ "object": [
170+ {
171+ "expr": "tojson(myObject)",
172+ "contexts": {
173+ "myObject": {}
174+ },
175+ "result": {
176+ "kind": "String",
177+ "value": "{}"
178+ }
179+ },
180+ {
181+ "expr": "tojson(myObject)",
182+ "contexts": {
183+ "myObject": {
184+ "one": "value one",
185+ "two" : "value two",
186+ "three": "value three"
187+ }
188+ },
189+ "result": {
190+ "kind": "String",
191+ "value": "{\n \"one\": \"value one\",\n \"two\": \"value two\",\n \"three\": \"value three\"\n}"
192+ }
193+ },
194+ {
195+ "expr": "tojson(myObject)",
196+ "contexts": {
197+ "myObject": {
198+ "nested-one": {
199+ "one": 1,
200+ "two": 2,
201+ "three": 3
202+ },
203+ "nested-two": {
204+ "string one": "value one",
205+ "string two": "value two",
206+ "string three": "value three"
207+ },
208+ "nested-three": {
209+ "true": true,
210+ "false": false,
211+ "null": null,
212+ "array": [],
213+ "object": {}
214+ }
215+ }
216+ },
217+ "result": {
218+ "kind": "String",
219+ "value": "{\n \"nested-one\": {\n \"one\": 1,\n \"two\": 2,\n \"three\": 3\n },\n \"nested-two\": {\n \"string one\": \"value one\",\n \"string two\": \"value two\",\n \"string three\": \"value three\"\n },\n \"nested-three\": {\n \"true\": true,\n \"false\": false,\n \"null\": null,\n \"array\": [],\n \"object\": {}\n }\n}"
220+ }
221+ }
222+ ]
223+}
A expr/testdata/number_cases.txt +443 -0
@@ -0,0 +1,443 @@
1+- 0
2+20 0
3+30 0
4+30.30 0
5+2D.30 8000000000000000
6+2B.30 0
7+31 3ff0000000000000
8+2D.31 bff0000000000000
9+2B.31 3ff0000000000000
10+31.32 4028000000000000
11+30.30.37 401c000000000000
12+31.2E.35 3ff8000000000000
13+2D.31.2E.35 bff8000000000000
14+31.2E 3ff0000000000000
15+2E.35 3fe0000000000000
16+2D.2E.35 bfe0000000000000
17+2B.2E.35 3fe0000000000000
18+2E NaN
19+2D.2E NaN
20+2B.2E NaN
21+2E.2E NaN
22+31.2E.2E.32 NaN
23+31.2E.32.2E.33 NaN
24+31.65.33 408f400000000000
25+31.45.33 408f400000000000
26+31.65.2B.33 408f400000000000
27+31.65.2D.33 3f50624dd2f1a9fc
28+31.65 NaN
29+31.65.2B NaN
30+31.65.2D NaN
31+65.33 NaN
32+45.33 NaN
33+2E.65.33 NaN
34+31.2E.65.33 408f400000000000
35+2E.35.65.33 407f400000000000
36+31.65.33.2E.35 NaN
37+31.65.30.33 408f400000000000
38+31.65.30.30.30.33 408f400000000000
39+31.65.33.30.38 7fe1ccf385ebc8a0
40+31.65.33.30.39 7ff0000000000000
41+2D.31.65.33.30.39 fff0000000000000
42+31.65.2D.33.32.34 0
43+31.65.2D.34.30.30 0
44+34.2E.39.65.2D.33.32.34 1
45+32.2E.35.65.2D.33.32.34 1
46+31.2E.37.39.37.36.39.33.31.33.34.38.36.32.33.31.35.37.65.33.30.38 7fefffffffffffff
47+31.2E.37.39.37.36.39.33.31.33.34.38.36.32.33.31.35.39.65.33.30.38 7ff0000000000000
48+30.2E.31 3fb999999999999a
49+30.2E.33.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.34 3fd3333333333334
50+31.32.33.34.35.36.37.38.39.30.31.32.33.34.35.36.37.38.39.30.31.32.33.34.35.36.37.38.39.30 45f8ee90ff6c373e
51+39.30.30.37.31.39.39.32.35.34.37.34.30.39.39.33 4340000000000000
52+2D.39.30.30.37.31.39.39.32.35.34.37.34.30.39.39.33 c340000000000000
53+30.2E.30.30.30.30.30.31 3eb0c6f7a0b5ed8d
54+31.5F.30.30.30 NaN
55+31.2C.30.30.30 NaN
56+31.20.30.30.30 NaN
57+661.662.663 NaN
58+FF11.FF12.FF13 NaN
59+49.6E.66.69.6E.69.74.79 7ff0000000000000
60+2D.49.6E.66.69.6E.69.74.79 fff0000000000000
61+2B.49.6E.66.69.6E.69.74.79 7ff0000000000000
62+69.6E.66.69.6E.69.74.79 7ff0000000000000
63+49.4E.46.49.4E.49.54.59 7ff0000000000000
64+49.6E.66.69.6E.69.74 NaN
65+49.6E.66.69.6E.69.74.79.78 NaN
66+69.6E.66 NaN
67+49.6E.66 NaN
68+2D.69.6E.66 NaN
69+2B.69.6E.66 NaN
70+221E NaN
71+2D.221E NaN
72+2212.49.6E.66.69.6E.69.74.79 NaN
73+2212.31 NaN
74+4E.61.4E NaN
75+6E.61.6E NaN
76+4E.41.4E NaN
77+2D.4E.61.4E NaN
78+2B.4E.61.4E NaN
79+4E.61.4E.78 NaN
80+4E NaN
81+6E.75.6C.6C NaN
82+74.72.75.65 NaN
83+66.61.6C.73.65 NaN
84+61.62.63 NaN
85+78 NaN
86+2D NaN
87+2B NaN
88+2B.2D.31 NaN
89+2D.2B.31 NaN
90+2D.2D.31 NaN
91+2B.2B.31 NaN
92+2D.20.31 NaN
93+2B.20.31 NaN
94+31.20.2D NaN
95+31.2D NaN
96+31.2B NaN
97+31.20.32 NaN
98+30.78 NaN
99+30.78.30 0
100+30.78.31 3ff0000000000000
101+30.78.31.30 4030000000000000
102+30.78.66.66 406fe00000000000
103+30.78.46.46 406fe00000000000
104+30.58.46.46 NaN
105+30.78.66.46 406fe00000000000
106+30.78.37.66.66.66.66.66.66.66 41dfffffffc00000
107+30.78.38.30.30.30.30.30.30.30 c1e0000000000000
108+30.78.66.66.66.66.66.66.66.66 bff0000000000000
109+30.78.31.30.30.30.30.30.30.30.30 NaN
110+30.78.46.46.46.46.46.46.46.46.46 NaN
111+30.78.30.30.30.30.30.30.30.31 3ff0000000000000
112+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
113+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
114+2D.30.78.31 NaN
115+2B.30.78.31 NaN
116+30.78.2D.31 NaN
117+30.78.2B.31 NaN
118+30.78.31.2E.30 NaN
119+30.78.31.70.33 NaN
120+30.78.67 NaN
121+30.78.20.31 NaN
122+30.20.78.31 NaN
123+30.78.31.20 3ff0000000000000
124+30.6F NaN
125+30.6F.30 0
126+30.6F.37 401c000000000000
127+30.6F.38 NaN
128+30.6F.31.37 402e000000000000
129+30.4F.31.37 NaN
130+30.6F.37.37.37 407ff00000000000
131+30.6F.31.37.37.37.37.37.37.37.37.37.37 41dfffffffc00000
132+30.6F.32.30.30.30.30.30.30.30.30.30.30 c1e0000000000000
133+30.6F.33.37.37.37.37.37.37.37.37.37.37 bff0000000000000
134+30.6F.34.30.30.30.30.30.30.30.30.30.30 NaN
135+30.6F.37.37.37.37.37.37.37.37.37.37.37 NaN
136+30.6F.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
137+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
138+2D.30.6F.37 NaN
139+30.6F.2D.37 NaN
140+30.6F.31.2E.35 NaN
141+30.6F.20.37 NaN
142+30.62 NaN
143+30.62.30 NaN
144+30.62.31 NaN
145+30.62.31.30.31 NaN
146+30.42.31.30.31 NaN
147+30.62.32 NaN
148+30.62.31.65.33 NaN
149+9.31 3ff0000000000000
150+31.9 3ff0000000000000
151+A.31.A 3ff0000000000000
152+D.31 3ff0000000000000
153+B.31 3ff0000000000000
154+C.31 3ff0000000000000
155+A0.31 3ff0000000000000
156+31.A0 3ff0000000000000
157+2028.31 3ff0000000000000
158+2029.31 3ff0000000000000
159+85.31 3ff0000000000000
160+31.85 3ff0000000000000
161+3000.31 3ff0000000000000
162+2003.31.2003 3ff0000000000000
163+FEFF.31 NaN
164+31.FEFF NaN
165+200B.31 NaN
166+180E.31 NaN
167+1680.31 3ff0000000000000
168+31.0 3ff0000000000000
169+0.31 NaN
170+31.20 3ff0000000000000
171+20.31 3ff0000000000000
172+20.20.31.20.20 3ff0000000000000
173+2B.20 NaN
174+20.2D.31.2E.35.65.32.20 c062c00000000000
175+31.30 4024000000000000
176+2E.31 3fb999999999999a
177+31.2E.31 3ff199999999999a
178+31.65.31 4024000000000000
179+31.65.2B.31 4024000000000000
180+31.65.2D.31 3fb999999999999a
181+2E.31.65.31 3ff0000000000000
182+31.2E.65.31 4024000000000000
183+30.78.41 4024000000000000
184+30.6F.31 3ff0000000000000
185+2B.31.30 4024000000000000
186+2B.31.2E 3ff0000000000000
187+2B.2E.31 3fb999999999999a
188+2B.31.2E.31 3ff199999999999a
189+2B.31.65.31 4024000000000000
190+2B.31.65.2B.31 4024000000000000
191+2B.31.65.2D.31 3fb999999999999a
192+2B.2E.31.65.31 3ff0000000000000
193+2B.31.2E.65.31 4024000000000000
194+2B.69.6E.66.69.6E.69.74.79 7ff0000000000000
195+2B.6E.61.6E NaN
196+2B.30.78.41 NaN
197+2B.30.6F.31 NaN
198+2B.30.62.31 NaN
199+2D.31.30 c024000000000000
200+2D.31.2E bff0000000000000
201+2D.2E.31 bfb999999999999a
202+2D.31.2E.31 bff199999999999a
203+2D.31.65.31 c024000000000000
204+2D.31.65.2B.31 c024000000000000
205+2D.31.65.2D.31 bfb999999999999a
206+2D.2E.31.65.31 bff0000000000000
207+2D.31.2E.65.31 c024000000000000
208+2D.69.6E.66.69.6E.69.74.79 fff0000000000000
209+2D.6E.61.6E NaN
210+2D.30.78.41 NaN
211+2D.30.6F.31 NaN
212+2D.30.62.31 NaN
213+30.78.66 402e000000000000
214+30.78.30.31 3ff0000000000000
215+30.6F.30.31 3ff0000000000000
216+30.2E.30.31 3f847ae147ae147b
217+30.6F.37.37 404f800000000000
218+30.78.30.30.31 3ff0000000000000
219+30.6F.30.30.31 3ff0000000000000
220+31.30.30 4059000000000000
221+30.2E.30.30.31 3f50624dd2f1a9fc
222+30.78.66.66.66 40affe0000000000
223+30.78.30.30.30.31 3ff0000000000000
224+30.6F.30.30.30.31 3ff0000000000000
225+31.30.30.30 408f400000000000
226+30.2E.30.30.30.31 3f1a36e2eb1c432d
227+30.78.66.66.66.66 40efffe000000000
228+30.6F.37.37.37.37 40affe0000000000
229+30.78.30.30.30.30.31 3ff0000000000000
230+30.6F.30.30.30.30.31 3ff0000000000000
231+31.30.30.30.30 40c3880000000000
232+30.2E.30.30.30.30.31 3ee4f8b588e368f1
233+30.78.66.66.66.66.66 412ffffe00000000
234+30.6F.37.37.37.37.37 40dfffc000000000
235+30.78.30.30.30.30.30.31 3ff0000000000000
236+30.6F.30.30.30.30.30.31 3ff0000000000000
237+31.30.30.30.30.30 40f86a0000000000
238+30.78.66.66.66.66.66.66 416fffffe0000000
239+30.6F.37.37.37.37.37.37 410ffff800000000
240+30.78.30.30.30.30.30.30.31 3ff0000000000000
241+30.6F.30.30.30.30.30.30.31 3ff0000000000000
242+31.30.30.30.30.30.30 412e848000000000
243+30.2E.30.30.30.30.30.30.31 3e7ad7f29abcaf48
244+30.78.66.66.66.66.66.66.66 41affffffe000000
245+30.6F.37.37.37.37.37.37.37 413fffff00000000
246+30.6F.30.30.30.30.30.30.30.31 3ff0000000000000
247+31.30.30.30.30.30.30.30 416312d000000000
248+30.2E.30.30.30.30.30.30.30.31 3e45798ee2308c3a
249+30.6F.37.37.37.37.37.37.37.37 416fffffe0000000
250+30.78.30.30.30.30.30.30.30.30.31 3ff0000000000000
251+30.6F.30.30.30.30.30.30.30.30.31 3ff0000000000000
252+31.30.30.30.30.30.30.30.30 4197d78400000000
253+30.2E.30.30.30.30.30.30.30.30.31 3e112e0be826d695
254+30.78.66.66.66.66.66.66.66.66.66 NaN
255+30.6F.37.37.37.37.37.37.37.37.37 419ffffffc000000
256+30.78.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
257+30.6F.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
258+31.30.30.30.30.30.30.30.30.30 41cdcd6500000000
259+30.2E.30.30.30.30.30.30.30.30.30.31 3ddb7cdfd9d7bdbb
260+30.78.66.66.66.66.66.66.66.66.66.66 NaN
261+30.6F.37.37.37.37.37.37.37.37.37.37 41cfffffff800000
262+30.78.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
263+30.6F.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
264+31.30.30.30.30.30.30.30.30.30.30 4202a05f20000000
265+30.2E.30.30.30.30.30.30.30.30.30.30.31 3da5fd7fe1796495
266+30.78.66.66.66.66.66.66.66.66.66.66.66 NaN
267+30.78.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
268+31.30.30.30.30.30.30.30.30.30.30.30 42374876e8000000
269+30.2E.30.30.30.30.30.30.30.30.30.30.30.31 3d719799812dea11
270+30.78.66.66.66.66.66.66.66.66.66.66.66.66 NaN
271+30.6F.37.37.37.37.37.37.37.37.37.37.37.37 NaN
272+30.78.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
273+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
274+31.30.30.30.30.30.30.30.30.30.30.30.30 426d1a94a2000000
275+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.31 3d3c25c268497682
276+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
277+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
278+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
279+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
280+31.30.30.30.30.30.30.30.30.30.30.30.30.30 42a2309ce5400000
281+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3d06849b86a12b9b
282+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
283+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
284+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
285+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
286+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30 42d6bcc41e900000
287+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3cd203af9ee75616
288+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
289+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
290+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
291+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 430c6bf526340000
292+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3c9cd2b297d889bc
293+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
294+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
295+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
296+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
297+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4341c37937e08000
298+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3c670ef54646d497
299+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
300+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
301+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
302+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
303+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4376345785d8a000
304+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3c32725dd1d243ac
305+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
306+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
307+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
308+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
309+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 43abc16d674ec800
310+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3bfd83c94fb6d2ac
311+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
312+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
313+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
314+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
315+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 43e158e460913d00
316+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3bc79ca10c924223
317+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
318+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
319+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
320+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4415af1d78b58c40
321+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3b92e3b40a0e9b4f
322+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
323+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
324+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
325+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 444b1ae4d6e2ef50
326+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3b5e392010175ee6
327+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
328+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
329+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
330+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
331+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4480f0cf064dd592
332+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3b282db34012b251
333+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
334+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
335+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
336+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
337+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 44b52d02c7e14af6
338+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3af357c299a88ea7
339+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
340+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
341+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
342+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
343+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 44ea784379d99db4
344+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3abef2d0f5da7dd9
345+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
346+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
347+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
348+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
349+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 45208b2a2c280291
350+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3a88c240c4aecb14
351+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
352+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
353+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
354+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
355+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4554adf4b7320335
356+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3a53ce9a36f23c10
357+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
358+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
359+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
360+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
361+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4589d971e4fe8402
362+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3a1fb0f6be506019
363+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
364+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
365+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
366+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
367+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 45c027e72f1f1281
368+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 39e95a5efea6b347
369+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
370+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
371+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
372+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
373+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 45f431e0fae6d721
374+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 39b4484bfeebc2a0
375+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
376+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
377+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
378+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
379+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 46293e5939a08cea
380+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 398039d665896880
381+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
382+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
383+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
384+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
385+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 465f8def8808b024
386+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3949f623d5a8a733
387+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
388+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
389+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
390+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
391+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4693b8b5b5056e17
392+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3914c4e977ba1f5c
393+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
394+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
395+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
396+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
397+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 46c8a6e32246c99c
398+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 38e09d8792fb4c49
399+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
400+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
401+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
402+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
403+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 46fed09bead87c03
404+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 38aa95a5b7f87a0f
405+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
406+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
407+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
408+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
409+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 4733426172c74d82
410+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 38754484932d2e72
411+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
412+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
413+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
414+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
415+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 476812f9cf7920e3
416+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3841039d428a8b8f
417+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
418+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
419+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
420+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
421+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 479e17b84357691b
422+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 380b38fb9daa78e4
423+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
424+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
425+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
426+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
427+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 47d2ced32a16a1b1
428+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 37d5c72fb1552d83
429+30.78.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66.66 NaN
430+30.6F.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37.37 NaN
431+30.78.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
432+30.6F.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 3ff0000000000000
433+31.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30 48078287f49c4a1d
434+30.2E.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.30.31 37a16c262777579c
435+30.6F.33.37.37 406fe00000000000
436+32.35.35 406fe00000000000
437+30.78.31.30.30 4070000000000000
438+30.6F.34.30.30 4070000000000000
439+32.35.36 4070000000000000
440+32.31.34.37.34.38.33.36.34.37 41dfffffffc00000
441+32.31.34.37.34.38.33.36.34.38 41e0000000000000
442+34.32.39.34.39.36.37.32.39.35 41efffffffe00000
443+34.32.39.34.39.36.37.32.39.36 41f0000000000000
A expr/testdata/ordinal_cases.txt +1564 -0
@@ -0,0 +1,1564 @@
1+- - 1 0 1 1 1
2+- 48.65.6C.6C.6F 0 -1 0 0 0
3+- 53.53 0 -1 0 0 0
4+- 66.66.69 0 -1 0 0 0
5+- 53 0 -1 0 0 0
6+- 3C3 0 -1 0 0 0
7+- 1C5 0 -1 0 0 0
8+- FF 0 -1 0 0 0
9+- 390 0 -1 0 0 0
10+- 1C4.61 0 -1 0 0 0
11+- 13F8 0 -1 0 0 0
12+- FFFF 0 -1 0 0 0
13+- 41.42.D83D.DE00 0 -1 0 0 0
14+- 2D 0 -1 0 0 0
15+61 - 0 1 1 1 1
16+61 61 1 0 1 1 1
17+61 41 1 0 1 1 1
18+61 68.45.4C.4C.4F 0 -1 0 0 0
19+61 73.73 0 -1 0 0 0
20+61 131 0 -1 0 0 0
21+61 212A 0 -1 0 0 0
22+61 3C2 0 -1 0 0 0
23+61 1C6 0 -1 0 0 0
24+61 178 0 -1 0 0 0
25+61 1FFC 0 -1 0 0 0
26+61 416 0 -1 0 0 0
27+61 13F0 0 -1 0 0 0
28+61 D83D.DE00 0 -1 0 0 0
29+61 78.2028.79 0 -1 0 0 0
30+61 5F 0 -1 0 0 0
31+41 - 0 1 1 1 1
32+41 61 1 0 1 1 1
33+41 41 1 0 1 1 1
34+41 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 -1 0 0 0
35+41 1E9E 0 -1 0 0 0
36+41 49 0 -1 0 0 0
37+41 6B 0 -1 0 0 0
38+41 3A3 0 -1 0 0 0
39+41 1C8 0 -1 0 0 0
40+41 B5 0 -1 0 0 0
41+41 1FB3 0 -1 0 0 0
42+41 436 0 -1 0 0 0
43+41 24D0 0 -1 0 0 0
44+41 D801.DC00 0 -1 0 0 0
45+41 78.20.79 0 -1 0 0 0
46+41 61.2D.62 0 -1 0 0 0
47+61.62 - 0 1 1 1 1
48+61.62 61 0 1 1 1 0
49+61.62 41 0 1 1 1 0
50+61.62 61.62 1 0 1 1 1
51+61.62 41.42 1 0 1 1 1
52+61.62 74.72.75.65 0 -1 0 0 0
53+61.62 149 0 -1 0 0 0
54+61.62 69 0 -1 0 0 0
55+61.62 4B 0 -1 0 0 0
56+61.62 391.3A3 0 -1 0 0 0
57+61.62 1C9 0 -1 0 0 0
58+61.62 3BC 0 -1 0 0 0
59+61.62 1FBC 0 -1 0 0 0
60+61.62 10D0 0 -1 0 0 0
61+61.62 24B6 0 -1 0 0 0
62+61.62 D801.DC28 0 -1 0 0 0
63+61.62 78.A0.79 0 -1 0 0 0
64+61.62 61.5F.62 0 -1 0 0 0
65+41.42 - 0 1 1 1 1
66+41.42 61 0 1 1 1 0
67+41.42 41 0 1 1 1 0
68+41.42 61.62 1 0 1 1 1
69+41.42 41.42 1 0 1 1 1
70+41.42 54.52.55.45 0 -1 0 0 0
71+41.42 2BC.4E 0 -1 0 0 0
72+41.42 130 0 -1 0 0 0
73+41.42 212B 0 -1 0 0 0
74+41.42 3B1.3C2 0 -1 0 0 0
75+41.42 E9 0 -1 0 0 0
76+41.42 39C 0 -1 0 0 0
77+41.42 E9 0 -1 0 0 0
78+41.42 1C90 0 -1 0 0 0
79+41.42 FF41 0 -1 0 0 0
80+41.42 D801.DC28 0 -1 0 0 0
81+41.42 30 0 1 0 0 0
82+41.42 5B.5D 0 -1 0 0 0
83+61.62.63 - 0 1 1 1 1
84+61.62.63 61 0 1 1 1 0
85+61.62.63 41 0 1 1 1 0
86+61.62.63 61.62 0 1 1 1 0
87+61.62.63 41.42 0 1 1 1 0
88+61.62.63 61.62.63 1 0 1 1 1
89+61.62.63 41.42.43 1 0 1 1 1
90+61.62.63 6E.75.6C.6C 0 -1 0 0 0
91+61.62.63 FB03 0 -1 0 0 0
92+61.62.63 17F 0 -1 0 0 0
93+61.62.63 E5 0 -1 0 0 0
94+61.62.63 3B1.3C3 0 -1 0 0 0
95+61.62.63 C9 0 -1 0 0 0
96+61.62.63 1F0 0 -1 0 0 0
97+61.62.63 C9 0 -1 0 0 0
98+61.62.63 AB70 0 -1 0 0 0
99+61.62.63 FF21 0 -1 0 0 0
100+61.62.63 D800.DC00 0 -1 0 0 0
101+61.62.63 30.30 0 1 0 0 0
102+61.62.63 27 0 1 0 0 0
103+41.42.43 - 0 1 1 1 1
104+41.42.43 61 0 1 1 1 0
105+41.42.43 41 0 1 1 1 0
106+41.42.43 61.62 0 1 1 1 0
107+41.42.43 41.42 0 1 1 1 0
108+41.42.43 61.62.63 1 0 1 1 1
109+41.42.43 41.42.43 1 0 1 1 1
110+41.42.43 DF 0 -1 0 0 0
111+41.42.43 46.46.49 0 -1 0 0 0
112+41.42.43 73 0 -1 0 0 0
113+41.42.43 C5 0 -1 0 0 0
114+41.42.43 1C4 0 -1 0 0 0
115+41.42.43 65.301 0 -1 0 0 0
116+41.42.43 4A.30C 0 -1 0 0 0
117+41.42.43 1C5.61 0 -1 0 0 0
118+41.42.43 13A0 0 -1 0 0 0
119+41.42.43 E000 0 -1 0 0 0
120+41.42.43 61.62.D83D.DE00 0 -1 0 0 0
121+41.42.43 31.65.33 0 1 0 0 0
122+48.65.6C.6C.6F - 0 1 1 1 1
123+48.65.6C.6C.6F 48.65.6C.6C.6F 1 0 1 1 1
124+48.65.6C.6C.6F 68.45.4C.4C.4F 1 0 1 1 1
125+48.65.6C.6C.6F 53.53 0 -1 0 0 0
126+48.65.6C.6C.6F 66.66.69 0 1 0 0 0
127+48.65.6C.6C.6F 53 0 -1 0 0 0
128+48.65.6C.6C.6F 3C3 0 -1 0 0 0
129+48.65.6C.6C.6F 1C5 0 -1 0 0 0
130+48.65.6C.6C.6F FF 0 -1 0 0 0
131+48.65.6C.6C.6F 390 0 -1 0 0 0
132+48.65.6C.6C.6F 1C4.61 0 -1 0 0 0
133+48.65.6C.6C.6F 13F8 0 -1 0 0 0
134+48.65.6C.6C.6F FFFF 0 -1 0 0 0
135+48.65.6C.6C.6F 41.42.D83D.DE00 0 1 0 0 0
136+48.65.6C.6C.6F 2D 0 1 0 0 0
137+68.45.4C.4C.4F - 0 1 1 1 1
138+68.45.4C.4C.4F 61 0 1 0 0 0
139+68.45.4C.4C.4F 48.65.6C.6C.6F 1 0 1 1 1
140+68.45.4C.4C.4F 68.45.4C.4C.4F 1 0 1 1 1
141+68.45.4C.4C.4F 73.73 0 -1 0 0 0
142+68.45.4C.4C.4F 131 0 -1 0 0 0
143+68.45.4C.4C.4F 212A 0 -1 0 0 0
144+68.45.4C.4C.4F 3C2 0 -1 0 0 0
145+68.45.4C.4C.4F 1C6 0 -1 0 0 0
146+68.45.4C.4C.4F 178 0 -1 0 0 0
147+68.45.4C.4C.4F 1FFC 0 -1 0 0 0
148+68.45.4C.4C.4F 416 0 -1 0 0 0
149+68.45.4C.4C.4F 13F0 0 -1 0 0 0
150+68.45.4C.4C.4F D83D.DE00 0 -1 0 0 0
151+68.45.4C.4C.4F 78.2028.79 0 -1 0 0 0
152+68.45.4C.4C.4F 5F 0 -1 0 0 0
153+68.65.6C.6C.6F.20.77.6F.72.6C.64 - 0 1 1 1 1
154+68.65.6C.6C.6F.20.77.6F.72.6C.64 41 0 1 0 0 0
155+68.65.6C.6C.6F.20.77.6F.72.6C.64 48.65.6C.6C.6F 0 1 1 1 0
156+68.65.6C.6C.6F.20.77.6F.72.6C.64 68.45.4C.4C.4F 0 1 1 1 0
157+68.65.6C.6C.6F.20.77.6F.72.6C.64 68.65.6C.6C.6F.20.77.6F.72.6C.64 1 0 1 1 1
158+68.65.6C.6C.6F.20.77.6F.72.6C.64 1E9E 0 -1 0 0 0
159+68.65.6C.6C.6F.20.77.6F.72.6C.64 49 0 -1 0 0 0
160+68.65.6C.6C.6F.20.77.6F.72.6C.64 6B 0 -1 0 0 0
161+68.65.6C.6C.6F.20.77.6F.72.6C.64 3A3 0 -1 0 0 0
162+68.65.6C.6C.6F.20.77.6F.72.6C.64 1C8 0 -1 0 0 0
163+68.65.6C.6C.6F.20.77.6F.72.6C.64 B5 0 -1 0 0 0
164+68.65.6C.6C.6F.20.77.6F.72.6C.64 1FB3 0 -1 0 0 0
165+68.65.6C.6C.6F.20.77.6F.72.6C.64 436 0 -1 0 0 0
166+68.65.6C.6C.6F.20.77.6F.72.6C.64 24D0 0 -1 0 0 0
167+68.65.6C.6C.6F.20.77.6F.72.6C.64 D801.DC00 0 -1 0 0 0
168+68.65.6C.6C.6F.20.77.6F.72.6C.64 78.20.79 0 -1 0 0 0
169+68.65.6C.6C.6F.20.77.6F.72.6C.64 61.2D.62 0 1 0 0 0
170+74.72.75.65 - 0 1 1 1 1
171+74.72.75.65 61.62 0 1 0 0 0
172+74.72.75.65 74.72.75.65 1 0 1 1 1
173+74.72.75.65 54.52.55.45 1 0 1 1 1
174+74.72.75.65 149 0 -1 0 0 0
175+74.72.75.65 69 0 1 0 0 0
176+74.72.75.65 4B 0 1 0 0 0
177+74.72.75.65 391.3A3 0 -1 0 0 0
178+74.72.75.65 1C9 0 -1 0 0 0
179+74.72.75.65 3BC 0 -1 0 0 0
180+74.72.75.65 1FBC 0 -1 0 0 0
181+74.72.75.65 10D0 0 -1 0 0 0
182+74.72.75.65 24B6 0 -1 0 0 0
183+74.72.75.65 D801.DC28 0 -1 0 0 0
184+74.72.75.65 78.A0.79 0 -1 0 0 0
185+74.72.75.65 61.5F.62 0 1 0 0 0
186+54.52.55.45 - 0 1 1 1 1
187+54.52.55.45 41.42 0 1 0 0 0
188+54.52.55.45 74.72.75.65 1 0 1 1 1
189+54.52.55.45 54.52.55.45 1 0 1 1 1
190+54.52.55.45 2BC.4E 0 -1 0 0 0
191+54.52.55.45 130 0 -1 0 0 0
192+54.52.55.45 212B 0 -1 0 0 0
193+54.52.55.45 3B1.3C2 0 -1 0 0 0
194+54.52.55.45 E9 0 -1 0 0 0
195+54.52.55.45 39C 0 -1 0 0 0
196+54.52.55.45 E9 0 -1 0 0 0
197+54.52.55.45 1C90 0 -1 0 0 0
198+54.52.55.45 FF41 0 -1 0 0 0
199+54.52.55.45 D801.DC28 0 -1 0 0 0
200+54.52.55.45 30 0 1 0 0 0
201+54.52.55.45 5B.5D 0 -1 0 0 0
202+6E.75.6C.6C - 0 1 1 1 1
203+6E.75.6C.6C 61.62.63 0 1 0 0 0
204+6E.75.6C.6C 6E.75.6C.6C 1 0 1 1 1
205+6E.75.6C.6C FB03 0 -1 0 0 0
206+6E.75.6C.6C 17F 0 -1 0 0 0
207+6E.75.6C.6C E5 0 -1 0 0 0
208+6E.75.6C.6C 3B1.3C3 0 -1 0 0 0
209+6E.75.6C.6C C9 0 -1 0 0 0
210+6E.75.6C.6C 1F0 0 -1 0 0 0
211+6E.75.6C.6C C9 0 -1 0 0 0
212+6E.75.6C.6C AB70 0 -1 0 0 0
213+6E.75.6C.6C FF21 0 -1 0 0 0
214+6E.75.6C.6C D800.DC00 0 -1 0 0 0
215+6E.75.6C.6C 30.30 0 1 0 0 0
216+6E.75.6C.6C 27 0 1 0 0 0
217+DF - 0 1 1 1 1
218+DF 41.42.43 0 1 0 0 0
219+DF DF 1 0 1 1 1
220+DF 46.46.49 0 1 0 0 0
221+DF 73 0 1 0 0 0
222+DF C5 0 1 0 0 0
223+DF 1C4 0 -1 0 0 0
224+DF 65.301 0 1 0 0 0
225+DF 4A.30C 0 1 0 0 0
226+DF 1C5.61 0 -1 0 0 0
227+DF 13A0 0 -1 0 0 0
228+DF E000 0 -1 0 0 0
229+DF 61.62.D83D.DE00 0 1 0 0 0
230+DF 31.65.33 0 1 0 0 0
231+53.53 - 0 1 1 1 1
232+53.53 48.65.6C.6C.6F 0 1 0 0 0
233+53.53 53.53 1 0 1 1 1
234+53.53 73.73 1 0 1 1 1
235+53.53 66.66.69 0 1 0 0 0
236+53.53 73 0 1 1 1 1
237+53.53 53 0 1 1 1 1
238+53.53 3C3 0 -1 0 0 0
239+53.53 1C5 0 -1 0 0 0
240+53.53 FF 0 -1 0 0 0
241+53.53 390 0 -1 0 0 0
242+53.53 1C4.61 0 -1 0 0 0
243+53.53 13F8 0 -1 0 0 0
244+53.53 FFFF 0 -1 0 0 0
245+53.53 41.42.D83D.DE00 0 1 0 0 0
246+53.53 2D 0 1 0 0 0
247+73.73 - 0 1 1 1 1
248+73.73 61 0 1 0 0 0
249+73.73 68.45.4C.4C.4F 0 1 0 0 0
250+73.73 53.53 1 0 1 1 1
251+73.73 73.73 1 0 1 1 1
252+73.73 131 0 -1 0 0 0
253+73.73 73 0 1 1 1 1
254+73.73 53 0 1 1 1 1
255+73.73 212A 0 -1 0 0 0
256+73.73 3C2 0 -1 0 0 0
257+73.73 1C6 0 -1 0 0 0
258+73.73 178 0 -1 0 0 0
259+73.73 1FFC 0 -1 0 0 0
260+73.73 416 0 -1 0 0 0
261+73.73 13F0 0 -1 0 0 0
262+73.73 D83D.DE00 0 -1 0 0 0
263+73.73 78.2028.79 0 -1 0 0 0
264+73.73 5F 0 -1 0 0 0
265+1E9E - 0 1 1 1 1
266+1E9E 41 0 1 0 0 0
267+1E9E 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
268+1E9E 1E9E 1 0 1 1 1
269+1E9E 49 0 1 0 0 0
270+1E9E 6B 0 1 0 0 0
271+1E9E 3A3 0 1 0 0 0
272+1E9E 1C8 0 1 0 0 0
273+1E9E B5 0 1 0 0 0
274+1E9E 1FB3 0 -1 0 0 0
275+1E9E 436 0 1 0 0 0
276+1E9E 24D0 0 -1 0 0 0
277+1E9E D801.DC00 0 -1 0 0 0
278+1E9E 78.20.79 0 1 0 0 0
279+1E9E 61.2D.62 0 1 0 0 0
280+149 - 0 1 1 1 1
281+149 61.62 0 1 0 0 0
282+149 74.72.75.65 0 1 0 0 0
283+149 149 1 0 1 1 1
284+149 69 0 1 0 0 0
285+149 4B 0 1 0 0 0
286+149 391.3A3 0 -1 0 0 0
287+149 1C9 0 -1 0 0 0
288+149 3BC 0 -1 0 0 0
289+149 1FBC 0 -1 0 0 0
290+149 10D0 0 -1 0 0 0
291+149 24B6 0 -1 0 0 0
292+149 D801.DC28 0 -1 0 0 0
293+149 78.A0.79 0 1 0 0 0
294+149 61.5F.62 0 1 0 0 0
295+2BC.4E - 0 1 1 1 1
296+2BC.4E 41.42 0 1 0 0 0
297+2BC.4E 54.52.55.45 0 1 0 0 0
298+2BC.4E 2BC.4E 1 0 1 1 1
299+2BC.4E 130 0 1 0 0 0
300+2BC.4E 212B 0 -1 0 0 0
301+2BC.4E 3B1.3C2 0 -1 0 0 0
302+2BC.4E E9 0 1 0 0 0
303+2BC.4E 39C 0 -1 0 0 0
304+2BC.4E E9 0 1 0 0 0
305+2BC.4E 1C90 0 -1 0 0 0
306+2BC.4E FF41 0 -1 0 0 0
307+2BC.4E D801.DC28 0 -1 0 0 0
308+2BC.4E 30 0 1 0 0 0
309+2BC.4E 5B.5D 0 1 0 0 0
310+FB03 - 0 1 1 1 1
311+FB03 61.62.63 0 1 0 0 0
312+FB03 6E.75.6C.6C 0 1 0 0 0
313+FB03 FB03 1 0 1 1 1
314+FB03 17F 0 1 0 0 0
315+FB03 E5 0 1 0 0 0
316+FB03 3B1.3C3 0 1 0 0 0
317+FB03 C9 0 1 0 0 0
318+FB03 1F0 0 1 0 0 0
319+FB03 C9 0 1 0 0 0
320+FB03 AB70 0 1 0 0 0
321+FB03 FF21 0 -1 0 0 0
322+FB03 D800.DC00 0 -1 0 0 0
323+FB03 30.30 0 1 0 0 0
324+FB03 27 0 1 0 0 0
325+46.46.49 - 0 1 1 1 1
326+46.46.49 41.42.43 0 1 0 0 0
327+46.46.49 DF 0 -1 0 0 0
328+46.46.49 46.46.49 1 0 1 1 1
329+46.46.49 66.66.69 1 0 1 1 1
330+46.46.49 49 0 -1 1 0 1
331+46.46.49 69 0 -1 1 0 1
332+46.46.49 73 0 -1 0 0 0
333+46.46.49 C5 0 -1 0 0 0
334+46.46.49 1C4 0 -1 0 0 0
335+46.46.49 65.301 0 1 0 0 0
336+46.46.49 4A.30C 0 -1 0 0 0
337+46.46.49 1C5.61 0 -1 0 0 0
338+46.46.49 13A0 0 -1 0 0 0
339+46.46.49 E000 0 -1 0 0 0
340+46.46.49 61.62.D83D.DE00 0 1 0 0 0
341+46.46.49 31.65.33 0 1 0 0 0
342+66.66.69 - 0 1 1 1 1
343+66.66.69 48.65.6C.6C.6F 0 -1 0 0 0
344+66.66.69 53.53 0 -1 0 0 0
345+66.66.69 46.46.49 1 0 1 1 1
346+66.66.69 66.66.69 1 0 1 1 1
347+66.66.69 49 0 -1 1 0 1
348+66.66.69 69 0 -1 1 0 1
349+66.66.69 53 0 -1 0 0 0
350+66.66.69 3C3 0 -1 0 0 0
351+66.66.69 1C5 0 -1 0 0 0
352+66.66.69 FF 0 -1 0 0 0
353+66.66.69 390 0 -1 0 0 0
354+66.66.69 1C4.61 0 -1 0 0 0
355+66.66.69 13F8 0 -1 0 0 0
356+66.66.69 FFFF 0 -1 0 0 0
357+66.66.69 41.42.D83D.DE00 0 1 0 0 0
358+66.66.69 2D 0 1 0 0 0
359+131 - 0 1 1 1 1
360+131 61 0 1 0 0 0
361+131 68.45.4C.4C.4F 0 1 0 0 0
362+131 73.73 0 1 0 0 0
363+131 131 1 0 1 1 1
364+131 212A 0 -1 0 0 0
365+131 3C2 0 -1 0 0 0
366+131 1C6 0 -1 0 0 0
367+131 178 0 -1 0 0 0
368+131 1FFC 0 -1 0 0 0
369+131 416 0 -1 0 0 0
370+131 13F0 0 -1 0 0 0
371+131 D83D.DE00 0 -1 0 0 0
372+131 78.2028.79 0 1 0 0 0
373+131 5F 0 1 0 0 0
374+49 - 0 1 1 1 1
375+49 41 0 1 0 0 0
376+49 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
377+49 1E9E 0 -1 0 0 0
378+49 49 1 0 1 1 1
379+49 69 1 0 1 1 1
380+49 6B 0 -1 0 0 0
381+49 3A3 0 -1 0 0 0
382+49 1C8 0 -1 0 0 0
383+49 B5 0 -1 0 0 0
384+49 1FB3 0 -1 0 0 0
385+49 436 0 -1 0 0 0
386+49 24D0 0 -1 0 0 0
387+49 D801.DC00 0 -1 0 0 0
388+49 78.20.79 0 -1 0 0 0
389+49 61.2D.62 0 1 0 0 0
390+69 - 0 1 1 1 1
391+69 61.62 0 1 0 0 0
392+69 74.72.75.65 0 -1 0 0 0
393+69 149 0 -1 0 0 0
394+69 49 1 0 1 1 1
395+69 69 1 0 1 1 1
396+69 4B 0 -1 0 0 0
397+69 391.3A3 0 -1 0 0 0
398+69 1C9 0 -1 0 0 0
399+69 3BC 0 -1 0 0 0
400+69 1FBC 0 -1 0 0 0
401+69 10D0 0 -1 0 0 0
402+69 24B6 0 -1 0 0 0
403+69 D801.DC28 0 -1 0 0 0
404+69 78.A0.79 0 -1 0 0 0
405+69 61.5F.62 0 1 0 0 0
406+130 - 0 1 1 1 1
407+130 41.42 0 1 0 0 0
408+130 54.52.55.45 0 1 0 0 0
409+130 2BC.4E 0 -1 0 0 0
410+130 130 1 0 1 1 1
411+130 212B 0 -1 0 0 0
412+130 3B1.3C2 0 -1 0 0 0
413+130 E9 0 1 0 0 0
414+130 39C 0 -1 0 0 0
415+130 E9 0 1 0 0 0
416+130 1C90 0 -1 0 0 0
417+130 FF41 0 -1 0 0 0
418+130 D801.DC28 0 -1 0 0 0
419+130 30 0 1 0 0 0
420+130 5B.5D 0 1 0 0 0
421+17F - 0 1 1 1 1
422+17F 61.62.63 0 1 0 0 0
423+17F 6E.75.6C.6C 0 1 0 0 0
424+17F FB03 0 -1 0 0 0
425+17F 17F 1 0 1 1 1
426+17F E5 0 1 0 0 0
427+17F 3B1.3C3 0 -1 0 0 0
428+17F C9 0 1 0 0 0
429+17F 1F0 0 -1 0 0 0
430+17F C9 0 1 0 0 0
431+17F AB70 0 -1 0 0 0
432+17F FF21 0 -1 0 0 0
433+17F D800.DC00 0 -1 0 0 0
434+17F 30.30 0 1 0 0 0
435+17F 27 0 1 0 0 0
436+73 - 0 1 1 1 1
437+73 41.42.43 0 1 0 0 0
438+73 DF 0 -1 0 0 0
439+73 46.46.49 0 1 0 0 0
440+73 73 1 0 1 1 1
441+73 53 1 0 1 1 1
442+73 C5 0 -1 0 0 0
443+73 1C4 0 -1 0 0 0
444+73 65.301 0 1 0 0 0
445+73 4A.30C 0 1 0 0 0
446+73 1C5.61 0 -1 0 0 0
447+73 13A0 0 -1 0 0 0
448+73 E000 0 -1 0 0 0
449+73 61.62.D83D.DE00 0 1 0 0 0
450+73 31.65.33 0 1 0 0 0
451+53 - 0 1 1 1 1
452+53 48.65.6C.6C.6F 0 1 0 0 0
453+53 53.53 0 -1 0 0 0
454+53 66.66.69 0 1 0 0 0
455+53 73 1 0 1 1 1
456+53 53 1 0 1 1 1
457+53 3C3 0 -1 0 0 0
458+53 1C5 0 -1 0 0 0
459+53 FF 0 -1 0 0 0
460+53 390 0 -1 0 0 0
461+53 1C4.61 0 -1 0 0 0
462+53 13F8 0 -1 0 0 0
463+53 FFFF 0 -1 0 0 0
464+53 41.42.D83D.DE00 0 1 0 0 0
465+53 2D 0 1 0 0 0
466+212A - 0 1 1 1 1
467+212A 61 0 1 0 0 0
468+212A 68.45.4C.4C.4F 0 1 0 0 0
469+212A 73.73 0 1 0 0 0
470+212A 131 0 1 0 0 0
471+212A 212A 1 0 1 1 1
472+212A 3C2 0 1 0 0 0
473+212A 1C6 0 1 0 0 0
474+212A 178 0 1 0 0 0
475+212A 1FFC 0 1 0 0 0
476+212A 416 0 1 0 0 0
477+212A 13F0 0 1 0 0 0
478+212A D83D.DE00 0 -1 0 0 0
479+212A 78.2028.79 0 1 0 0 0
480+212A 5F 0 1 0 0 0
481+6B - 0 1 1 1 1
482+6B 41 0 1 0 0 0
483+6B 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
484+6B 1E9E 0 -1 0 0 0
485+6B 49 0 1 0 0 0
486+6B 6B 1 0 1 1 1
487+6B 4B 1 0 1 1 1
488+6B 3A3 0 -1 0 0 0
489+6B 1C8 0 -1 0 0 0
490+6B B5 0 -1 0 0 0
491+6B 1FB3 0 -1 0 0 0
492+6B 436 0 -1 0 0 0
493+6B 24D0 0 -1 0 0 0
494+6B D801.DC00 0 -1 0 0 0
495+6B 78.20.79 0 -1 0 0 0
496+6B 61.2D.62 0 1 0 0 0
497+4B - 0 1 1 1 1
498+4B 61.62 0 1 0 0 0
499+4B 74.72.75.65 0 -1 0 0 0
500+4B 149 0 -1 0 0 0
501+4B 69 0 1 0 0 0
502+4B 6B 1 0 1 1 1
503+4B 4B 1 0 1 1 1
504+4B 391.3A3 0 -1 0 0 0
505+4B 1C9 0 -1 0 0 0
506+4B 3BC 0 -1 0 0 0
507+4B 1FBC 0 -1 0 0 0
508+4B 10D0 0 -1 0 0 0
509+4B 24B6 0 -1 0 0 0
510+4B D801.DC28 0 -1 0 0 0
511+4B 78.A0.79 0 -1 0 0 0
512+4B 61.5F.62 0 1 0 0 0
513+212B - 0 1 1 1 1
514+212B 41.42 0 1 0 0 0
515+212B 54.52.55.45 0 1 0 0 0
516+212B 2BC.4E 0 1 0 0 0
517+212B 130 0 1 0 0 0
518+212B 212B 1 0 1 1 1
519+212B 3B1.3C2 0 1 0 0 0
520+212B E9 0 1 0 0 0
521+212B 39C 0 1 0 0 0
522+212B E9 0 1 0 0 0
523+212B 1C90 0 1 0 0 0
524+212B FF41 0 -1 0 0 0
525+212B D801.DC28 0 -1 0 0 0
526+212B 30 0 1 0 0 0
527+212B 5B.5D 0 1 0 0 0
528+E5 - 0 1 1 1 1
529+E5 61.62.63 0 1 0 0 0
530+E5 6E.75.6C.6C 0 1 0 0 0
531+E5 FB03 0 -1 0 0 0
532+E5 17F 0 -1 0 0 0
533+E5 E5 1 0 1 1 1
534+E5 C5 1 0 1 1 1
535+E5 3B1.3C3 0 -1 0 0 0
536+E5 C9 0 -1 0 0 0
537+E5 1F0 0 -1 0 0 0
538+E5 C9 0 -1 0 0 0
539+E5 AB70 0 -1 0 0 0
540+E5 FF21 0 -1 0 0 0
541+E5 D800.DC00 0 -1 0 0 0
542+E5 30.30 0 1 0 0 0
543+E5 27 0 1 0 0 0
544+C5 - 0 1 1 1 1
545+C5 41.42.43 0 1 0 0 0
546+C5 DF 0 -1 0 0 0
547+C5 46.46.49 0 1 0 0 0
548+C5 73 0 1 0 0 0
549+C5 E5 1 0 1 1 1
550+C5 C5 1 0 1 1 1
551+C5 1C4 0 -1 0 0 0
552+C5 65.301 0 1 0 0 0
553+C5 4A.30C 0 1 0 0 0
554+C5 1C5.61 0 -1 0 0 0
555+C5 13A0 0 -1 0 0 0
556+C5 E000 0 -1 0 0 0
557+C5 61.62.D83D.DE00 0 1 0 0 0
558+C5 31.65.33 0 1 0 0 0
559+3C3 - 0 1 1 1 1
560+3C3 48.65.6C.6C.6F 0 1 0 0 0
561+3C3 53.53 0 1 0 0 0
562+3C3 66.66.69 0 1 0 0 0
563+3C3 53 0 1 0 0 0
564+3C3 3C3 1 0 1 1 1
565+3C3 3C2 1 0 1 1 1
566+3C3 3A3 1 0 1 1 1
567+3C3 1C5 0 1 0 0 0
568+3C3 FF 0 1 0 0 0
569+3C3 390 0 1 0 0 0
570+3C3 1C4.61 0 1 0 0 0
571+3C3 13F8 0 -1 0 0 0
572+3C3 FFFF 0 -1 0 0 0
573+3C3 41.42.D83D.DE00 0 1 0 0 0
574+3C3 2D 0 1 0 0 0
575+3C2 - 0 1 1 1 1
576+3C2 61 0 1 0 0 0
577+3C2 68.45.4C.4C.4F 0 1 0 0 0
578+3C2 73.73 0 1 0 0 0
579+3C2 131 0 1 0 0 0
580+3C2 212A 0 -1 0 0 0
581+3C2 3C3 1 0 1 1 1
582+3C2 3C2 1 0 1 1 1
583+3C2 3A3 1 0 1 1 1
584+3C2 1C6 0 1 0 0 0
585+3C2 178 0 1 0 0 0
586+3C2 1FFC 0 -1 0 0 0
587+3C2 416 0 -1 0 0 0
588+3C2 13F0 0 -1 0 0 0
589+3C2 D83D.DE00 0 -1 0 0 0
590+3C2 78.2028.79 0 1 0 0 0
591+3C2 5F 0 1 0 0 0
592+3A3 - 0 1 1 1 1
593+3A3 41 0 1 0 0 0
594+3A3 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
595+3A3 1E9E 0 -1 0 0 0
596+3A3 49 0 1 0 0 0
597+3A3 6B 0 1 0 0 0
598+3A3 3C3 1 0 1 1 1
599+3A3 3C2 1 0 1 1 1
600+3A3 3A3 1 0 1 1 1
601+3A3 1C8 0 1 0 0 0
602+3A3 B5 0 1 0 0 0
603+3A3 1FB3 0 -1 0 0 0
604+3A3 436 0 -1 0 0 0
605+3A3 24D0 0 -1 0 0 0
606+3A3 D801.DC00 0 -1 0 0 0
607+3A3 78.20.79 0 1 0 0 0
608+3A3 61.2D.62 0 1 0 0 0
609+391.3A3 - 0 1 1 1 1
610+391.3A3 61.62 0 1 0 0 0
611+391.3A3 74.72.75.65 0 1 0 0 0
612+391.3A3 149 0 1 0 0 0
613+391.3A3 69 0 1 0 0 0
614+391.3A3 4B 0 1 0 0 0
615+391.3A3 3C3 0 -1 1 0 1
616+391.3A3 3C2 0 -1 1 0 1
617+391.3A3 3A3 0 -1 1 0 1
618+391.3A3 391.3A3 1 0 1 1 1
619+391.3A3 3B1.3C2 1 0 1 1 1
620+391.3A3 3B1.3C3 1 0 1 1 1
621+391.3A3 1C9 0 1 0 0 0
622+391.3A3 3BC 0 -1 0 0 0
623+391.3A3 1FBC 0 -1 0 0 0
624+391.3A3 10D0 0 -1 0 0 0
625+391.3A3 24B6 0 -1 0 0 0
626+391.3A3 D801.DC28 0 -1 0 0 0
627+391.3A3 78.A0.79 0 1 0 0 0
628+391.3A3 61.5F.62 0 1 0 0 0
629+3B1.3C2 - 0 1 1 1 1
630+3B1.3C2 41.42 0 1 0 0 0
631+3B1.3C2 54.52.55.45 0 1 0 0 0
632+3B1.3C2 2BC.4E 0 1 0 0 0
633+3B1.3C2 130 0 1 0 0 0
634+3B1.3C2 212B 0 -1 0 0 0
635+3B1.3C2 3C3 0 -1 1 0 1
636+3B1.3C2 3C2 0 -1 1 0 1
637+3B1.3C2 3A3 0 -1 1 0 1
638+3B1.3C2 391.3A3 1 0 1 1 1
639+3B1.3C2 3B1.3C2 1 0 1 1 1
640+3B1.3C2 3B1.3C3 1 0 1 1 1
641+3B1.3C2 E9 0 1 0 0 0
642+3B1.3C2 39C 0 -1 0 0 0
643+3B1.3C2 E9 0 1 0 0 0
644+3B1.3C2 1C90 0 -1 0 0 0
645+3B1.3C2 FF41 0 -1 0 0 0
646+3B1.3C2 D801.DC28 0 -1 0 0 0
647+3B1.3C2 30 0 1 0 0 0
648+3B1.3C2 5B.5D 0 1 0 0 0
649+3B1.3C3 - 0 1 1 1 1
650+3B1.3C3 61.62.63 0 1 0 0 0
651+3B1.3C3 6E.75.6C.6C 0 1 0 0 0
652+3B1.3C3 FB03 0 -1 0 0 0
653+3B1.3C3 17F 0 1 0 0 0
654+3B1.3C3 E5 0 1 0 0 0
655+3B1.3C3 3C3 0 -1 1 0 1
656+3B1.3C3 3C2 0 -1 1 0 1
657+3B1.3C3 3A3 0 -1 1 0 1
658+3B1.3C3 391.3A3 1 0 1 1 1
659+3B1.3C3 3B1.3C2 1 0 1 1 1
660+3B1.3C3 3B1.3C3 1 0 1 1 1
661+3B1.3C3 C9 0 1 0 0 0
662+3B1.3C3 1F0 0 1 0 0 0
663+3B1.3C3 C9 0 1 0 0 0
664+3B1.3C3 AB70 0 -1 0 0 0
665+3B1.3C3 FF21 0 -1 0 0 0
666+3B1.3C3 D800.DC00 0 -1 0 0 0
667+3B1.3C3 30.30 0 1 0 0 0
668+3B1.3C3 27 0 1 0 0 0
669+1C4 - 0 1 1 1 1
670+1C4 41.42.43 0 1 0 0 0
671+1C4 DF 0 1 0 0 0
672+1C4 46.46.49 0 1 0 0 0
673+1C4 73 0 1 0 0 0
674+1C4 C5 0 1 0 0 0
675+1C4 1C4 1 0 1 1 1
676+1C4 1C5 1 0 1 1 1
677+1C4 1C6 1 0 1 1 1
678+1C4 65.301 0 1 0 0 0
679+1C4 4A.30C 0 1 0 0 0
680+1C4 1C5.61 0 -1 0 0 0
681+1C4 13A0 0 -1 0 0 0
682+1C4 E000 0 -1 0 0 0
683+1C4 61.62.D83D.DE00 0 1 0 0 0
684+1C4 31.65.33 0 1 0 0 0
685+1C5 - 0 1 1 1 1
686+1C5 48.65.6C.6C.6F 0 1 0 0 0
687+1C5 53.53 0 1 0 0 0
688+1C5 66.66.69 0 1 0 0 0
689+1C5 53 0 1 0 0 0
690+1C5 3C3 0 -1 0 0 0
691+1C5 1C4 1 0 1 1 1
692+1C5 1C5 1 0 1 1 1
693+1C5 1C6 1 0 1 1 1
694+1C5 FF 0 1 0 0 0
695+1C5 390 0 -1 0 0 0
696+1C5 1C4.61 0 -1 0 0 0
697+1C5 13F8 0 -1 0 0 0
698+1C5 FFFF 0 -1 0 0 0
699+1C5 41.42.D83D.DE00 0 1 0 0 0
700+1C5 2D 0 1 0 0 0
701+1C6 - 0 1 1 1 1
702+1C6 61 0 1 0 0 0
703+1C6 68.45.4C.4C.4F 0 1 0 0 0
704+1C6 73.73 0 1 0 0 0
705+1C6 131 0 1 0 0 0
706+1C6 212A 0 -1 0 0 0
707+1C6 3C2 0 -1 0 0 0
708+1C6 1C4 1 0 1 1 1
709+1C6 1C5 1 0 1 1 1
710+1C6 1C6 1 0 1 1 1
711+1C6 178 0 1 0 0 0
712+1C6 1FFC 0 -1 0 0 0
713+1C6 416 0 -1 0 0 0
714+1C6 13F0 0 -1 0 0 0
715+1C6 D83D.DE00 0 -1 0 0 0
716+1C6 78.2028.79 0 1 0 0 0
717+1C6 5F 0 1 0 0 0
718+1C8 - 0 1 1 1 1
719+1C8 41 0 1 0 0 0
720+1C8 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
721+1C8 1E9E 0 -1 0 0 0
722+1C8 49 0 1 0 0 0
723+1C8 6B 0 1 0 0 0
724+1C8 3A3 0 -1 0 0 0
725+1C8 1C8 1 0 1 1 1
726+1C8 1C9 1 0 1 1 1
727+1C8 B5 0 -1 0 0 0
728+1C8 1FB3 0 -1 0 0 0
729+1C8 436 0 -1 0 0 0
730+1C8 24D0 0 -1 0 0 0
731+1C8 D801.DC00 0 -1 0 0 0
732+1C8 78.20.79 0 1 0 0 0
733+1C8 61.2D.62 0 1 0 0 0
734+1C9 - 0 1 1 1 1
735+1C9 61.62 0 1 0 0 0
736+1C9 74.72.75.65 0 1 0 0 0
737+1C9 149 0 1 0 0 0
738+1C9 69 0 1 0 0 0
739+1C9 4B 0 1 0 0 0
740+1C9 391.3A3 0 -1 0 0 0
741+1C9 1C8 1 0 1 1 1
742+1C9 1C9 1 0 1 1 1
743+1C9 3BC 0 -1 0 0 0
744+1C9 1FBC 0 -1 0 0 0
745+1C9 10D0 0 -1 0 0 0
746+1C9 24B6 0 -1 0 0 0
747+1C9 D801.DC28 0 -1 0 0 0
748+1C9 78.A0.79 0 1 0 0 0
749+1C9 61.5F.62 0 1 0 0 0
750+E9 - 0 1 1 1 1
751+E9 41.42 0 1 0 0 0
752+E9 54.52.55.45 0 1 0 0 0
753+E9 2BC.4E 0 -1 0 0 0
754+E9 130 0 -1 0 0 0
755+E9 212B 0 -1 0 0 0
756+E9 3B1.3C2 0 -1 0 0 0
757+E9 E9 1 0 1 1 1
758+E9 C9 1 0 1 1 1
759+E9 39C 0 -1 0 0 0
760+E9 E9 1 0 1 1 1
761+E9 C9 1 0 1 1 1
762+E9 1C90 0 -1 0 0 0
763+E9 FF41 0 -1 0 0 0
764+E9 D801.DC28 0 -1 0 0 0
765+E9 30 0 1 0 0 0
766+E9 5B.5D 0 1 0 0 0
767+C9 - 0 1 1 1 1
768+C9 61.62.63 0 1 0 0 0
769+C9 6E.75.6C.6C 0 1 0 0 0
770+C9 FB03 0 -1 0 0 0
771+C9 17F 0 -1 0 0 0
772+C9 E5 0 1 0 0 0
773+C9 3B1.3C3 0 -1 0 0 0
774+C9 E9 1 0 1 1 1
775+C9 C9 1 0 1 1 1
776+C9 1F0 0 -1 0 0 0
777+C9 E9 1 0 1 1 1
778+C9 C9 1 0 1 1 1
779+C9 AB70 0 -1 0 0 0
780+C9 FF21 0 -1 0 0 0
781+C9 D800.DC00 0 -1 0 0 0
782+C9 30.30 0 1 0 0 0
783+C9 27 0 1 0 0 0
784+65.301 - 0 1 1 1 1
785+65.301 41.42.43 0 1 0 0 0
786+65.301 DF 0 -1 0 0 0
787+65.301 46.46.49 0 -1 0 0 0
788+65.301 73 0 -1 0 0 0
789+65.301 C5 0 -1 0 0 0
790+65.301 1C4 0 -1 0 0 0
791+65.301 65.301 1 0 1 1 1
792+65.301 4A.30C 0 -1 0 0 0
793+65.301 1C5.61 0 -1 0 0 0
794+65.301 13A0 0 -1 0 0 0
795+65.301 E000 0 -1 0 0 0
796+65.301 61.62.D83D.DE00 0 1 0 0 0
797+65.301 31.65.33 0 1 0 0 0
798+FF - 0 1 1 1 1
799+FF 48.65.6C.6C.6F 0 1 0 0 0
800+FF 53.53 0 1 0 0 0
801+FF 66.66.69 0 1 0 0 0
802+FF 53 0 1 0 0 0
803+FF 3C3 0 -1 0 0 0
804+FF 1C5 0 -1 0 0 0
805+FF FF 1 0 1 1 1
806+FF 178 1 0 1 1 1
807+FF 390 0 -1 0 0 0
808+FF 1C4.61 0 -1 0 0 0
809+FF 13F8 0 -1 0 0 0
810+FF FFFF 0 -1 0 0 0
811+FF 41.42.D83D.DE00 0 1 0 0 0
812+FF 2D 0 1 0 0 0
813+178 - 0 1 1 1 1
814+178 61 0 1 0 0 0
815+178 68.45.4C.4C.4F 0 1 0 0 0
816+178 73.73 0 1 0 0 0
817+178 131 0 1 0 0 0
818+178 212A 0 -1 0 0 0
819+178 3C2 0 -1 0 0 0
820+178 1C6 0 -1 0 0 0
821+178 FF 1 0 1 1 1
822+178 178 1 0 1 1 1
823+178 1FFC 0 -1 0 0 0
824+178 416 0 -1 0 0 0
825+178 13F0 0 -1 0 0 0
826+178 D83D.DE00 0 -1 0 0 0
827+178 78.2028.79 0 1 0 0 0
828+178 5F 0 1 0 0 0
829+B5 - 0 1 1 1 1
830+B5 41 0 1 0 0 0
831+B5 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
832+B5 1E9E 0 -1 0 0 0
833+B5 49 0 1 0 0 0
834+B5 6B 0 1 0 0 0
835+B5 3A3 0 -1 0 0 0
836+B5 1C8 0 1 0 0 0
837+B5 B5 1 0 1 1 1
838+B5 3BC 1 0 1 1 1
839+B5 39C 1 0 1 1 1
840+B5 1FB3 0 -1 0 0 0
841+B5 436 0 -1 0 0 0
842+B5 24D0 0 -1 0 0 0
843+B5 D801.DC00 0 -1 0 0 0
844+B5 78.20.79 0 1 0 0 0
845+B5 61.2D.62 0 1 0 0 0
846+3BC - 0 1 1 1 1
847+3BC 61.62 0 1 0 0 0
848+3BC 74.72.75.65 0 1 0 0 0
849+3BC 149 0 1 0 0 0
850+3BC 69 0 1 0 0 0
851+3BC 4B 0 1 0 0 0
852+3BC 391.3A3 0 1 0 0 0
853+3BC 1C9 0 1 0 0 0
854+3BC B5 1 0 1 1 1
855+3BC 3BC 1 0 1 1 1
856+3BC 39C 1 0 1 1 1
857+3BC 1FBC 0 -1 0 0 0
858+3BC 10D0 0 -1 0 0 0
859+3BC 24B6 0 -1 0 0 0
860+3BC D801.DC28 0 -1 0 0 0
861+3BC 78.A0.79 0 1 0 0 0
862+3BC 61.5F.62 0 1 0 0 0
863+39C - 0 1 1 1 1
864+39C 41.42 0 1 0 0 0
865+39C 54.52.55.45 0 1 0 0 0
866+39C 2BC.4E 0 1 0 0 0
867+39C 130 0 1 0 0 0
868+39C 212B 0 -1 0 0 0
869+39C 3B1.3C2 0 1 0 0 0
870+39C E9 0 1 0 0 0
871+39C B5 1 0 1 1 1
872+39C 3BC 1 0 1 1 1
873+39C 39C 1 0 1 1 1
874+39C E9 0 1 0 0 0
875+39C 1C90 0 -1 0 0 0
876+39C FF41 0 -1 0 0 0
877+39C D801.DC28 0 -1 0 0 0
878+39C 30 0 1 0 0 0
879+39C 5B.5D 0 1 0 0 0
880+1F0 - 0 1 1 1 1
881+1F0 61.62.63 0 1 0 0 0
882+1F0 6E.75.6C.6C 0 1 0 0 0
883+1F0 FB03 0 -1 0 0 0
884+1F0 17F 0 1 0 0 0
885+1F0 E5 0 1 0 0 0
886+1F0 3B1.3C3 0 -1 0 0 0
887+1F0 C9 0 1 0 0 0
888+1F0 1F0 1 0 1 1 1
889+1F0 C9 0 1 0 0 0
890+1F0 AB70 0 -1 0 0 0
891+1F0 FF21 0 -1 0 0 0
892+1F0 D800.DC00 0 -1 0 0 0
893+1F0 30.30 0 1 0 0 0
894+1F0 27 0 1 0 0 0
895+4A.30C - 0 1 1 1 1
896+4A.30C 41.42.43 0 1 0 0 0
897+4A.30C DF 0 -1 0 0 0
898+4A.30C 46.46.49 0 1 0 0 0
899+4A.30C 73 0 -1 0 0 0
900+4A.30C C5 0 -1 0 0 0
901+4A.30C 1C4 0 -1 0 0 0
902+4A.30C 65.301 0 1 0 0 0
903+4A.30C 4A.30C 1 0 1 1 1
904+4A.30C 1C5.61 0 -1 0 0 0
905+4A.30C 13A0 0 -1 0 0 0
906+4A.30C E000 0 -1 0 0 0
907+4A.30C 61.62.D83D.DE00 0 1 0 0 0
908+4A.30C 31.65.33 0 1 0 0 0
909+390 - 0 1 1 1 1
910+390 48.65.6C.6C.6F 0 1 0 0 0
911+390 53.53 0 1 0 0 0
912+390 66.66.69 0 1 0 0 0
913+390 53 0 1 0 0 0
914+390 3C3 0 -1 0 0 0
915+390 1C5 0 1 0 0 0
916+390 FF 0 1 0 0 0
917+390 390 1 0 1 1 1
918+390 1C4.61 0 1 0 0 0
919+390 13F8 0 -1 0 0 0
920+390 FFFF 0 -1 0 0 0
921+390 41.42.D83D.DE00 0 1 0 0 0
922+390 2D 0 1 0 0 0
923+1FFC - 0 1 1 1 1
924+1FFC 61 0 1 0 0 0
925+1FFC 68.45.4C.4C.4F 0 1 0 0 0
926+1FFC 73.73 0 1 0 0 0
927+1FFC 131 0 1 0 0 0
928+1FFC 212A 0 -1 0 0 0
929+1FFC 3C2 0 1 0 0 0
930+1FFC 1C6 0 1 0 0 0
931+1FFC 178 0 1 0 0 0
932+1FFC 1FFC 1 0 1 1 1
933+1FFC 416 0 1 0 0 0
934+1FFC 13F0 0 1 0 0 0
935+1FFC D83D.DE00 0 -1 0 0 0
936+1FFC 78.2028.79 0 1 0 0 0
937+1FFC 5F 0 1 0 0 0
938+1FB3 - 0 1 1 1 1
939+1FB3 41 0 1 0 0 0
940+1FB3 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
941+1FB3 1E9E 0 1 0 0 0
942+1FB3 49 0 1 0 0 0
943+1FB3 6B 0 1 0 0 0
944+1FB3 3A3 0 1 0 0 0
945+1FB3 1C8 0 1 0 0 0
946+1FB3 B5 0 1 0 0 0
947+1FB3 1FB3 1 0 1 1 1
948+1FB3 1FBC 1 0 1 1 1
949+1FB3 436 0 1 0 0 0
950+1FB3 24D0 0 -1 0 0 0
951+1FB3 D801.DC00 0 -1 0 0 0
952+1FB3 78.20.79 0 1 0 0 0
953+1FB3 61.2D.62 0 1 0 0 0
954+1FBC - 0 1 1 1 1
955+1FBC 61.62 0 1 0 0 0
956+1FBC 74.72.75.65 0 1 0 0 0
957+1FBC 149 0 1 0 0 0
958+1FBC 69 0 1 0 0 0
959+1FBC 4B 0 1 0 0 0
960+1FBC 391.3A3 0 1 0 0 0
961+1FBC 1C9 0 1 0 0 0
962+1FBC 3BC 0 1 0 0 0
963+1FBC 1FB3 1 0 1 1 1
964+1FBC 1FBC 1 0 1 1 1
965+1FBC 10D0 0 1 0 0 0
966+1FBC 24B6 0 -1 0 0 0
967+1FBC D801.DC28 0 -1 0 0 0
968+1FBC 78.A0.79 0 1 0 0 0
969+1FBC 61.5F.62 0 1 0 0 0
970+E9 - 0 1 1 1 1
971+E9 41.42 0 1 0 0 0
972+E9 54.52.55.45 0 1 0 0 0
973+E9 2BC.4E 0 -1 0 0 0
974+E9 130 0 -1 0 0 0
975+E9 212B 0 -1 0 0 0
976+E9 3B1.3C2 0 -1 0 0 0
977+E9 E9 1 0 1 1 1
978+E9 C9 1 0 1 1 1
979+E9 39C 0 -1 0 0 0
980+E9 E9 1 0 1 1 1
981+E9 C9 1 0 1 1 1
982+E9 1C90 0 -1 0 0 0
983+E9 FF41 0 -1 0 0 0
984+E9 D801.DC28 0 -1 0 0 0
985+E9 30 0 1 0 0 0
986+E9 5B.5D 0 1 0 0 0
987+C9 - 0 1 1 1 1
988+C9 61.62.63 0 1 0 0 0
989+C9 6E.75.6C.6C 0 1 0 0 0
990+C9 FB03 0 -1 0 0 0
991+C9 17F 0 -1 0 0 0
992+C9 E5 0 1 0 0 0
993+C9 3B1.3C3 0 -1 0 0 0
994+C9 E9 1 0 1 1 1
995+C9 C9 1 0 1 1 1
996+C9 1F0 0 -1 0 0 0
997+C9 E9 1 0 1 1 1
998+C9 C9 1 0 1 1 1
999+C9 AB70 0 -1 0 0 0
1000+C9 FF21 0 -1 0 0 0
1001+C9 D800.DC00 0 -1 0 0 0
1002+C9 30.30 0 1 0 0 0
1003+C9 27 0 1 0 0 0
1004+1C5.61 - 0 1 1 1 1
1005+1C5.61 61 0 1 1 0 1
1006+1C5.61 41 0 1 1 0 1
1007+1C5.61 41.42.43 0 1 0 0 0
1008+1C5.61 DF 0 1 0 0 0
1009+1C5.61 46.46.49 0 1 0 0 0
1010+1C5.61 73 0 1 0 0 0
1011+1C5.61 C5 0 1 0 0 0
1012+1C5.61 1C4 0 1 1 1 0
1013+1C5.61 1C5 0 1 1 1 0
1014+1C5.61 1C6 0 1 1 1 0
1015+1C5.61 65.301 0 1 0 0 0
1016+1C5.61 4A.30C 0 1 0 0 0
1017+1C5.61 1C5.61 1 0 1 1 1
1018+1C5.61 1C4.61 1 0 1 1 1
1019+1C5.61 13A0 0 -1 0 0 0
1020+1C5.61 E000 0 -1 0 0 0
1021+1C5.61 61.62.D83D.DE00 0 1 0 0 0
1022+1C5.61 31.65.33 0 1 0 0 0
1023+1C4.61 - 0 1 1 1 1
1024+1C4.61 61 0 1 1 0 1
1025+1C4.61 41 0 1 1 0 1
1026+1C4.61 48.65.6C.6C.6F 0 1 0 0 0
1027+1C4.61 53.53 0 1 0 0 0
1028+1C4.61 66.66.69 0 1 0 0 0
1029+1C4.61 53 0 1 0 0 0
1030+1C4.61 3C3 0 -1 0 0 0
1031+1C4.61 1C4 0 1 1 1 0
1032+1C4.61 1C5 0 1 1 1 0
1033+1C4.61 1C6 0 1 1 1 0
1034+1C4.61 FF 0 1 0 0 0
1035+1C4.61 390 0 -1 0 0 0
1036+1C4.61 1C5.61 1 0 1 1 1
1037+1C4.61 1C4.61 1 0 1 1 1
1038+1C4.61 13F8 0 -1 0 0 0
1039+1C4.61 FFFF 0 -1 0 0 0
1040+1C4.61 41.42.D83D.DE00 0 1 0 0 0
1041+1C4.61 2D 0 1 0 0 0
1042+416 - 0 1 1 1 1
1043+416 61 0 1 0 0 0
1044+416 68.45.4C.4C.4F 0 1 0 0 0
1045+416 73.73 0 1 0 0 0
1046+416 131 0 1 0 0 0
1047+416 212A 0 -1 0 0 0
1048+416 3C2 0 1 0 0 0
1049+416 1C6 0 1 0 0 0
1050+416 178 0 1 0 0 0
1051+416 1FFC 0 -1 0 0 0
1052+416 416 1 0 1 1 1
1053+416 436 1 0 1 1 1
1054+416 13F0 0 -1 0 0 0
1055+416 D83D.DE00 0 -1 0 0 0
1056+416 78.2028.79 0 1 0 0 0
1057+416 5F 0 1 0 0 0
1058+436 - 0 1 1 1 1
1059+436 41 0 1 0 0 0
1060+436 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
1061+436 1E9E 0 -1 0 0 0
1062+436 49 0 1 0 0 0
1063+436 6B 0 1 0 0 0
1064+436 3A3 0 1 0 0 0
1065+436 1C8 0 1 0 0 0
1066+436 B5 0 1 0 0 0
1067+436 1FB3 0 -1 0 0 0
1068+436 416 1 0 1 1 1
1069+436 436 1 0 1 1 1
1070+436 24D0 0 -1 0 0 0
1071+436 D801.DC00 0 -1 0 0 0
1072+436 78.20.79 0 1 0 0 0
1073+436 61.2D.62 0 1 0 0 0
1074+10D0 - 0 1 1 1 1
1075+10D0 61.62 0 1 0 0 0
1076+10D0 74.72.75.65 0 1 0 0 0
1077+10D0 149 0 1 0 0 0
1078+10D0 69 0 1 0 0 0
1079+10D0 4B 0 1 0 0 0
1080+10D0 391.3A3 0 1 0 0 0
1081+10D0 1C9 0 1 0 0 0
1082+10D0 3BC 0 1 0 0 0
1083+10D0 1FBC 0 -1 0 0 0
1084+10D0 10D0 1 0 1 1 1
1085+10D0 1C90 1 0 1 1 1
1086+10D0 24B6 0 -1 0 0 0
1087+10D0 D801.DC28 0 -1 0 0 0
1088+10D0 78.A0.79 0 1 0 0 0
1089+10D0 61.5F.62 0 1 0 0 0
1090+1C90 - 0 1 1 1 1
1091+1C90 41.42 0 1 0 0 0
1092+1C90 54.52.55.45 0 1 0 0 0
1093+1C90 2BC.4E 0 1 0 0 0
1094+1C90 130 0 1 0 0 0
1095+1C90 212B 0 -1 0 0 0
1096+1C90 3B1.3C2 0 1 0 0 0
1097+1C90 E9 0 1 0 0 0
1098+1C90 39C 0 1 0 0 0
1099+1C90 E9 0 1 0 0 0
1100+1C90 10D0 1 0 1 1 1
1101+1C90 1C90 1 0 1 1 1
1102+1C90 FF41 0 -1 0 0 0
1103+1C90 D801.DC28 0 -1 0 0 0
1104+1C90 30 0 1 0 0 0
1105+1C90 5B.5D 0 1 0 0 0
1106+AB70 - 0 1 1 1 1
1107+AB70 61.62.63 0 1 0 0 0
1108+AB70 6E.75.6C.6C 0 1 0 0 0
1109+AB70 FB03 0 -1 0 0 0
1110+AB70 17F 0 1 0 0 0
1111+AB70 E5 0 1 0 0 0
1112+AB70 3B1.3C3 0 1 0 0 0
1113+AB70 C9 0 1 0 0 0
1114+AB70 1F0 0 1 0 0 0
1115+AB70 C9 0 1 0 0 0
1116+AB70 AB70 1 0 1 1 1
1117+AB70 13A0 1 0 1 1 1
1118+AB70 FF21 0 -1 0 0 0
1119+AB70 D800.DC00 0 -1 0 0 0
1120+AB70 30.30 0 1 0 0 0
1121+AB70 27 0 1 0 0 0
1122+13A0 - 0 1 1 1 1
1123+13A0 41.42.43 0 1 0 0 0
1124+13A0 DF 0 1 0 0 0
1125+13A0 46.46.49 0 1 0 0 0
1126+13A0 73 0 1 0 0 0
1127+13A0 C5 0 1 0 0 0
1128+13A0 1C4 0 1 0 0 0
1129+13A0 65.301 0 1 0 0 0
1130+13A0 4A.30C 0 1 0 0 0
1131+13A0 1C5.61 0 1 0 0 0
1132+13A0 AB70 1 0 1 1 1
1133+13A0 13A0 1 0 1 1 1
1134+13A0 E000 0 -1 0 0 0
1135+13A0 61.62.D83D.DE00 0 1 0 0 0
1136+13A0 31.65.33 0 1 0 0 0
1137+13F8 - 0 1 1 1 1
1138+13F8 48.65.6C.6C.6F 0 1 0 0 0
1139+13F8 53.53 0 1 0 0 0
1140+13F8 66.66.69 0 1 0 0 0
1141+13F8 53 0 1 0 0 0
1142+13F8 3C3 0 1 0 0 0
1143+13F8 1C5 0 1 0 0 0
1144+13F8 FF 0 1 0 0 0
1145+13F8 390 0 1 0 0 0
1146+13F8 1C4.61 0 1 0 0 0
1147+13F8 13F8 1 0 1 1 1
1148+13F8 13F0 1 0 1 1 1
1149+13F8 FFFF 0 -1 0 0 0
1150+13F8 41.42.D83D.DE00 0 1 0 0 0
1151+13F8 2D 0 1 0 0 0
1152+13F0 - 0 1 1 1 1
1153+13F0 61 0 1 0 0 0
1154+13F0 68.45.4C.4C.4F 0 1 0 0 0
1155+13F0 73.73 0 1 0 0 0
1156+13F0 131 0 1 0 0 0
1157+13F0 212A 0 -1 0 0 0
1158+13F0 3C2 0 1 0 0 0
1159+13F0 1C6 0 1 0 0 0
1160+13F0 178 0 1 0 0 0
1161+13F0 1FFC 0 -1 0 0 0
1162+13F0 416 0 1 0 0 0
1163+13F0 13F8 1 0 1 1 1
1164+13F0 13F0 1 0 1 1 1
1165+13F0 D83D.DE00 0 -1 0 0 0
1166+13F0 78.2028.79 0 1 0 0 0
1167+13F0 5F 0 1 0 0 0
1168+24D0 - 0 1 1 1 1
1169+24D0 41 0 1 0 0 0
1170+24D0 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
1171+24D0 1E9E 0 1 0 0 0
1172+24D0 49 0 1 0 0 0
1173+24D0 6B 0 1 0 0 0
1174+24D0 3A3 0 1 0 0 0
1175+24D0 1C8 0 1 0 0 0
1176+24D0 B5 0 1 0 0 0
1177+24D0 1FB3 0 1 0 0 0
1178+24D0 436 0 1 0 0 0
1179+24D0 24D0 1 0 1 1 1
1180+24D0 24B6 1 0 1 1 1
1181+24D0 D801.DC00 0 -1 0 0 0
1182+24D0 78.20.79 0 1 0 0 0
1183+24D0 61.2D.62 0 1 0 0 0
1184+24B6 - 0 1 1 1 1
1185+24B6 61.62 0 1 0 0 0
1186+24B6 74.72.75.65 0 1 0 0 0
1187+24B6 149 0 1 0 0 0
1188+24B6 69 0 1 0 0 0
1189+24B6 4B 0 1 0 0 0
1190+24B6 391.3A3 0 1 0 0 0
1191+24B6 1C9 0 1 0 0 0
1192+24B6 3BC 0 1 0 0 0
1193+24B6 1FBC 0 1 0 0 0
1194+24B6 10D0 0 1 0 0 0
1195+24B6 24D0 1 0 1 1 1
1196+24B6 24B6 1 0 1 1 1
1197+24B6 D801.DC28 0 -1 0 0 0
1198+24B6 78.A0.79 0 1 0 0 0
1199+24B6 61.5F.62 0 1 0 0 0
1200+FF41 - 0 1 1 1 1
1201+FF41 41.42 0 1 0 0 0
1202+FF41 54.52.55.45 0 1 0 0 0
1203+FF41 2BC.4E 0 1 0 0 0
1204+FF41 130 0 1 0 0 0
1205+FF41 212B 0 1 0 0 0
1206+FF41 3B1.3C2 0 1 0 0 0
1207+FF41 E9 0 1 0 0 0
1208+FF41 39C 0 1 0 0 0
1209+FF41 E9 0 1 0 0 0
1210+FF41 1C90 0 1 0 0 0
1211+FF41 FF41 1 0 1 1 1
1212+FF41 FF21 1 0 1 1 1
1213+FF41 D801.DC28 0 -1 0 0 0
1214+FF41 30 0 1 0 0 0
1215+FF41 5B.5D 0 1 0 0 0
1216+FF21 - 0 1 1 1 1
1217+FF21 61.62.63 0 1 0 0 0
1218+FF21 6E.75.6C.6C 0 1 0 0 0
1219+FF21 FB03 0 1 0 0 0
1220+FF21 17F 0 1 0 0 0
1221+FF21 E5 0 1 0 0 0
1222+FF21 3B1.3C3 0 1 0 0 0
1223+FF21 C9 0 1 0 0 0
1224+FF21 1F0 0 1 0 0 0
1225+FF21 C9 0 1 0 0 0
1226+FF21 AB70 0 1 0 0 0
1227+FF21 FF41 1 0 1 1 1
1228+FF21 FF21 1 0 1 1 1
1229+FF21 D800.DC00 0 -1 0 0 0
1230+FF21 30.30 0 1 0 0 0
1231+FF21 27 0 1 0 0 0
1232+E000 - 0 1 1 1 1
1233+E000 41.42.43 0 1 0 0 0
1234+E000 DF 0 1 0 0 0
1235+E000 46.46.49 0 1 0 0 0
1236+E000 73 0 1 0 0 0
1237+E000 C5 0 1 0 0 0
1238+E000 1C4 0 1 0 0 0
1239+E000 65.301 0 1 0 0 0
1240+E000 4A.30C 0 1 0 0 0
1241+E000 1C5.61 0 1 0 0 0
1242+E000 13A0 0 1 0 0 0
1243+E000 E000 1 0 1 1 1
1244+E000 61.62.D83D.DE00 0 1 0 0 0
1245+E000 31.65.33 0 1 0 0 0
1246+FFFF - 0 1 1 1 1
1247+FFFF 48.65.6C.6C.6F 0 1 0 0 0
1248+FFFF 53.53 0 1 0 0 0
1249+FFFF 66.66.69 0 1 0 0 0
1250+FFFF 53 0 1 0 0 0
1251+FFFF 3C3 0 1 0 0 0
1252+FFFF 1C5 0 1 0 0 0
1253+FFFF FF 0 1 0 0 0
1254+FFFF 390 0 1 0 0 0
1255+FFFF 1C4.61 0 1 0 0 0
1256+FFFF 13F8 0 1 0 0 0
1257+FFFF FFFF 1 0 1 1 1
1258+FFFF 41.42.D83D.DE00 0 1 0 0 0
1259+FFFF 2D 0 1 0 0 0
1260+D83D.DE00 - 0 1 1 1 1
1261+D83D.DE00 61 0 1 0 0 0
1262+D83D.DE00 68.45.4C.4C.4F 0 1 0 0 0
1263+D83D.DE00 73.73 0 1 0 0 0
1264+D83D.DE00 131 0 1 0 0 0
1265+D83D.DE00 212A 0 1 0 0 0
1266+D83D.DE00 3C2 0 1 0 0 0
1267+D83D.DE00 1C6 0 1 0 0 0
1268+D83D.DE00 178 0 1 0 0 0
1269+D83D.DE00 1FFC 0 1 0 0 0
1270+D83D.DE00 416 0 1 0 0 0
1271+D83D.DE00 13F0 0 1 0 0 0
1272+D83D.DE00 D83D.DE00 1 0 1 1 1
1273+D83D.DE00 78.2028.79 0 1 0 0 0
1274+D83D.DE00 5F 0 1 0 0 0
1275+D801.DC00 - 0 1 1 1 1
1276+D801.DC00 41 0 1 0 0 0
1277+D801.DC00 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
1278+D801.DC00 1E9E 0 1 0 0 0
1279+D801.DC00 49 0 1 0 0 0
1280+D801.DC00 6B 0 1 0 0 0
1281+D801.DC00 3A3 0 1 0 0 0
1282+D801.DC00 1C8 0 1 0 0 0
1283+D801.DC00 B5 0 1 0 0 0
1284+D801.DC00 1FB3 0 1 0 0 0
1285+D801.DC00 436 0 1 0 0 0
1286+D801.DC00 24D0 0 1 0 0 0
1287+D801.DC00 D801.DC00 1 0 1 1 1
1288+D801.DC00 D801.DC28 1 0 1 1 1
1289+D801.DC00 D801.DC28 1 0 1 1 1
1290+D801.DC00 78.20.79 0 1 0 0 0
1291+D801.DC00 61.2D.62 0 1 0 0 0
1292+D801.DC28 - 0 1 1 1 1
1293+D801.DC28 61.62 0 1 0 0 0
1294+D801.DC28 74.72.75.65 0 1 0 0 0
1295+D801.DC28 149 0 1 0 0 0
1296+D801.DC28 69 0 1 0 0 0
1297+D801.DC28 4B 0 1 0 0 0
1298+D801.DC28 391.3A3 0 1 0 0 0
1299+D801.DC28 1C9 0 1 0 0 0
1300+D801.DC28 3BC 0 1 0 0 0
1301+D801.DC28 1FBC 0 1 0 0 0
1302+D801.DC28 10D0 0 1 0 0 0
1303+D801.DC28 24B6 0 1 0 0 0
1304+D801.DC28 D801.DC00 1 0 1 1 1
1305+D801.DC28 D801.DC28 1 0 1 1 1
1306+D801.DC28 D801.DC28 1 0 1 1 1
1307+D801.DC28 78.A0.79 0 1 0 0 0
1308+D801.DC28 61.5F.62 0 1 0 0 0
1309+D801.DC28 - 0 1 1 1 1
1310+D801.DC28 41.42 0 1 0 0 0
1311+D801.DC28 54.52.55.45 0 1 0 0 0
1312+D801.DC28 2BC.4E 0 1 0 0 0
1313+D801.DC28 130 0 1 0 0 0
1314+D801.DC28 212B 0 1 0 0 0
1315+D801.DC28 3B1.3C2 0 1 0 0 0
1316+D801.DC28 E9 0 1 0 0 0
1317+D801.DC28 39C 0 1 0 0 0
1318+D801.DC28 E9 0 1 0 0 0
1319+D801.DC28 1C90 0 1 0 0 0
1320+D801.DC28 FF41 0 1 0 0 0
1321+D801.DC28 D801.DC00 1 0 1 1 1
1322+D801.DC28 D801.DC28 1 0 1 1 1
1323+D801.DC28 D801.DC28 1 0 1 1 1
1324+D801.DC28 30 0 1 0 0 0
1325+D801.DC28 5B.5D 0 1 0 0 0
1326+D800.DC00 - 0 1 1 1 1
1327+D800.DC00 61.62.63 0 1 0 0 0
1328+D800.DC00 6E.75.6C.6C 0 1 0 0 0
1329+D800.DC00 FB03 0 1 0 0 0
1330+D800.DC00 17F 0 1 0 0 0
1331+D800.DC00 E5 0 1 0 0 0
1332+D800.DC00 3B1.3C3 0 1 0 0 0
1333+D800.DC00 C9 0 1 0 0 0
1334+D800.DC00 1F0 0 1 0 0 0
1335+D800.DC00 C9 0 1 0 0 0
1336+D800.DC00 AB70 0 1 0 0 0
1337+D800.DC00 FF21 0 1 0 0 0
1338+D800.DC00 D800.DC00 1 0 1 1 1
1339+D800.DC00 30.30 0 1 0 0 0
1340+D800.DC00 27 0 1 0 0 0
1341+61.62.D83D.DE00 - 0 1 1 1 1
1342+61.62.D83D.DE00 61 0 1 1 1 0
1343+61.62.D83D.DE00 41 0 1 1 1 0
1344+61.62.D83D.DE00 61.62 0 1 1 1 0
1345+61.62.D83D.DE00 41.42 0 1 1 1 0
1346+61.62.D83D.DE00 41.42.43 0 1 0 0 0
1347+61.62.D83D.DE00 DF 0 -1 0 0 0
1348+61.62.D83D.DE00 46.46.49 0 -1 0 0 0
1349+61.62.D83D.DE00 73 0 -1 0 0 0
1350+61.62.D83D.DE00 C5 0 -1 0 0 0
1351+61.62.D83D.DE00 1C4 0 -1 0 0 0
1352+61.62.D83D.DE00 65.301 0 -1 0 0 0
1353+61.62.D83D.DE00 4A.30C 0 -1 0 0 0
1354+61.62.D83D.DE00 1C5.61 0 -1 0 0 0
1355+61.62.D83D.DE00 13A0 0 -1 0 0 0
1356+61.62.D83D.DE00 E000 0 -1 0 0 0
1357+61.62.D83D.DE00 D83D.DE00 0 -1 1 0 1
1358+61.62.D83D.DE00 61.62.D83D.DE00 1 0 1 1 1
1359+61.62.D83D.DE00 41.42.D83D.DE00 1 0 1 1 1
1360+61.62.D83D.DE00 31.65.33 0 1 0 0 0
1361+41.42.D83D.DE00 - 0 1 1 1 1
1362+41.42.D83D.DE00 61 0 1 1 1 0
1363+41.42.D83D.DE00 41 0 1 1 1 0
1364+41.42.D83D.DE00 61.62 0 1 1 1 0
1365+41.42.D83D.DE00 41.42 0 1 1 1 0
1366+41.42.D83D.DE00 48.65.6C.6C.6F 0 -1 0 0 0
1367+41.42.D83D.DE00 53.53 0 -1 0 0 0
1368+41.42.D83D.DE00 66.66.69 0 -1 0 0 0
1369+41.42.D83D.DE00 53 0 -1 0 0 0
1370+41.42.D83D.DE00 3C3 0 -1 0 0 0
1371+41.42.D83D.DE00 1C5 0 -1 0 0 0
1372+41.42.D83D.DE00 FF 0 -1 0 0 0
1373+41.42.D83D.DE00 390 0 -1 0 0 0
1374+41.42.D83D.DE00 1C4.61 0 -1 0 0 0
1375+41.42.D83D.DE00 13F8 0 -1 0 0 0
1376+41.42.D83D.DE00 FFFF 0 -1 0 0 0
1377+41.42.D83D.DE00 D83D.DE00 0 -1 1 0 1
1378+41.42.D83D.DE00 61.62.D83D.DE00 1 0 1 1 1
1379+41.42.D83D.DE00 41.42.D83D.DE00 1 0 1 1 1
1380+41.42.D83D.DE00 2D 0 1 0 0 0
1381+78.2028.79 - 0 1 1 1 1
1382+78.2028.79 61 0 1 0 0 0
1383+78.2028.79 68.45.4C.4C.4F 0 1 0 0 0
1384+78.2028.79 73.73 0 1 0 0 0
1385+78.2028.79 131 0 -1 0 0 0
1386+78.2028.79 212A 0 -1 0 0 0
1387+78.2028.79 3C2 0 -1 0 0 0
1388+78.2028.79 1C6 0 -1 0 0 0
1389+78.2028.79 178 0 -1 0 0 0
1390+78.2028.79 1FFC 0 -1 0 0 0
1391+78.2028.79 416 0 -1 0 0 0
1392+78.2028.79 13F0 0 -1 0 0 0
1393+78.2028.79 D83D.DE00 0 -1 0 0 0
1394+78.2028.79 78.2028.79 1 0 1 1 1
1395+78.2028.79 5F 0 -1 0 0 0
1396+78.20.79 - 0 1 1 1 1
1397+78.20.79 41 0 1 0 0 0
1398+78.20.79 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 1 0 0 0
1399+78.20.79 1E9E 0 -1 0 0 0
1400+78.20.79 49 0 1 0 0 0
1401+78.20.79 6B 0 1 0 0 0
1402+78.20.79 3A3 0 -1 0 0 0
1403+78.20.79 1C8 0 -1 0 0 0
1404+78.20.79 B5 0 -1 0 0 0
1405+78.20.79 1FB3 0 -1 0 0 0
1406+78.20.79 436 0 -1 0 0 0
1407+78.20.79 24D0 0 -1 0 0 0
1408+78.20.79 D801.DC00 0 -1 0 0 0
1409+78.20.79 78.20.79 1 0 1 1 1
1410+78.20.79 61.2D.62 0 1 0 0 0
1411+78.A0.79 - 0 1 1 1 1
1412+78.A0.79 61.62 0 1 0 0 0
1413+78.A0.79 74.72.75.65 0 1 0 0 0
1414+78.A0.79 149 0 -1 0 0 0
1415+78.A0.79 69 0 1 0 0 0
1416+78.A0.79 4B 0 1 0 0 0
1417+78.A0.79 391.3A3 0 -1 0 0 0
1418+78.A0.79 1C9 0 -1 0 0 0
1419+78.A0.79 3BC 0 -1 0 0 0
1420+78.A0.79 1FBC 0 -1 0 0 0
1421+78.A0.79 10D0 0 -1 0 0 0
1422+78.A0.79 24B6 0 -1 0 0 0
1423+78.A0.79 D801.DC28 0 -1 0 0 0
1424+78.A0.79 78.A0.79 1 0 1 1 1
1425+78.A0.79 61.5F.62 0 1 0 0 0
1426+30 - 0 1 1 1 1
1427+30 41.42 0 -1 0 0 0
1428+30 54.52.55.45 0 -1 0 0 0
1429+30 2BC.4E 0 -1 0 0 0
1430+30 130 0 -1 0 0 0
1431+30 212B 0 -1 0 0 0
1432+30 3B1.3C2 0 -1 0 0 0
1433+30 E9 0 -1 0 0 0
1434+30 39C 0 -1 0 0 0
1435+30 E9 0 -1 0 0 0
1436+30 1C90 0 -1 0 0 0
1437+30 FF41 0 -1 0 0 0
1438+30 D801.DC28 0 -1 0 0 0
1439+30 30 1 0 1 1 1
1440+30 5B.5D 0 -1 0 0 0
1441+30.30 - 0 1 1 1 1
1442+30.30 61.62.63 0 -1 0 0 0
1443+30.30 6E.75.6C.6C 0 -1 0 0 0
1444+30.30 FB03 0 -1 0 0 0
1445+30.30 17F 0 -1 0 0 0
1446+30.30 E5 0 -1 0 0 0
1447+30.30 3B1.3C3 0 -1 0 0 0
1448+30.30 C9 0 -1 0 0 0
1449+30.30 1F0 0 -1 0 0 0
1450+30.30 C9 0 -1 0 0 0
1451+30.30 AB70 0 -1 0 0 0
1452+30.30 FF21 0 -1 0 0 0
1453+30.30 D800.DC00 0 -1 0 0 0
1454+30.30 30 0 1 1 1 1
1455+30.30 30.30 1 0 1 1 1
1456+30.30 27 0 1 0 0 0
1457+31.65.33 - 0 1 1 1 1
1458+31.65.33 41.42.43 0 -1 0 0 0
1459+31.65.33 DF 0 -1 0 0 0
1460+31.65.33 46.46.49 0 -1 0 0 0
1461+31.65.33 73 0 -1 0 0 0
1462+31.65.33 C5 0 -1 0 0 0
1463+31.65.33 1C4 0 -1 0 0 0
1464+31.65.33 65.301 0 -1 0 0 0
1465+31.65.33 4A.30C 0 -1 0 0 0
1466+31.65.33 1C5.61 0 -1 0 0 0
1467+31.65.33 13A0 0 -1 0 0 0
1468+31.65.33 E000 0 -1 0 0 0
1469+31.65.33 61.62.D83D.DE00 0 -1 0 0 0
1470+31.65.33 31.65.33 1 0 1 1 1
1471+2D - 0 1 1 1 1
1472+2D 48.65.6C.6C.6F 0 -1 0 0 0
1473+2D 53.53 0 -1 0 0 0
1474+2D 66.66.69 0 -1 0 0 0
1475+2D 53 0 -1 0 0 0
1476+2D 3C3 0 -1 0 0 0
1477+2D 1C5 0 -1 0 0 0
1478+2D FF 0 -1 0 0 0
1479+2D 390 0 -1 0 0 0
1480+2D 1C4.61 0 -1 0 0 0
1481+2D 13F8 0 -1 0 0 0
1482+2D FFFF 0 -1 0 0 0
1483+2D 41.42.D83D.DE00 0 -1 0 0 0
1484+2D 2D 1 0 1 1 1
1485+5F - 0 1 1 1 1
1486+5F 61 0 1 0 0 0
1487+5F 68.45.4C.4C.4F 0 1 0 0 0
1488+5F 73.73 0 1 0 0 0
1489+5F 131 0 -1 0 0 0
1490+5F 212A 0 -1 0 0 0
1491+5F 3C2 0 -1 0 0 0
1492+5F 1C6 0 -1 0 0 0
1493+5F 178 0 -1 0 0 0
1494+5F 1FFC 0 -1 0 0 0
1495+5F 416 0 -1 0 0 0
1496+5F 13F0 0 -1 0 0 0
1497+5F D83D.DE00 0 -1 0 0 0
1498+5F 78.2028.79 0 1 0 0 0
1499+5F 5F 1 0 1 1 1
1500+61.2D.62 - 0 1 1 1 1
1501+61.2D.62 61 0 1 1 1 0
1502+61.2D.62 41 0 1 1 1 0
1503+61.2D.62 68.65.6C.6C.6F.20.77.6F.72.6C.64 0 -1 0 0 0
1504+61.2D.62 1E9E 0 -1 0 0 0
1505+61.2D.62 49 0 -1 0 0 0
1506+61.2D.62 6B 0 -1 0 0 0
1507+61.2D.62 3A3 0 -1 0 0 0
1508+61.2D.62 1C8 0 -1 0 0 0
1509+61.2D.62 B5 0 -1 0 0 0
1510+61.2D.62 1FB3 0 -1 0 0 0
1511+61.2D.62 436 0 -1 0 0 0
1512+61.2D.62 24D0 0 -1 0 0 0
1513+61.2D.62 D801.DC00 0 -1 0 0 0
1514+61.2D.62 78.20.79 0 -1 0 0 0
1515+61.2D.62 2D 0 1 1 0 0
1516+61.2D.62 61.2D.62 1 0 1 1 1
1517+61.5F.62 - 0 1 1 1 1
1518+61.5F.62 61 0 1 1 1 0
1519+61.5F.62 41 0 1 1 1 0
1520+61.5F.62 61.62 0 1 0 0 0
1521+61.5F.62 74.72.75.65 0 -1 0 0 0
1522+61.5F.62 149 0 -1 0 0 0
1523+61.5F.62 69 0 -1 0 0 0
1524+61.5F.62 4B 0 -1 0 0 0
1525+61.5F.62 391.3A3 0 -1 0 0 0
1526+61.5F.62 1C9 0 -1 0 0 0
1527+61.5F.62 3BC 0 -1 0 0 0
1528+61.5F.62 1FBC 0 -1 0 0 0
1529+61.5F.62 10D0 0 -1 0 0 0
1530+61.5F.62 24B6 0 -1 0 0 0
1531+61.5F.62 D801.DC28 0 -1 0 0 0
1532+61.5F.62 78.A0.79 0 -1 0 0 0
1533+61.5F.62 5F 0 -1 1 0 0
1534+61.5F.62 61.5F.62 1 0 1 1 1
1535+5B.5D - 0 1 1 1 1
1536+5B.5D 41.42 0 1 0 0 0
1537+5B.5D 54.52.55.45 0 1 0 0 0
1538+5B.5D 2BC.4E 0 -1 0 0 0
1539+5B.5D 130 0 -1 0 0 0
1540+5B.5D 212B 0 -1 0 0 0
1541+5B.5D 3B1.3C2 0 -1 0 0 0
1542+5B.5D E9 0 -1 0 0 0
1543+5B.5D 39C 0 -1 0 0 0
1544+5B.5D E9 0 -1 0 0 0
1545+5B.5D 1C90 0 -1 0 0 0
1546+5B.5D FF41 0 -1 0 0 0
1547+5B.5D D801.DC28 0 -1 0 0 0
1548+5B.5D 30 0 1 0 0 0
1549+5B.5D 5B.5D 1 0 1 1 1
1550+27 - 0 1 1 1 1
1551+27 61.62.63 0 -1 0 0 0
1552+27 6E.75.6C.6C 0 -1 0 0 0
1553+27 FB03 0 -1 0 0 0
1554+27 17F 0 -1 0 0 0
1555+27 E5 0 -1 0 0 0
1556+27 3B1.3C3 0 -1 0 0 0
1557+27 C9 0 -1 0 0 0
1558+27 1F0 0 -1 0 0 0
1559+27 C9 0 -1 0 0 0
1560+27 AB70 0 -1 0 0 0
1561+27 FF21 0 -1 0 0 0
1562+27 D800.DC00 0 -1 0 0 0
1563+27 30.30 0 -1 0 0 0
1564+27 27 1 0 1 1 1
A expr/testdata/runner.json.gz +0 -0 binary file: no text diff

binary file: no text diff

A expr/testdata/tojson/big_int.runner +1 -0
@@ -0,0 +1,1 @@
1+1.23456789012346E+19⏎̸
A expr/testdata/tojson/exp_20.runner +1 -0
@@ -0,0 +1,1 @@
1+1E+20⏎̸
A expr/testdata/tojson/exp_large.runner +1 -0
@@ -0,0 +1,1 @@
1+1E+21⏎̸
A expr/testdata/tojson/exp_small.runner +1 -0
@@ -0,0 +1,1 @@
1+1E-07⏎̸
A expr/testdata/tojson/max_double.runner +1 -0
@@ -0,0 +1,1 @@
1+1.79769313486232E+308⏎̸
A expr/testdata/tojson/min_double.runner +1 -0
@@ -0,0 +1,1 @@
1+4.94065645841247E-324⏎̸
A expr/testdata/tojson/object_case_dup.runner +4 -0
@@ -0,0 +1,4 @@
1+{
2+ "a": 2,
3+ "b": 4
4+}⏎̸
A expr/testdata/tojson/object_numeric_keys.runner +11 -0
@@ -0,0 +1,11 @@
1+{
2+ "b": 1,
3+ "10": 2,
4+ "2": 3,
5+ "a": 4,
6+ "01": 5,
7+ "1": 6,
8+ "4294967294": 7,
9+ "4294967295": 8,
10+ "-1": 9
11+}⏎̸
A expr/testdata/tojson/overflow.runner +1 -0
@@ -0,0 +1,1 @@
1+Infinity⏎̸
A expr/testdata/tojson/precision.runner +1 -0
@@ -0,0 +1,1 @@
1+0.3⏎̸
A expr/testdata/tojson/string_lone_surrogate.runner +1 -0
@@ -0,0 +1,1 @@
1+"�"⏎̸
A expr/testdata/tojson/string_unicode.runner +1 -0
@@ -0,0 +1,1 @@
1+"héllo 世界 😀 \u2028\u2029"⏎̸
M expr/testutil_test.go +5 -1
@@ -4,6 +4,7 @@ package expr
4 4
5 import (5 import (
6 "context"6 "context"
7+ "strings"
7 "testing"8 "testing"
8 )9 )
9 10
@@ -52,7 +53,10 @@ func evalString(tb testing.TB, src string) string {
52 tb.Helper()53 tb.Helper()
53 node, err := Parse(src, testContextNames, nil)54 node, err := Parse(src, testContextNames, nil)
54 if err != nil {55 if err != nil {
55- return "PARSE " + err.Error()56+ // The runner appends ". Located at position N within expression: ..."
57+ // to errors that name a token; TestRunnerErrorPositions covers that.
58+ msg, _, _ := strings.Cut(err.Error(), ". Located at position ")
59+ return "PARSE " + msg
56 }60 }
57 v, err := Evaluate(context.Background(), node, EvalContext{Contexts: testContexts(tb)})61 v, err := Evaluate(context.Background(), node, EvalContext{Contexts: testContexts(tb)})
58 if err != nil {62 if err != nil {
A expr/xlang_test.go +283 -0
@@ -0,0 +1,283 @@
1+// SPDX-License-Identifier: MIT
2+
3+package expr
4+
5+import (
6+ "context"
7+ "encoding/json"
8+ "errors"
9+ "fmt"
10+ "math"
11+ "os"
12+ "path/filepath"
13+ "sort"
14+ "strconv"
15+ "strings"
16+ "testing"
17+)
18+
19+// The cross-language fixtures of actions/languageservices
20+// (testdata/languageservices, see SOURCE there) are shared by the TypeScript
21+// and the C# implementations. This runner reproduces
22+// expressions/src/xlang.test.ts of that repository:
23+//
24+// - contexts are revived into expression data, and their names are the
25+// context names given to the parser; no extension functions are declared;
26+// - a lexing error is compared with the error message of the lexer;
27+// - a parsing error with "<message>. Located at position <column+1> within
28+// expression: <expr>";
29+// - an evaluation error with "<message>. Located within expression: <expr>";
30+// - error expectations match by substring, result kinds by name, result
31+// values by their JSON.stringify form (NaN and Infinity are null).
32+//
33+// The only deliberate difference from the TypeScript runner is that cases
34+// marked options.skip = ["typescript"] are run too: they are skipped there
35+// because the TypeScript port lacks a feature, not because the fixture is
36+// wrong. Fixtures that this package cannot satisfy are listed in
37+// xlangKnownDifferences with the reason.
38+
39+type xlangResult struct {
40+ Kind string `json:"kind"`
41+ Value json.RawMessage `json:"value"`
42+}
43+
44+type xlangErr struct {
45+ Kind string `json:"kind"`
46+ Value string `json:"value"`
47+}
48+
49+type xlangCase struct {
50+ Expr string `json:"expr"`
51+ Result *xlangResult `json:"result"`
52+ Err *xlangErr `json:"err"`
53+ Contexts json.RawMessage `json:"contexts"`
54+ Options struct {
55+ Skip []string `json:"skip"`
56+ } `json:"options"`
57+}
58+
59+// xlangKnownDifferences maps "file: group (expr prefix)" keys to the reason
60+// the fixture is not satisfied. Keep it empty if possible.
61+var xlangKnownDifferences = map[string]string{
62+ "coerce_string: number#10": "negative zero: the fixture says \"0\" (as the newer engine, src/Sdk/Expressions/EvaluationResult.cs); the legacy engine that runs today prints \"-0\" (DTExpressions2/EvaluationResult.cs, no -0 special case), checked with gen/runner and engines.mjs",
63+ "format: format#19": "negative zero (format('{0}', -0)), same reason as coerce_string number#10",
64+ "format: format#20": "negative zero (-0.0 literal), same reason",
65+ "tojson: basics#4": "negative zero (tojson(-0)), same reason: toJson writes numbers with ConvertToString",
66+ "coerce_string: number#11": "negative zero (-0.0 literal), same reason as number#10",
67+ "case: case#27": "the runner reports an even number of case() arguments on the '(' token (position 5), not on the name: ExpressionParser.FlushTopEndParameters passes the popped StartParameters token; checked by running the runner's code",
68+ "syntax-errors: memory-errors#0": "memory accounting differs from the runner's EvaluationMemory",
69+}
70+
71+// xlangKind is the kind name the fixtures use.
72+func xlangKind(v Value) string {
73+ switch v.Kind() {
74+ case KindArray:
75+ return "Array"
76+ case KindBoolean:
77+ return "Boolean"
78+ case KindNull:
79+ return "Null"
80+ case KindNumber:
81+ return "Number"
82+ case KindString:
83+ return "String"
84+ }
85+ return "Object"
86+}
87+
88+// jsonStringify mimics JSON.stringify(v) (no indentation) for a value.
89+func jsonStringify(b *strings.Builder, v Value) {
90+ switch x := v.(type) {
91+ case nil, Null:
92+ b.WriteString("null")
93+ case Boolean:
94+ b.WriteString(strconv.FormatBool(bool(x)))
95+ case Number:
96+ f := float64(x)
97+ if math.IsNaN(f) || math.IsInf(f, 0) {
98+ b.WriteString("null")
99+ } else {
100+ b.WriteString(jsNumberToString(f))
101+ }
102+ case String:
103+ s, _ := json.Marshal(string(x))
104+ b.Write(s)
105+ case *Array:
106+ b.WriteByte('[')
107+ for i, it := range x.Items() {
108+ if i > 0 {
109+ b.WriteByte(',')
110+ }
111+ jsonStringify(b, it)
112+ }
113+ b.WriteByte(']')
114+ case *Dictionary:
115+ b.WriteByte('{')
116+ for i, p := range x.Pairs() {
117+ if i > 0 {
118+ b.WriteByte(',')
119+ }
120+ k, _ := json.Marshal(p.Key)
121+ b.Write(k)
122+ b.WriteByte(':')
123+ jsonStringify(b, p.Value)
124+ }
125+ b.WriteByte('}')
126+ }
127+}
128+
129+func stringifyValue(v Value) string {
130+ var b strings.Builder
131+ jsonStringify(&b, v)
132+ return b.String()
133+}
134+
135+// xlangContexts returns the revived contexts and their names, sorted.
136+func xlangContexts(tb testing.TB, raw json.RawMessage) (map[string]Value, []string) {
137+ tb.Helper()
138+ ctx := map[string]Value{}
139+ if len(raw) == 0 {
140+ return ctx, nil
141+ }
142+ var m map[string]json.RawMessage
143+ if err := json.Unmarshal(raw, &m); err != nil {
144+ tb.Fatalf("contexts: %v", err)
145+ }
146+ var names []string
147+ for k, r := range m {
148+ ctx[k] = mustJSON(tb, string(r))
149+ names = append(names, k)
150+ }
151+ sort.Strings(names)
152+ return ctx, names
153+}
154+
155+// runXlangCase returns "" when the case passes, else the reason it fails.
156+func runXlangCase(tb testing.TB, c xlangCase) string {
157+ ctxValues, names := xlangContexts(tb, c.Contexts)
158+ expectErr := func(kind string) bool { return c.Err != nil && c.Err.Kind == kind }
159+
160+ // Lex.
161+ _, err := Lex(c.Expr)
162+ if err != nil {
163+ if expectErr("lexing") {
164+ if strings.Contains(err.Error(), c.Err.Value) {
165+ return ""
166+ }
167+ return fmt.Sprintf("lexing error %q does not contain %q", err.Error(), c.Err.Value)
168+ }
169+ return "unexpected error lexing expression: " + err.Error()
170+ }
171+ if expectErr("lexing") {
172+ return "expected error lexing expression, but got none"
173+ }
174+
175+ // Parse.
176+ node, err := Parse(c.Expr, names, nil)
177+ if err != nil {
178+ if expectErr("parsing") {
179+ var pe *Error
180+ if !errors.As(err, &pe) {
181+ return fmt.Sprintf("parse error has type %T", err)
182+ }
183+ // The TypeScript runner builds the suffix from a column; this
184+ // package, like the C# runner, already has it in the message.
185+ full := pe.Message
186+ if !strings.Contains(full, ". Located at position ") {
187+ full = fmt.Sprintf("%s. Located at position %d within expression: %s", pe.Message, pe.Pos.Column+1, c.Expr)
188+ }
189+ if strings.Contains(full, c.Err.Value) {
190+ return ""
191+ }
192+ return fmt.Sprintf("parsing error %q does not contain %q", full, c.Err.Value)
193+ }
194+ return "unexpected error parsing expression: " + err.Error()
195+ }
196+ if expectErr("parsing") {
197+ return "expected error parsing expression, but got none"
198+ }
199+
200+ // Evaluate. An empty expression is null.
201+ var result Value = Null{}
202+ if node != nil {
203+ result, err = Evaluate(context.Background(), node, EvalContext{Contexts: ctxValues})
204+ }
205+ if err != nil {
206+ if expectErr("evaluation") {
207+ full := err.Error() + ". Located within expression: " + c.Expr
208+ if strings.Contains(full, c.Err.Value) {
209+ return ""
210+ }
211+ return fmt.Sprintf("evaluation error %q does not contain %q", full, c.Err.Value)
212+ }
213+ return "unexpected error evaluating expression: " + err.Error()
214+ }
215+ if expectErr("evaluation") {
216+ return "expected error evaluating expression, but got none"
217+ }
218+ if c.Result == nil {
219+ return "fixture has neither result nor err"
220+ }
221+ if k := xlangKind(result); k != c.Result.Kind {
222+ return fmt.Sprintf("kind: got %s, want %s", k, c.Result.Kind)
223+ }
224+ want := stringifyValue(mustJSON(tb, string(c.Result.Value)))
225+ if got := stringifyValue(result); got != want {
226+ return fmt.Sprintf("value: got %s, want %s", got, want)
227+ }
228+ return ""
229+}
230+
231+func TestLanguageServicesFixtures(t *testing.T) {
232+ files, err := filepath.Glob("testdata/languageservices/*.json")
233+ if err != nil || len(files) != 28 {
234+ t.Fatalf("fixture files: %d, %v", len(files), err)
235+ }
236+ var total, skippedByTS, known, failed int
237+ for _, file := range files {
238+ name := strings.TrimSuffix(filepath.Base(file), ".json")
239+ data, err := os.ReadFile(file)
240+ if err != nil {
241+ t.Fatal(err)
242+ }
243+ var groups map[string][]xlangCase
244+ if err := json.Unmarshal(data, &groups); err != nil {
245+ t.Fatalf("%s: %v", file, err)
246+ }
247+ groupNames := make([]string, 0, len(groups))
248+ for g := range groups {
249+ groupNames = append(groupNames, g)
250+ }
251+ sort.Strings(groupNames)
252+ for _, g := range groupNames {
253+ for i, c := range groups[g] {
254+ total++
255+ for _, s := range c.Options.Skip {
256+ if s == "typescript" {
257+ skippedByTS++
258+ }
259+ }
260+ key := name + ": " + g
261+ label := fmt.Sprintf("%s/%s/%d", name, g, i)
262+ reason, isKnown := xlangKnownDifferences[fmt.Sprintf("%s#%d", key, i)]
263+ t.Run(label, func(t *testing.T) {
264+ msg := runXlangCase(t, c)
265+ switch {
266+ case msg == "" && isKnown:
267+ t.Errorf("listed as a known difference (%s) but passes", reason)
268+ case msg != "" && isKnown:
269+ known++
270+ t.Logf("known difference (%s): %s [%s]", reason, msg, c.Expr)
271+ case msg != "":
272+ failed++
273+ t.Errorf("%s [%s]", msg, c.Expr)
274+ }
275+ })
276+ }
277+ }
278+ }
279+ t.Logf("%d fixtures in %d files (%d skipped by the TypeScript runner and run here), %d known differences, %d failures", total, len(files), skippedByTS, known, failed)
280+ if total != 1020 {
281+ t.Errorf("expected 1020 fixtures, found %d", total)
282+ }
283+}
M template/read_test.go +5 -4
@@ -22,17 +22,18 @@ const head = "on: push\njobs:\n a:\n runs-on: x\n steps:\n"
22 22
23 func TestReadErrors(t *testing.T) {23 func TestReadErrors(t *testing.T) {
24 // Expected messages come from @actions/workflow-parser 0.3.61 run on the24 // Expected messages come from @actions/workflow-parser 0.3.61 run on the
25- // same inputs.25+ // same inputs, except that expression errors carry the position suffix of
26+ // the C# runner (package expr follows it).
26 tests := []struct {27 tests := []struct {
27 name, src string28 name, src string
28 want []string29 want []string
29 }{30 }{
30 {"unknown context, block scalar", head + " - run: |\n echo hi\n echo ${{ foo.bar }}\n",31 {"unknown context, block scalar", head + " - run: |\n echo hi\n echo ${{ foo.bar }}\n",
31- []string{"a.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'"}},32+ []string{"a.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'. Located at position 1 within expression: foo.bar"}},
32 {"unknown context, plain", head + " - run: echo ${{ foo.bar }} abc\n",33 {"unknown context, plain", head + " - run: echo ${{ foo.bar }} abc\n",
33- []string{"a.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'"}},34+ []string{"a.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'. Located at position 1 within expression: foo.bar"}},
34 {"unknown context, double quoted", head + " - run: \"echo ${{ foo.bar }} abc\"\n",35 {"unknown context, double quoted", head + " - run: \"echo ${{ foo.bar }} abc\"\n",
35- []string{"a.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'"}},36+ []string{"a.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'. Located at position 1 within expression: foo.bar"}},
36 {"bad expression and insert in a name", head + " - run: echo ${{ 1 + }}\n - name: ${{ insert }}\n run: x\n",37 {"bad expression and insert in a name", head + " - run: echo ${{ 1 + }}\n - name: ${{ insert }}\n run: x\n",
37 []string{"a.yml (Line: 6, Col: 14): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", "a.yml (Line: 7, Col: 15): Unexpected value '${{ insert }}'"}},38 []string{"a.yml (Line: 6, Col: 14): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", "a.yml (Line: 7, Col: 15): Unexpected value '${{ insert }}'"}},
38 {"not closed", head + " - run: echo ${{ 1\n",39 {"not closed", head + " - run: echo ${{ 1\n",
M workflow/convert_test.go +69 -1
@@ -7,6 +7,7 @@ import (
7 "os"7 "os"
8 "path/filepath"8 "path/filepath"
9 "reflect"9 "reflect"
10+ "regexp"
10 "sort"11 "sort"
11 "strings"12 "strings"
12 "testing"13 "testing"
@@ -50,6 +51,73 @@ func errorTexts(errs []Error) []string {
50 return out51 return out
51 }52 }
52 53
54+// The reference (@actions/workflow-parser) words expression parse errors like
55+// the JavaScript expression library: no position suffix, and the function name
56+// in arity errors. package expr follows the C# runner, which is what runs the
57+// workflow: "<description>: '<token>'. Located at position N within
58+// expression: <expression>", with the "(" token in arity errors. Only the
59+// reference's side is rewritten (sameErrorAsReference); what this package reports
60+// is compared as it is, and must carry the runner's suffix.
61+var (
62+ runnerArity = regexp.MustCompile(`((?:Too few|Too many) parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '[^']*'`)
63+ // exprError recognises the messages of package expr that the runner ends
64+ // with its position suffix.
65+ exprError = regexp.MustCompile(`: (?:Unrecognized named-value|Unrecognized function|Unexpected symbol|Unexpected end of expression|Too few parameters supplied|Too many parameters supplied|Even number of parameters supplied, requires an odd number of parameters): '`)
66+ runnerSuffix = regexp.MustCompile(`^\. Located at position \d+ within expression: .+$`)
67+)
68+
69+// sameErrorAsReference reports whether got, the message of this package, is the
70+// reference's want in the runner's wording: the reference's arity errors name
71+// the function where the runner names "(", and its expression errors lack the
72+// position suffix that the runner adds (when the reference has it already, as
73+// for lexer errors, the message must be the same).
74+func sameErrorAsReference(got, want string) bool {
75+ want = runnerArity.ReplaceAllString(want, "$1: '('")
76+ if !exprError.MatchString(want) {
77+ return got == want
78+ }
79+ if strings.Contains(want, ". Located at position ") {
80+ return got == want
81+ }
82+ rest, ok := strings.CutPrefix(got, want)
83+ return ok && runnerSuffix.MatchString(rest)
84+}
85+
86+func sameErrorsAsReference(got, want []string) bool {
87+ if len(got) != len(want) {
88+ return false
89+ }
90+ for i := range got {
91+ if !sameErrorAsReference(got[i], want[i]) {
92+ return false
93+ }
94+ }
95+ return true
96+}
97+
98+func TestSameErrorAsReference(t *testing.T) {
99+ const loc = ". Located at position 3 within expression: a b"
100+ for _, tc := range []struct {
101+ name, got, want string
102+ ok bool
103+ }{
104+ {"suffix added", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'" + loc, "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", true},
105+ {"suffix missing", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", false},
106+ {"suffix empty expression", "f.yml (Line: 1, Col: 1): Unexpected symbol: 'a'. Located at position 3 within expression: ", "f.yml (Line: 1, Col: 1): Unexpected symbol: 'a'", false},
107+ {"arity names the paren", "f.yml (Line: 1, Col: 1): Too many parameters supplied: '('" + loc, "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'", true},
108+ {"arity names the function", "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'" + loc, "f.yml (Line: 1, Col: 1): Too many parameters supplied: 'failure'", false},
109+ {"different token", "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'b'" + loc, "f.yml (Line: 1, Col: 1): Unrecognized named-value: 'a'", false},
110+ {"reference has the suffix", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", true},
111+ {"reference suffix differs", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 4 within expression: 1 +", "f.yml (Line: 1, Col: 1): Unexpected symbol: '+'. Located at position 3 within expression: 1 +", false},
112+ {"not an expression error", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", true},
113+ {"not an expression error with a suffix", "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'" + loc, "f.yml (Line: 1, Col: 1): Unexpected value '${{ x }}'", false},
114+ } {
115+ if got := sameErrorAsReference(tc.got, tc.want); got != tc.ok {
116+ t.Errorf("%s: got %v, want %v", tc.name, got, tc.ok)
117+ }
118+ }
119+}
120+
53 // isYAMLError recognises the reference's YAML syntax errors, whose text is121 // isYAMLError recognises the reference's YAML syntax errors, whose text is
54 // the JavaScript library's and cannot be reproduced word for word.122 // the JavaScript library's and cannot be reproduced word for word.
55 func isYAMLError(s string) bool { return strings.Contains(s, " at line ") }123 func isYAMLError(s string) bool { return strings.Contains(s, " at line ") }
@@ -101,7 +169,7 @@ func TestConvertAgainstReference(t *testing.T) {
101 }169 }
102 return170 return
103 }171 }
104- if !reflect.DeepEqual(nonNil(got), nonNil(want.Errors)) {172+ if !sameErrorsAsReference(nonNil(got), nonNil(want.Errors)) {
105 t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors)173 t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors)
106 }174 }
107 if len(want.Errors) > 0 {175 if len(want.Errors) > 0 {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close