@xano/xanoscript-language-server 12.0.0 → 12.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lexer/error_trigger.js +23 -0
- package/lexer/tokens.js +6 -0
- package/onHover/functions.md +46 -0
- package/package.json +1 -1
- package/parser/attributes/columnDefaultValueAttribute.js +2 -0
- package/parser/definitions/objectColumnDefinition.js +8 -2
- package/parser/error_trigger_parser.js +88 -0
- package/parser/functions/api/apiLambdaFn.js +2 -2
- package/parser/functions/api/apiMicroserviceFn.js +39 -0
- package/parser/functions/api/register.js +2 -0
- package/parser/functions/apiFn.js +1 -0
- package/parser/functions/array/arrayShiftFn.js +11 -2
- package/parser/functions/controls/conditionalElifFn.js +2 -0
- package/parser/functions/controls/forFn.js +1 -0
- package/parser/functions/db/dbBulkAddFn.js +1 -1
- package/parser/functions/schema/schemaParseObjectFn.js +10 -2
- package/parser/functions/storage/storageCreateAttachmentFn.js +1 -1
- package/parser/generic/expressionFn.js +19 -6
- package/parser/generic/filterFn.js +14 -1
- package/parser/parser.js +3 -0
- package/parser/register.js +5 -0
- package/utils.js +1 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Identifier } from "./identifier.js";
|
|
2
|
+
import { createTokenByName } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
// "error_trigger"
|
|
5
|
+
export const ErrorTriggerToken = createTokenByName("error_trigger", {
|
|
6
|
+
longer_alt: Identifier,
|
|
7
|
+
categories: [Identifier], // could also be an identifier
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const ErrorTriggerTokens = [ErrorTriggerToken];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Maps a token name to a type
|
|
14
|
+
* @param {string} token the token name
|
|
15
|
+
*/
|
|
16
|
+
export function mapTokenToType(token) {
|
|
17
|
+
switch (token) {
|
|
18
|
+
case ErrorTriggerToken.name:
|
|
19
|
+
return "keyword";
|
|
20
|
+
default:
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lexer/tokens.js
CHANGED
|
@@ -54,6 +54,10 @@ import {
|
|
|
54
54
|
} from "./control.js";
|
|
55
55
|
import { DbTokens, mapTokenToType as mapDbTokenToType } from "./db.js";
|
|
56
56
|
import { DebugTokens, mapTokenToType as mapDebugTokenToType } from "./debug.js";
|
|
57
|
+
import {
|
|
58
|
+
ErrorTriggerTokens,
|
|
59
|
+
mapTokenToType as mapErrorTriggerTokenToType,
|
|
60
|
+
} from "./error_trigger.js";
|
|
57
61
|
import {
|
|
58
62
|
ExpectFnTokens,
|
|
59
63
|
mapTokenToType as mapExpectTokenToType,
|
|
@@ -399,6 +403,7 @@ export const allTokens = uniq([
|
|
|
399
403
|
...UtilTokens,
|
|
400
404
|
...TableTriggerTokens,
|
|
401
405
|
...WorkspaceTriggerTokens,
|
|
406
|
+
...ErrorTriggerTokens,
|
|
402
407
|
...TaskTokens,
|
|
403
408
|
...WorkflowTestTokens,
|
|
404
409
|
...ApiGroupTokens,
|
|
@@ -446,6 +451,7 @@ const tokenMappers = [
|
|
|
446
451
|
mapControlsTokenToType,
|
|
447
452
|
mapDbTokenToType,
|
|
448
453
|
mapDebugTokenToType,
|
|
454
|
+
mapErrorTriggerTokenToType,
|
|
449
455
|
mapExpectTokenToType,
|
|
450
456
|
mapFunctionTokenToType,
|
|
451
457
|
mapLiteralTokenToType,
|
package/onHover/functions.md
CHANGED
|
@@ -287,6 +287,52 @@ A `task` file defines a scheduled job that runs automatically at specified times
|
|
|
287
287
|
|
|
288
288
|
Tasks are ideal for automating recurring operations like generating reports or syncing data.
|
|
289
289
|
|
|
290
|
+
# error_trigger
|
|
291
|
+
|
|
292
|
+
```xs
|
|
293
|
+
error_trigger "notify on errors" {
|
|
294
|
+
input {
|
|
295
|
+
enum event? {
|
|
296
|
+
values = ["new", "regression", "fixed"]
|
|
297
|
+
}
|
|
298
|
+
int id?
|
|
299
|
+
text signature?
|
|
300
|
+
object error? {
|
|
301
|
+
schema {
|
|
302
|
+
text code?
|
|
303
|
+
text message?
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
stack {
|
|
309
|
+
api.request {
|
|
310
|
+
url = "https://hooks.example.com/alerts"
|
|
311
|
+
method = "POST"
|
|
312
|
+
params = {
|
|
313
|
+
event : $input.event
|
|
314
|
+
message: $input.error.message
|
|
315
|
+
}
|
|
316
|
+
} as $api1
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
An `error_trigger` file defines the workspace's error-notification handler, fired when error signatures are first captured, regress, or are marked fixed. Only one `error_trigger` exists per workspace. It includes:
|
|
322
|
+
|
|
323
|
+
- A name (e.g., `"notify on errors"` or `trigger_on_error`) to identify the trigger,
|
|
324
|
+
|
|
325
|
+
- An `input` block describing the payload the runtime delivers, including:
|
|
326
|
+
- `event`: which event fired the trigger — `"new"` (first capture or re-capture after the ~5 minute snapshot dedup window), `"regression"` (a previously-fixed signature re-occurred), or `"fixed"` (a user marked the signature as fixed),
|
|
327
|
+
- `signature`, `id`: stable identifiers for the error,
|
|
328
|
+
- `error`: the captured error `code` and `message`,
|
|
329
|
+
- `caller`, `statement`: where the error originated (null on `"fixed"` events),
|
|
330
|
+
- `actor`: the user who marked it fixed (only on `"fixed"` events),
|
|
331
|
+
- `count`, `first_seen`, `last_seen`, `fixed_at`: occurrence metadata.
|
|
332
|
+
- A `stack` block defining the actions to take — typically calling out to a webhook, sending an email, or otherwise notifying the team.
|
|
333
|
+
|
|
334
|
+
Use an `error_trigger` to wire workspace-wide alerting on top of the error-logs system without subscribing per object or per event source.
|
|
335
|
+
|
|
290
336
|
# run.job
|
|
291
337
|
|
|
292
338
|
```xs
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
FalseToken,
|
|
9
9
|
Identifier,
|
|
10
10
|
NowToken,
|
|
11
|
+
NullToken,
|
|
11
12
|
TrueToken,
|
|
12
13
|
} from "../../lexer/tokens.js";
|
|
13
14
|
|
|
@@ -33,6 +34,7 @@ export function columnDefaultValueAttribute($) {
|
|
|
33
34
|
{ ALT: () => $.CONSUME(TrueToken) }, // true
|
|
34
35
|
{ ALT: () => $.CONSUME(FalseToken) }, // false
|
|
35
36
|
{ ALT: () => $.CONSUME(NowToken) }, // now
|
|
37
|
+
{ ALT: () => $.CONSUME(NullToken) }, // null
|
|
36
38
|
{ ALT: () => $.CONSUME(Identifier) }, // when everything else fails, it's an identifier
|
|
37
39
|
]);
|
|
38
40
|
$.sectionStack.pop();
|
|
@@ -19,7 +19,7 @@ export function objectColumnDefinition($) {
|
|
|
19
19
|
const parent = $.CONSUME(ObjectToken); // "object"
|
|
20
20
|
|
|
21
21
|
let iterable = false;
|
|
22
|
-
|
|
22
|
+
let nullable = false;
|
|
23
23
|
let optional = false;
|
|
24
24
|
|
|
25
25
|
$.OPTION2(() => {
|
|
@@ -28,6 +28,12 @@ export function objectColumnDefinition($) {
|
|
|
28
28
|
$.SUBRULE($.arraySlice);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
// object? (nullable) or object[]? (nullable array)
|
|
32
|
+
$.OPTION3(() => {
|
|
33
|
+
nullable = true;
|
|
34
|
+
$.CONSUME(Question);
|
|
35
|
+
});
|
|
36
|
+
|
|
31
37
|
// Column name (e.g., "user_id", "optional_column", "required_column")
|
|
32
38
|
const name = $.CONSUME(Identifier);
|
|
33
39
|
|
|
@@ -40,7 +46,7 @@ export function objectColumnDefinition($) {
|
|
|
40
46
|
// ? (is it an optional field)
|
|
41
47
|
$.OPTION(() => {
|
|
42
48
|
optional = true;
|
|
43
|
-
$.
|
|
49
|
+
$.CONSUME1(Question);
|
|
44
50
|
});
|
|
45
51
|
|
|
46
52
|
$.SUBRULE($.objectColumnMetadataDefinition); // Object metadata
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { LCurly, RCurly } from "../lexer/control.js";
|
|
2
|
+
import { ErrorTriggerToken } from "../lexer/error_trigger.js";
|
|
3
|
+
import { StringLiteral } from "../lexer/literal.js";
|
|
4
|
+
import { Identifier, NewlineToken } from "../lexer/tokens.js";
|
|
5
|
+
|
|
6
|
+
export function errorTriggerDeclaration($) {
|
|
7
|
+
return () => {
|
|
8
|
+
let hasDescription = false;
|
|
9
|
+
let hasGuid = false;
|
|
10
|
+
let hasHistory = false;
|
|
11
|
+
let hasInput = false;
|
|
12
|
+
let hasStack = false;
|
|
13
|
+
let hasTags = false;
|
|
14
|
+
|
|
15
|
+
$.sectionStack.push("errorTriggerDeclaration");
|
|
16
|
+
// Allow leading comments and newlines before the error_trigger declaration
|
|
17
|
+
$.SUBRULE($.optionalCommentBlockFn);
|
|
18
|
+
|
|
19
|
+
const parent = $.CONSUME(ErrorTriggerToken); // error_trigger
|
|
20
|
+
const nameToken = $.OR([
|
|
21
|
+
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
22
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
23
|
+
]);
|
|
24
|
+
$.validateDeclarationName(nameToken, "error_trigger");
|
|
25
|
+
$.CONSUME(LCurly); // "{"
|
|
26
|
+
$.MANY(() => {
|
|
27
|
+
$.AT_LEAST_ONE(() => $.CONSUME(NewlineToken)); // at least one new line
|
|
28
|
+
$.OR2([
|
|
29
|
+
{ ALT: () => $.SUBRULE($.commentBlockFn) },
|
|
30
|
+
{
|
|
31
|
+
GATE: () => !hasDescription,
|
|
32
|
+
ALT: () => {
|
|
33
|
+
hasDescription = true;
|
|
34
|
+
$.SUBRULE($.descriptionFieldAttribute);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
GATE: () => !hasGuid,
|
|
39
|
+
ALT: () => {
|
|
40
|
+
hasGuid = true;
|
|
41
|
+
$.SUBRULE($.guidFieldAttribute);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
GATE: () => !hasHistory,
|
|
46
|
+
ALT: () => {
|
|
47
|
+
hasHistory = true;
|
|
48
|
+
$.SUBRULE($.historyClause);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
GATE: () => !hasInput,
|
|
53
|
+
ALT: () => {
|
|
54
|
+
hasInput = true;
|
|
55
|
+
$.SUBRULE($.inputClause);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
GATE: () => !hasStack,
|
|
60
|
+
ALT: () => {
|
|
61
|
+
hasStack = true;
|
|
62
|
+
$.SUBRULE($.stackClause);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
GATE: () => !hasTags,
|
|
67
|
+
ALT: () => {
|
|
68
|
+
hasTags = true;
|
|
69
|
+
$.SUBRULE($.tagsAttribute);
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!hasInput) {
|
|
76
|
+
$.addMissingError(parent, "{} is missing an input clause");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!hasStack) {
|
|
80
|
+
$.addMissingError(parent, "{} is missing a stack clause");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
$.MANY1(() => $.CONSUME1(NewlineToken)); // optional new line
|
|
84
|
+
$.CONSUME(RCurly); // "}"
|
|
85
|
+
$.MANY2(() => $.CONSUME2(NewlineToken)); // optional new line
|
|
86
|
+
$.sectionStack.pop();
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -4,8 +4,8 @@ import { LambdaToken } from "../../../lexer/api.js";
|
|
|
4
4
|
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
5
5
|
*/
|
|
6
6
|
export function apiLambdaFn($) {
|
|
7
|
-
const requiredAttrs = ["code"
|
|
8
|
-
const optionalAttrs = ["description", "disabled"];
|
|
7
|
+
const requiredAttrs = ["code"];
|
|
8
|
+
const optionalAttrs = ["description", "disabled", "timeout"];
|
|
9
9
|
|
|
10
10
|
return () => {
|
|
11
11
|
$.sectionStack.push("apiLambdaFn");
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MicroserviceToken } from "../../../lexer/api.js";
|
|
2
|
+
|
|
3
|
+
// api.microservice {
|
|
4
|
+
// host = $host
|
|
5
|
+
// path = "/reload"
|
|
6
|
+
// method = "POST"
|
|
7
|
+
// params = {}
|
|
8
|
+
// headers = []
|
|
9
|
+
// timeout = 60
|
|
10
|
+
// follow_location = true
|
|
11
|
+
// } as $api_1
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('../../base_parser.js').XanoBaseParser} $
|
|
15
|
+
*/
|
|
16
|
+
export function apiMicroserviceFn($) {
|
|
17
|
+
return () => {
|
|
18
|
+
$.sectionStack.push("apiMicroserviceFn");
|
|
19
|
+
const fnToken = $.CONSUME(MicroserviceToken); // "microservice"
|
|
20
|
+
$.SUBRULE($.schemaParseAttributeFn, {
|
|
21
|
+
ARGS: [
|
|
22
|
+
fnToken,
|
|
23
|
+
{
|
|
24
|
+
host: "[expression]",
|
|
25
|
+
path: "[expression]",
|
|
26
|
+
method: "[expression]",
|
|
27
|
+
"description?": "[string]",
|
|
28
|
+
"disabled?": "[boolean]",
|
|
29
|
+
"params?": "[expression]",
|
|
30
|
+
"headers?": "[expression]",
|
|
31
|
+
"timeout?": "[expression]",
|
|
32
|
+
"follow_location?": "[expression]",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
$.SUBRULE($.asVariable, { ARGS: [fnToken] });
|
|
37
|
+
$.sectionStack.pop();
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiCallFn } from "./apiCallFn.js";
|
|
2
2
|
import { apiLambdaFn } from "./apiLambdaFn.js";
|
|
3
|
+
import { apiMicroserviceFn } from "./apiMicroserviceFn.js";
|
|
3
4
|
import { apiRealtimeEventFn } from "./apiRealtimeEventFn.js";
|
|
4
5
|
import { apiRequestFn } from "./apiRequestFn.js";
|
|
5
6
|
import { apiStreamFn } from "./apiStreamFn.js";
|
|
@@ -11,6 +12,7 @@ import { webflowRequestFn } from "./webflowRequestFn.js";
|
|
|
11
12
|
*/
|
|
12
13
|
export const register = ($) => {
|
|
13
14
|
$.apiLambdaFn = $.RULE("apiLambdaFn", apiLambdaFn($));
|
|
15
|
+
$.apiMicroserviceFn = $.RULE("apiMicroserviceFn", apiMicroserviceFn($));
|
|
14
16
|
$.apiRealtimeEventFn = $.RULE("apiRealtimeEventFn", apiRealtimeEventFn($));
|
|
15
17
|
$.apiRequestFn = $.RULE("apiRequestFn", apiRequestFn($));
|
|
16
18
|
$.apiStreamFn = $.RULE("apiStreamFn", apiStreamFn($));
|
|
@@ -15,6 +15,7 @@ export function apiFn($) {
|
|
|
15
15
|
{ ALT: () => $.SUBRULE($.apiRequestFn) }, // "api.request"
|
|
16
16
|
{ ALT: () => $.SUBRULE($.apiRealtimeEventFn) }, // "api.realtime_event"
|
|
17
17
|
{ ALT: () => $.SUBRULE($.apiStreamFn) }, // "api.stream"
|
|
18
|
+
{ ALT: () => $.SUBRULE($.apiMicroserviceFn) }, // "api.microservice"
|
|
18
19
|
]);
|
|
19
20
|
$.sectionStack.pop();
|
|
20
21
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { ShiftToken } from "../../../lexer/arrays.js";
|
|
1
|
+
import { ArrayToken, ShiftToken } from "../../../lexer/arrays.js";
|
|
2
|
+
import { AsToken } from "../../../lexer/control.js";
|
|
3
|
+
import { DotToken } from "../../../lexer/tokens.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* array.shift $my_array as $test
|
|
@@ -7,9 +9,16 @@ import { ShiftToken } from "../../../lexer/arrays.js";
|
|
|
7
9
|
export function arrayShiftFn($) {
|
|
8
10
|
return () => {
|
|
9
11
|
$.sectionStack.push("arrayShiftFn");
|
|
12
|
+
$.OPTION(() => {
|
|
13
|
+
$.CONSUME(ArrayToken); // "array"
|
|
14
|
+
$.CONSUME(DotToken); // "."
|
|
15
|
+
});
|
|
10
16
|
$.CONSUME(ShiftToken); // "shift"
|
|
11
17
|
$.SUBRULE($.variableOnly);
|
|
12
|
-
$.
|
|
18
|
+
$.OPTION1(() => {
|
|
19
|
+
$.CONSUME(AsToken);
|
|
20
|
+
$.SUBRULE($.assignableVariableAs);
|
|
21
|
+
});
|
|
13
22
|
$.sectionStack.pop();
|
|
14
23
|
};
|
|
15
24
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExclamationToken } from "../../../lexer/cast.js";
|
|
1
2
|
import { ElseifToken, LParent, RParent } from "../../../lexer/control.js";
|
|
2
3
|
import { NewlineToken } from "../../../lexer/tokens.js";
|
|
3
4
|
|
|
@@ -7,6 +8,7 @@ import { NewlineToken } from "../../../lexer/tokens.js";
|
|
|
7
8
|
export function conditionalElifFn($) {
|
|
8
9
|
return () => {
|
|
9
10
|
$.sectionStack.push("conditionalElifFn");
|
|
11
|
+
$.OPTION1(() => $.CONSUME(ExclamationToken)); // "!" (disabled)
|
|
10
12
|
$.CONSUME(ElseifToken); // "elseif"
|
|
11
13
|
$.CONSUME(LParent); // "("
|
|
12
14
|
$.MANY(() => $.CONSUME(NewlineToken));
|
|
@@ -7,7 +7,12 @@ import {
|
|
|
7
7
|
RCurly,
|
|
8
8
|
} from "../../../lexer/control.js";
|
|
9
9
|
import { StringLiteral } from "../../../lexer/literal.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
DotToken,
|
|
12
|
+
Identifier,
|
|
13
|
+
NewlineToken,
|
|
14
|
+
NowToken,
|
|
15
|
+
} from "../../../lexer/tokens.js";
|
|
11
16
|
import { InputVariable, ShortFormVariable } from "../../../lexer/variables.js";
|
|
12
17
|
import {
|
|
13
18
|
canBeDisabledAttribute,
|
|
@@ -67,7 +72,10 @@ export function schemaParseObjectFn($) {
|
|
|
67
72
|
const keyValue = $.OR([
|
|
68
73
|
{
|
|
69
74
|
ALT: () => {
|
|
70
|
-
objectKeyToken = $.
|
|
75
|
+
objectKeyToken = $.OR6([
|
|
76
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
77
|
+
{ ALT: () => $.CONSUME(NowToken) },
|
|
78
|
+
]);
|
|
71
79
|
const tokens = [objectKeyToken];
|
|
72
80
|
$.MANY3(() => {
|
|
73
81
|
tokens.push($.CONSUME(DotToken));
|
|
@@ -115,6 +115,13 @@ export function expressionFn($) {
|
|
|
115
115
|
let hasTests = false;
|
|
116
116
|
let has_ternary = false;
|
|
117
117
|
|
|
118
|
+
// Inside any delimited sub-expression — `(...)`, `[...]`, `{...}` — a
|
|
119
|
+
// filter chain is unambiguous and should always be allowed, even when
|
|
120
|
+
// the outer expression has filters disabled (e.g. it is itself a filter
|
|
121
|
+
// argument). See filterFn.js for the rationale on why filter args
|
|
122
|
+
// disable filter parsing.
|
|
123
|
+
const delimitedOptions = { ...options, allowFilter: true };
|
|
124
|
+
|
|
118
125
|
$.OR([
|
|
119
126
|
{
|
|
120
127
|
ALT: () => {
|
|
@@ -127,13 +134,17 @@ export function expressionFn($) {
|
|
|
127
134
|
$.OR3([
|
|
128
135
|
{
|
|
129
136
|
ALT: () =>
|
|
130
|
-
$.SUBRULE($.expressionFn, {
|
|
137
|
+
$.SUBRULE($.expressionFn, {
|
|
138
|
+
ARGS: [token, delimitedOptions],
|
|
139
|
+
}),
|
|
131
140
|
},
|
|
132
141
|
{
|
|
133
142
|
// some filters can be used as standalone value (e.g., `(|uuid)`)
|
|
134
143
|
// bug have to be inside parentheses
|
|
135
144
|
ALT: () =>
|
|
136
|
-
$.SUBRULE($.multilineFilterFn, {
|
|
145
|
+
$.SUBRULE($.multilineFilterFn, {
|
|
146
|
+
ARGS: [delimitedOptions],
|
|
147
|
+
}),
|
|
137
148
|
},
|
|
138
149
|
]);
|
|
139
150
|
$.OPTION(() => $.CONSUME1(NewlineToken));
|
|
@@ -160,7 +171,7 @@ export function expressionFn($) {
|
|
|
160
171
|
token,
|
|
161
172
|
options.allowQueryExpression ? ["[query]"] : ["[expression]"],
|
|
162
173
|
{},
|
|
163
|
-
|
|
174
|
+
delimitedOptions,
|
|
164
175
|
],
|
|
165
176
|
});
|
|
166
177
|
},
|
|
@@ -181,7 +192,7 @@ export function expressionFn($) {
|
|
|
181
192
|
: "[expression]",
|
|
182
193
|
},
|
|
183
194
|
{},
|
|
184
|
-
|
|
195
|
+
delimitedOptions,
|
|
185
196
|
],
|
|
186
197
|
});
|
|
187
198
|
$.SUBRULE($.chainedIdentifier);
|
|
@@ -199,8 +210,10 @@ export function expressionFn($) {
|
|
|
199
210
|
// We use LookAhead to peek at the next tokens without consuming them
|
|
200
211
|
// to decide if we should close or keep the gate open
|
|
201
212
|
GATE: () =>
|
|
202
|
-
|
|
203
|
-
($.LA(1).tokenType ===
|
|
213
|
+
options.allowFilter &&
|
|
214
|
+
($.LA(1).tokenType === PipeToken ||
|
|
215
|
+
($.LA(1).tokenType === NewlineToken &&
|
|
216
|
+
$.LA(2).tokenType === PipeToken)),
|
|
204
217
|
DEF: () => {
|
|
205
218
|
hasFilters = true;
|
|
206
219
|
$.SUBRULE($.filterFn, { ARGS: [options] });
|
|
@@ -40,9 +40,22 @@ export function filterFn($) {
|
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Filter arguments do not absorb subsequent `|filter` tokens — those
|
|
44
|
+
// belong to the outer expression's filter chain. Without this, a long
|
|
45
|
+
// sibling chain like `[]|push:1|push:2|push:3` parses as deep right-
|
|
46
|
+
// nested SUBRULEs and overflows the stack. Explicit nesting still works
|
|
47
|
+
// via parentheses (`|f:(x|g:y)`), since the parens branch in
|
|
48
|
+
// expressionFn restores `allowFilter: true`.
|
|
49
|
+
// allowBareIdentifier lets property-name args like `|get:items:null`
|
|
50
|
+
// parse as Identifier without requiring quotes or backticks.
|
|
51
|
+
const argOptions = {
|
|
52
|
+
...options,
|
|
53
|
+
allowFilter: false,
|
|
54
|
+
allowBareIdentifier: true,
|
|
55
|
+
};
|
|
43
56
|
$.MANY1(() => {
|
|
44
57
|
$.CONSUME(ColonToken);
|
|
45
|
-
$.SUBRULE($.expressionFn, { ARGS: [filterToken,
|
|
58
|
+
$.SUBRULE($.expressionFn, { ARGS: [filterToken, argOptions, state] });
|
|
46
59
|
});
|
|
47
60
|
};
|
|
48
61
|
}
|
package/parser/parser.js
CHANGED
|
@@ -36,6 +36,9 @@ export function xanoscriptParser(text, scheme, preTokenized = null) {
|
|
|
36
36
|
case "branch":
|
|
37
37
|
parser.branchDeclaration();
|
|
38
38
|
return parser;
|
|
39
|
+
case "error_trigger":
|
|
40
|
+
parser.errorTriggerDeclaration();
|
|
41
|
+
return parser;
|
|
39
42
|
case "db":
|
|
40
43
|
parser.tableDeclaration();
|
|
41
44
|
return parser;
|
package/parser/register.js
CHANGED
|
@@ -6,6 +6,7 @@ import { register as register_attributes } from "./attributes/register.js";
|
|
|
6
6
|
import { branchDeclaration } from "./branch_parser.js";
|
|
7
7
|
import { register as register_clauses } from "./clauses/register.js";
|
|
8
8
|
import { register as register_definitions } from "./definitions/register.js";
|
|
9
|
+
import { errorTriggerDeclaration } from "./error_trigger_parser.js";
|
|
9
10
|
import { functionDeclaration } from "./function_parser.js";
|
|
10
11
|
import { register as register_functions } from "./functions/register.js";
|
|
11
12
|
import { register as register_generic } from "./generic/register.js";
|
|
@@ -50,6 +51,10 @@ export const register = ($) => {
|
|
|
50
51
|
$.queryDeclaration = $.RULE("queryDeclaration", queryDeclaration($));
|
|
51
52
|
$.apiGroupDeclaration = $.RULE("apiGroupDeclaration", apiGroupDeclaration($));
|
|
52
53
|
$.branchDeclaration = $.RULE("branchDeclaration", branchDeclaration($));
|
|
54
|
+
$.errorTriggerDeclaration = $.RULE(
|
|
55
|
+
"errorTriggerDeclaration",
|
|
56
|
+
errorTriggerDeclaration($)
|
|
57
|
+
);
|
|
53
58
|
$.tableDeclaration = $.RULE("tableDeclaration", tableDeclaration($));
|
|
54
59
|
$.mcpServerDeclaration = $.RULE(
|
|
55
60
|
"mcpServerDeclaration",
|
package/utils.js
CHANGED