@xemahq/dsl 0.1.1
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/LICENSE +201 -0
- package/dist/deliverable-spec/index.d.ts +3 -0
- package/dist/deliverable-spec/index.d.ts.map +1 -0
- package/dist/deliverable-spec/index.js +19 -0
- package/dist/deliverable-spec/index.js.map +1 -0
- package/dist/deliverable-spec/lib/schema.d.ts +151 -0
- package/dist/deliverable-spec/lib/schema.d.ts.map +1 -0
- package/dist/deliverable-spec/lib/schema.js +139 -0
- package/dist/deliverable-spec/lib/schema.js.map +1 -0
- package/dist/deliverable-spec/lib/types.d.ts +8 -0
- package/dist/deliverable-spec/lib/types.d.ts.map +1 -0
- package/dist/deliverable-spec/lib/types.js +3 -0
- package/dist/deliverable-spec/lib/types.js.map +1 -0
- package/dist/payload-codec/index.d.ts +8 -0
- package/dist/payload-codec/index.d.ts.map +1 -0
- package/dist/payload-codec/index.js +27 -0
- package/dist/payload-codec/index.js.map +1 -0
- package/dist/payload-codec/lib/blob-store.d.ts +37 -0
- package/dist/payload-codec/lib/blob-store.d.ts.map +1 -0
- package/dist/payload-codec/lib/blob-store.js +0 -0
- package/dist/payload-codec/lib/blob-store.js.map +1 -0
- package/dist/payload-codec/lib/codec-context.d.ts +6 -0
- package/dist/payload-codec/lib/codec-context.d.ts.map +1 -0
- package/dist/payload-codec/lib/codec-context.js +16 -0
- package/dist/payload-codec/lib/codec-context.js.map +1 -0
- package/dist/payload-codec/lib/codec.d.ts +51 -0
- package/dist/payload-codec/lib/codec.d.ts.map +1 -0
- package/dist/payload-codec/lib/codec.js +330 -0
- package/dist/payload-codec/lib/codec.js.map +1 -0
- package/dist/payload-codec/lib/enums.d.ts +18 -0
- package/dist/payload-codec/lib/enums.d.ts.map +1 -0
- package/dist/payload-codec/lib/enums.js +23 -0
- package/dist/payload-codec/lib/enums.js.map +1 -0
- package/dist/payload-codec/lib/errors.d.ts +18 -0
- package/dist/payload-codec/lib/errors.d.ts.map +1 -0
- package/dist/payload-codec/lib/errors.js +39 -0
- package/dist/payload-codec/lib/errors.js.map +1 -0
- package/dist/payload-codec/lib/http-blob-store.d.ts +21 -0
- package/dist/payload-codec/lib/http-blob-store.d.ts.map +1 -0
- package/dist/payload-codec/lib/http-blob-store.js +139 -0
- package/dist/payload-codec/lib/http-blob-store.js.map +1 -0
- package/dist/payload-codec/lib/lru-cache.d.ts +12 -0
- package/dist/payload-codec/lib/lru-cache.d.ts.map +1 -0
- package/dist/payload-codec/lib/lru-cache.js +59 -0
- package/dist/payload-codec/lib/lru-cache.js.map +1 -0
- package/dist/schema/action.schema.json +181 -0
- package/dist/schema/reusable-workflow.schema.json +46 -0
- package/dist/schema/workflow.schema.json +373 -0
- package/dist/workflow/index.d.ts +14 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +49 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/lib/action-input-validator.d.ts +10 -0
- package/dist/workflow/lib/action-input-validator.d.ts.map +1 -0
- package/dist/workflow/lib/action-input-validator.js +69 -0
- package/dist/workflow/lib/action-input-validator.js.map +1 -0
- package/dist/workflow/lib/compiler/action-shape.d.ts +5 -0
- package/dist/workflow/lib/compiler/action-shape.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/action-shape.js +43 -0
- package/dist/workflow/lib/compiler/action-shape.js.map +1 -0
- package/dist/workflow/lib/compiler/canonical-json.d.ts +3 -0
- package/dist/workflow/lib/compiler/canonical-json.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/canonical-json.js +45 -0
- package/dist/workflow/lib/compiler/canonical-json.js.map +1 -0
- package/dist/workflow/lib/compiler/compile.d.ts +4 -0
- package/dist/workflow/lib/compiler/compile.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/compile.js +794 -0
- package/dist/workflow/lib/compiler/compile.js.map +1 -0
- package/dist/workflow/lib/compiler/concurrency.d.ts +5 -0
- package/dist/workflow/lib/compiler/concurrency.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/concurrency.js +104 -0
- package/dist/workflow/lib/compiler/concurrency.js.map +1 -0
- package/dist/workflow/lib/compiler/dag.d.ts +10 -0
- package/dist/workflow/lib/compiler/dag.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/dag.js +74 -0
- package/dist/workflow/lib/compiler/dag.js.map +1 -0
- package/dist/workflow/lib/compiler/index.d.ts +6 -0
- package/dist/workflow/lib/compiler/index.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/index.js +14 -0
- package/dist/workflow/lib/compiler/index.js.map +1 -0
- package/dist/workflow/lib/compiler/inputs.d.ts +4 -0
- package/dist/workflow/lib/compiler/inputs.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/inputs.js +108 -0
- package/dist/workflow/lib/compiler/inputs.js.map +1 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.d.ts +9 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.js +76 -0
- package/dist/workflow/lib/compiler/installation-resource-validator.js.map +1 -0
- package/dist/workflow/lib/compiler/manifest-source.d.ts +4 -0
- package/dist/workflow/lib/compiler/manifest-source.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/manifest-source.js +100 -0
- package/dist/workflow/lib/compiler/manifest-source.js.map +1 -0
- package/dist/workflow/lib/compiler/matrix.d.ts +4 -0
- package/dist/workflow/lib/compiler/matrix.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/matrix.js +76 -0
- package/dist/workflow/lib/compiler/matrix.js.map +1 -0
- package/dist/workflow/lib/compiler/mount-plan.d.ts +4 -0
- package/dist/workflow/lib/compiler/mount-plan.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/mount-plan.js +96 -0
- package/dist/workflow/lib/compiler/mount-plan.js.map +1 -0
- package/dist/workflow/lib/compiler/payload-reach-in.d.ts +14 -0
- package/dist/workflow/lib/compiler/payload-reach-in.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/payload-reach-in.js +273 -0
- package/dist/workflow/lib/compiler/payload-reach-in.js.map +1 -0
- package/dist/workflow/lib/compiler/permissions.d.ts +6 -0
- package/dist/workflow/lib/compiler/permissions.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/permissions.js +43 -0
- package/dist/workflow/lib/compiler/permissions.js.map +1 -0
- package/dist/workflow/lib/compiler/retry-timeout.d.ts +6 -0
- package/dist/workflow/lib/compiler/retry-timeout.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/retry-timeout.js +64 -0
- package/dist/workflow/lib/compiler/retry-timeout.js.map +1 -0
- package/dist/workflow/lib/compiler/review-step.d.ts +18 -0
- package/dist/workflow/lib/compiler/review-step.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/review-step.js +247 -0
- package/dist/workflow/lib/compiler/review-step.js.map +1 -0
- package/dist/workflow/lib/compiler/types.d.ts +42 -0
- package/dist/workflow/lib/compiler/types.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/types.js +3 -0
- package/dist/workflow/lib/compiler/types.js.map +1 -0
- package/dist/workflow/lib/compiler/variable-requirements.d.ts +5 -0
- package/dist/workflow/lib/compiler/variable-requirements.d.ts.map +1 -0
- package/dist/workflow/lib/compiler/variable-requirements.js +119 -0
- package/dist/workflow/lib/compiler/variable-requirements.js.map +1 -0
- package/dist/workflow/lib/deliverable-spec-keys.d.ts +3 -0
- package/dist/workflow/lib/deliverable-spec-keys.d.ts.map +1 -0
- package/dist/workflow/lib/deliverable-spec-keys.js +90 -0
- package/dist/workflow/lib/deliverable-spec-keys.js.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/index.d.ts +23 -0
- package/dist/workflow/lib/dispatch-inputs/index.d.ts.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/index.js +106 -0
- package/dist/workflow/lib/dispatch-inputs/index.js.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.d.ts +3 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.d.ts.map +1 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.js +43 -0
- package/dist/workflow/lib/dispatch-inputs/to-json-schema.js.map +1 -0
- package/dist/workflow/lib/duration.d.ts +2 -0
- package/dist/workflow/lib/duration.d.ts.map +1 -0
- package/dist/workflow/lib/duration.js +26 -0
- package/dist/workflow/lib/duration.js.map +1 -0
- package/dist/workflow/lib/errors.d.ts +9 -0
- package/dist/workflow/lib/errors.d.ts.map +1 -0
- package/dist/workflow/lib/errors.js +28 -0
- package/dist/workflow/lib/errors.js.map +1 -0
- package/dist/workflow/lib/expression/ast.d.ts +61 -0
- package/dist/workflow/lib/expression/ast.d.ts.map +1 -0
- package/dist/workflow/lib/expression/ast.js +34 -0
- package/dist/workflow/lib/expression/ast.js.map +1 -0
- package/dist/workflow/lib/expression/context.d.ts +63 -0
- package/dist/workflow/lib/expression/context.d.ts.map +1 -0
- package/dist/workflow/lib/expression/context.js +32 -0
- package/dist/workflow/lib/expression/context.js.map +1 -0
- package/dist/workflow/lib/expression/evaluator.d.ts +5 -0
- package/dist/workflow/lib/expression/evaluator.d.ts.map +1 -0
- package/dist/workflow/lib/expression/evaluator.js +291 -0
- package/dist/workflow/lib/expression/evaluator.js.map +1 -0
- package/dist/workflow/lib/expression/index.d.ts +9 -0
- package/dist/workflow/lib/expression/index.d.ts.map +1 -0
- package/dist/workflow/lib/expression/index.js +26 -0
- package/dist/workflow/lib/expression/index.js.map +1 -0
- package/dist/workflow/lib/expression/interpolation.d.ts +9 -0
- package/dist/workflow/lib/expression/interpolation.d.ts.map +1 -0
- package/dist/workflow/lib/expression/interpolation.js +51 -0
- package/dist/workflow/lib/expression/interpolation.js.map +1 -0
- package/dist/workflow/lib/expression/parser.d.ts +4 -0
- package/dist/workflow/lib/expression/parser.d.ts.map +1 -0
- package/dist/workflow/lib/expression/parser.js +203 -0
- package/dist/workflow/lib/expression/parser.js.map +1 -0
- package/dist/workflow/lib/expression/template.d.ts +18 -0
- package/dist/workflow/lib/expression/template.d.ts.map +1 -0
- package/dist/workflow/lib/expression/template.js +63 -0
- package/dist/workflow/lib/expression/template.js.map +1 -0
- package/dist/workflow/lib/expression/tokenizer.d.ts +3 -0
- package/dist/workflow/lib/expression/tokenizer.d.ts.map +1 -0
- package/dist/workflow/lib/expression/tokenizer.js +153 -0
- package/dist/workflow/lib/expression/tokenizer.js.map +1 -0
- package/dist/workflow/lib/expression/tokens.d.ts +25 -0
- package/dist/workflow/lib/expression/tokens.d.ts.map +1 -0
- package/dist/workflow/lib/expression/tokens.js +24 -0
- package/dist/workflow/lib/expression/tokens.js.map +1 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.d.ts +5 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.d.ts.map +1 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.js +138 -0
- package/dist/workflow/lib/expression/walk-artifact-refs.js.map +1 -0
- package/dist/workflow/lib/installation-resource-kind.d.ts +14 -0
- package/dist/workflow/lib/installation-resource-kind.d.ts.map +1 -0
- package/dist/workflow/lib/installation-resource-kind.js +59 -0
- package/dist/workflow/lib/installation-resource-kind.js.map +1 -0
- package/dist/workflow/lib/schemas-loader.d.ts +4 -0
- package/dist/workflow/lib/schemas-loader.d.ts.map +1 -0
- package/dist/workflow/lib/schemas-loader.js +36 -0
- package/dist/workflow/lib/schemas-loader.js.map +1 -0
- package/dist/workflow/lib/serializer.d.ts +3 -0
- package/dist/workflow/lib/serializer.d.ts.map +1 -0
- package/dist/workflow/lib/serializer.js +15 -0
- package/dist/workflow/lib/serializer.js.map +1 -0
- package/dist/workflow/lib/types.d.ts +179 -0
- package/dist/workflow/lib/types.d.ts.map +1 -0
- package/dist/workflow/lib/types.js +3 -0
- package/dist/workflow/lib/types.js.map +1 -0
- package/dist/workflow/lib/validate.d.ts +8 -0
- package/dist/workflow/lib/validate.d.ts.map +1 -0
- package/dist/workflow/lib/validate.js +119 -0
- package/dist/workflow/lib/validate.js.map +1 -0
- package/dist/workspace-manifest/index.d.ts +6 -0
- package/dist/workspace-manifest/index.d.ts.map +1 -0
- package/dist/workspace-manifest/index.js +22 -0
- package/dist/workspace-manifest/index.js.map +1 -0
- package/dist/workspace-manifest/lib/compile.d.ts +8 -0
- package/dist/workspace-manifest/lib/compile.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/compile.js +439 -0
- package/dist/workspace-manifest/lib/compile.js.map +1 -0
- package/dist/workspace-manifest/lib/interpolate.d.ts +12 -0
- package/dist/workspace-manifest/lib/interpolate.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/interpolate.js +81 -0
- package/dist/workspace-manifest/lib/interpolate.js.map +1 -0
- package/dist/workspace-manifest/lib/resolve-extends.d.ts +10 -0
- package/dist/workspace-manifest/lib/resolve-extends.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/resolve-extends.js +108 -0
- package/dist/workspace-manifest/lib/resolve-extends.js.map +1 -0
- package/dist/workspace-manifest/lib/schema.d.ts +710 -0
- package/dist/workspace-manifest/lib/schema.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/schema.js +355 -0
- package/dist/workspace-manifest/lib/schema.js.map +1 -0
- package/dist/workspace-manifest/lib/types.d.ts +153 -0
- package/dist/workspace-manifest/lib/types.d.ts.map +1 -0
- package/dist/workspace-manifest/lib/types.js +10 -0
- package/dist/workspace-manifest/lib/types.js.map +1 -0
- package/package.json +79 -0
- package/schema/action.schema.json +181 -0
- package/schema/reusable-workflow.schema.json +46 -0
- package/schema/workflow.schema.json +373 -0
- package/src/deliverable-spec/index.ts +19 -0
- package/src/deliverable-spec/lib/schema.ts +248 -0
- package/src/deliverable-spec/lib/types.ts +26 -0
- package/src/payload-codec/index.ts +40 -0
- package/src/payload-codec/lib/blob-store.ts +0 -0
- package/src/payload-codec/lib/codec-context.ts +38 -0
- package/src/payload-codec/lib/codec.ts +593 -0
- package/src/payload-codec/lib/enums.ts +58 -0
- package/src/payload-codec/lib/errors.ts +54 -0
- package/src/payload-codec/lib/http-blob-store.ts +257 -0
- package/src/payload-codec/lib/lru-cache.ts +81 -0
- package/src/workflow/index.ts +98 -0
- package/src/workflow/lib/action-input-validator.ts +160 -0
- package/src/workflow/lib/compiler/action-shape.ts +71 -0
- package/src/workflow/lib/compiler/canonical-json.ts +53 -0
- package/src/workflow/lib/compiler/compile.ts +1518 -0
- package/src/workflow/lib/compiler/concurrency.ts +223 -0
- package/src/workflow/lib/compiler/dag.ts +108 -0
- package/src/workflow/lib/compiler/index.ts +10 -0
- package/src/workflow/lib/compiler/inputs.ts +199 -0
- package/src/workflow/lib/compiler/installation-resource-validator.ts +114 -0
- package/src/workflow/lib/compiler/manifest-source.ts +176 -0
- package/src/workflow/lib/compiler/matrix.ts +135 -0
- package/src/workflow/lib/compiler/mount-plan.ts +202 -0
- package/src/workflow/lib/compiler/payload-reach-in.ts +497 -0
- package/src/workflow/lib/compiler/permissions.ts +64 -0
- package/src/workflow/lib/compiler/retry-timeout.ts +105 -0
- package/src/workflow/lib/compiler/review-step.ts +517 -0
- package/src/workflow/lib/compiler/types.ts +170 -0
- package/src/workflow/lib/compiler/variable-requirements.ts +208 -0
- package/src/workflow/lib/deliverable-spec-keys.ts +109 -0
- package/src/workflow/lib/dispatch-inputs/index.ts +160 -0
- package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +60 -0
- package/src/workflow/lib/duration.ts +48 -0
- package/src/workflow/lib/errors.ts +37 -0
- package/src/workflow/lib/expression/ast.ts +108 -0
- package/src/workflow/lib/expression/context.ts +148 -0
- package/src/workflow/lib/expression/evaluator.ts +492 -0
- package/src/workflow/lib/expression/index.ts +28 -0
- package/src/workflow/lib/expression/interpolation.ts +84 -0
- package/src/workflow/lib/expression/parser.ts +264 -0
- package/src/workflow/lib/expression/template.ts +117 -0
- package/src/workflow/lib/expression/tokenizer.ts +200 -0
- package/src/workflow/lib/expression/tokens.ts +30 -0
- package/src/workflow/lib/expression/walk-artifact-refs.ts +232 -0
- package/src/workflow/lib/installation-resource-kind.ts +107 -0
- package/src/workflow/lib/schemas-loader.ts +64 -0
- package/src/workflow/lib/serializer.ts +30 -0
- package/src/workflow/lib/types.ts +361 -0
- package/src/workflow/lib/validate.ts +199 -0
- package/src/workspace-manifest/index.ts +27 -0
- package/src/workspace-manifest/lib/compile.ts +608 -0
- package/src/workspace-manifest/lib/interpolate.ts +140 -0
- package/src/workspace-manifest/lib/resolve-extends.ts +260 -0
- package/src/workspace-manifest/lib/schema.ts +612 -0
- package/src/workspace-manifest/lib/types.ts +392 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { WorkflowErrorCode } from '@xemahq/kernel-contracts/workflow';
|
|
2
|
+
import { WorkflowDslError } from '../errors';
|
|
3
|
+
import {
|
|
4
|
+
type BinaryNode,
|
|
5
|
+
type ExpressionNode,
|
|
6
|
+
ExpressionFunction,
|
|
7
|
+
ExpressionNodeKind,
|
|
8
|
+
ExpressionRoot,
|
|
9
|
+
type IdentifierNode,
|
|
10
|
+
type IndexNode,
|
|
11
|
+
type LiteralNode,
|
|
12
|
+
type MemberNode,
|
|
13
|
+
} from './ast';
|
|
14
|
+
import { type Token, TokenKind } from './tokens';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Recursive-descent parser for the Xema expression subset. Grammar is a
|
|
18
|
+
* closed subset inspired by GitHub Actions' expression language:
|
|
19
|
+
*
|
|
20
|
+
* expression := logicalOr
|
|
21
|
+
* logicalOr := logicalAnd ('||' logicalAnd)*
|
|
22
|
+
* logicalAnd := equality ('&&' equality)*
|
|
23
|
+
* equality := unary (('==' | '!=') unary)*
|
|
24
|
+
* unary := '!' unary | postfix
|
|
25
|
+
* postfix := primary ('.' IDENT | '[' expression ']')*
|
|
26
|
+
* primary := '(' expression ')'
|
|
27
|
+
* | literal
|
|
28
|
+
* | identifierOrCall
|
|
29
|
+
* identifierOrCall := IDENT ('(' argList? ')')?
|
|
30
|
+
* argList := expression (',' expression)*
|
|
31
|
+
*
|
|
32
|
+
* Root identifiers must be one of {needs, matrix, inputs, vars, trigger, job}.
|
|
33
|
+
* Call callees must be one of {fromJSON, toJSON}.
|
|
34
|
+
*/
|
|
35
|
+
export function parseExpression(source: string, tokens: readonly Token[]): ExpressionNode {
|
|
36
|
+
const parser = new Parser(source, tokens);
|
|
37
|
+
const node = parser.parseExpression();
|
|
38
|
+
parser.expect(TokenKind.EOF);
|
|
39
|
+
return node;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class Parser {
|
|
43
|
+
private index = 0;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
private readonly source: string,
|
|
47
|
+
private readonly tokens: readonly Token[],
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
parseExpression(): ExpressionNode {
|
|
51
|
+
return this.parseOr();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
expect(kind: TokenKind): Token {
|
|
55
|
+
const token = this.peek();
|
|
56
|
+
if (token.kind !== kind) {
|
|
57
|
+
throw this.error(
|
|
58
|
+
`Expected ${kind} but found ${token.kind === TokenKind.EOF ? 'end of expression' : token.kind}`,
|
|
59
|
+
token.column,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
this.index++;
|
|
63
|
+
return token;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private peek(): Token {
|
|
67
|
+
const token = this.tokens[this.index];
|
|
68
|
+
if (!token) {
|
|
69
|
+
throw this.error('Parser consumed past end of token stream', this.source.length + 1);
|
|
70
|
+
}
|
|
71
|
+
return token;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private consume(): Token {
|
|
75
|
+
const token = this.peek();
|
|
76
|
+
this.index++;
|
|
77
|
+
return token;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private match(kind: TokenKind): boolean {
|
|
81
|
+
return this.peek().kind === kind;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private parseOr(): ExpressionNode {
|
|
85
|
+
let left = this.parseAnd();
|
|
86
|
+
while (this.match(TokenKind.OR)) {
|
|
87
|
+
this.consume();
|
|
88
|
+
const right = this.parseAnd();
|
|
89
|
+
left = binary(ExpressionNodeKind.BINARY_OR, left, right);
|
|
90
|
+
}
|
|
91
|
+
return left;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private parseAnd(): ExpressionNode {
|
|
95
|
+
let left = this.parseEquality();
|
|
96
|
+
while (this.match(TokenKind.AND)) {
|
|
97
|
+
this.consume();
|
|
98
|
+
const right = this.parseEquality();
|
|
99
|
+
left = binary(ExpressionNodeKind.BINARY_AND, left, right);
|
|
100
|
+
}
|
|
101
|
+
return left;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private parseEquality(): ExpressionNode {
|
|
105
|
+
let left = this.parseUnary();
|
|
106
|
+
for (;;) {
|
|
107
|
+
if (this.match(TokenKind.EQ)) {
|
|
108
|
+
this.consume();
|
|
109
|
+
const right = this.parseUnary();
|
|
110
|
+
left = binary(ExpressionNodeKind.BINARY_EQ, left, right);
|
|
111
|
+
} else if (this.match(TokenKind.NEQ)) {
|
|
112
|
+
this.consume();
|
|
113
|
+
const right = this.parseUnary();
|
|
114
|
+
left = binary(ExpressionNodeKind.BINARY_NEQ, left, right);
|
|
115
|
+
} else {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return left;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private parseUnary(): ExpressionNode {
|
|
123
|
+
if (this.match(TokenKind.BANG)) {
|
|
124
|
+
this.consume();
|
|
125
|
+
const operand = this.parseUnary();
|
|
126
|
+
return { kind: ExpressionNodeKind.UNARY_NOT, operand };
|
|
127
|
+
}
|
|
128
|
+
return this.parsePostfix();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private parsePostfix(): ExpressionNode {
|
|
132
|
+
let node = this.parsePrimary();
|
|
133
|
+
for (;;) {
|
|
134
|
+
if (this.match(TokenKind.DOT)) {
|
|
135
|
+
this.consume();
|
|
136
|
+
const propertyToken = this.expect(TokenKind.IDENTIFIER);
|
|
137
|
+
const member: MemberNode = {
|
|
138
|
+
kind: ExpressionNodeKind.MEMBER,
|
|
139
|
+
target: node,
|
|
140
|
+
property: propertyToken.value,
|
|
141
|
+
};
|
|
142
|
+
node = member;
|
|
143
|
+
} else if (this.match(TokenKind.LBRACKET)) {
|
|
144
|
+
this.consume();
|
|
145
|
+
const indexExpr = this.parseExpression();
|
|
146
|
+
this.expect(TokenKind.RBRACKET);
|
|
147
|
+
const indexNode: IndexNode = {
|
|
148
|
+
kind: ExpressionNodeKind.INDEX,
|
|
149
|
+
target: node,
|
|
150
|
+
index: indexExpr,
|
|
151
|
+
};
|
|
152
|
+
node = indexNode;
|
|
153
|
+
} else {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return node;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private parsePrimary(): ExpressionNode {
|
|
161
|
+
const token = this.peek();
|
|
162
|
+
switch (token.kind) {
|
|
163
|
+
case TokenKind.LPAREN: {
|
|
164
|
+
this.consume();
|
|
165
|
+
const expr = this.parseExpression();
|
|
166
|
+
this.expect(TokenKind.RPAREN);
|
|
167
|
+
return expr;
|
|
168
|
+
}
|
|
169
|
+
case TokenKind.NUMBER: {
|
|
170
|
+
this.consume();
|
|
171
|
+
const literal: LiteralNode = { kind: ExpressionNodeKind.LITERAL, value: Number(token.value) };
|
|
172
|
+
return literal;
|
|
173
|
+
}
|
|
174
|
+
case TokenKind.STRING: {
|
|
175
|
+
this.consume();
|
|
176
|
+
const literal: LiteralNode = { kind: ExpressionNodeKind.LITERAL, value: token.value };
|
|
177
|
+
return literal;
|
|
178
|
+
}
|
|
179
|
+
case TokenKind.BOOLEAN: {
|
|
180
|
+
this.consume();
|
|
181
|
+
const literal: LiteralNode = { kind: ExpressionNodeKind.LITERAL, value: token.value === 'true' };
|
|
182
|
+
return literal;
|
|
183
|
+
}
|
|
184
|
+
case TokenKind.NULL: {
|
|
185
|
+
this.consume();
|
|
186
|
+
const literal: LiteralNode = { kind: ExpressionNodeKind.LITERAL, value: null };
|
|
187
|
+
return literal;
|
|
188
|
+
}
|
|
189
|
+
case TokenKind.IDENTIFIER: {
|
|
190
|
+
return this.parseIdentifierOrCall();
|
|
191
|
+
}
|
|
192
|
+
default:
|
|
193
|
+
throw this.error(
|
|
194
|
+
`Unexpected token '${token.value}' at column ${token.column}`,
|
|
195
|
+
token.column,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private parseIdentifierOrCall(): ExpressionNode {
|
|
201
|
+
const identifierToken = this.expect(TokenKind.IDENTIFIER);
|
|
202
|
+
const name = identifierToken.value;
|
|
203
|
+
|
|
204
|
+
if (this.match(TokenKind.LPAREN)) {
|
|
205
|
+
this.consume();
|
|
206
|
+
if (!isExpressionFunction(name)) {
|
|
207
|
+
throw this.error(
|
|
208
|
+
`Unknown function '${name}' at column ${identifierToken.column}. Allowed: ${Object.values(ExpressionFunction).join(', ')}.`,
|
|
209
|
+
identifierToken.column,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const args: ExpressionNode[] = [];
|
|
213
|
+
if (!this.match(TokenKind.RPAREN)) {
|
|
214
|
+
args.push(this.parseExpression());
|
|
215
|
+
while (this.match(TokenKind.COMMA)) {
|
|
216
|
+
this.consume();
|
|
217
|
+
args.push(this.parseExpression());
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
this.expect(TokenKind.RPAREN);
|
|
221
|
+
return {
|
|
222
|
+
kind: ExpressionNodeKind.CALL,
|
|
223
|
+
callee: name,
|
|
224
|
+
args,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Bare identifier: must be one of the allowed roots.
|
|
229
|
+
if (!isExpressionRoot(name)) {
|
|
230
|
+
throw this.error(
|
|
231
|
+
`Unknown identifier '${name}' at column ${identifierToken.column}. Allowed roots: ${Object.values(ExpressionRoot).join(', ')}.`,
|
|
232
|
+
identifierToken.column,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
const ident: IdentifierNode = { kind: ExpressionNodeKind.IDENTIFIER, name };
|
|
236
|
+
return ident;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private error(message: string, column: number): WorkflowDslError {
|
|
240
|
+
return new WorkflowDslError(
|
|
241
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
242
|
+
`${message} [expression: ${this.source}]`,
|
|
243
|
+
{ expression: this.source, column },
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function binary(
|
|
249
|
+
kind: BinaryNode['kind'],
|
|
250
|
+
left: ExpressionNode,
|
|
251
|
+
right: ExpressionNode,
|
|
252
|
+
): BinaryNode {
|
|
253
|
+
return { kind, left, right };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isExpressionRoot(name: string): name is ExpressionRoot {
|
|
257
|
+
const roots: readonly string[] = Object.values(ExpressionRoot);
|
|
258
|
+
return roots.includes(name);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function isExpressionFunction(name: string): name is ExpressionFunction {
|
|
262
|
+
const fns: readonly string[] = Object.values(ExpressionFunction);
|
|
263
|
+
return fns.includes(name);
|
|
264
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { WorkflowErrorCode } from '@xemahq/kernel-contracts/workflow';
|
|
2
|
+
import { WorkflowDslError } from '../errors';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Template parsing for fields that explicitly accept partial interpolation
|
|
6
|
+
* (`literal-text-${{ expression }}-more-text`). Most authored expressions in
|
|
7
|
+
* the DSL must be fully wrapped — see `interpolation.ts` and the policy on
|
|
8
|
+
* `with`, `outputs`, `if`, `strategy.dynamic.from`. Concurrency `group` is
|
|
9
|
+
* the documented exception, so it composes from this primitive instead.
|
|
10
|
+
*
|
|
11
|
+
* The parser is deterministic and total: every input string maps to one
|
|
12
|
+
* sequence of segments. Authoring errors (an open `${{` without a matching
|
|
13
|
+
* `}}`, an empty body) raise `DSL_EXPRESSION_INVALID` at the position they
|
|
14
|
+
* occur — never at <root>, never silently coerced.
|
|
15
|
+
*/
|
|
16
|
+
export enum TemplateSegmentKind {
|
|
17
|
+
Literal = 'literal',
|
|
18
|
+
Expression = 'expression',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TemplateLiteralSegment {
|
|
22
|
+
readonly kind: TemplateSegmentKind.Literal;
|
|
23
|
+
readonly value: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TemplateExpressionSegment {
|
|
27
|
+
readonly kind: TemplateSegmentKind.Expression;
|
|
28
|
+
readonly source: string;
|
|
29
|
+
/** Byte offset of the opening `${{` within the input source. */
|
|
30
|
+
readonly start: number;
|
|
31
|
+
/** Byte offset just past the closing `}}` within the input source. */
|
|
32
|
+
readonly end: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type TemplateSegment = TemplateLiteralSegment | TemplateExpressionSegment;
|
|
36
|
+
|
|
37
|
+
const OPEN_DELIM = '${{';
|
|
38
|
+
const CLOSE_DELIM = '}}';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse a string into a sequence of literal + expression segments. Pure
|
|
42
|
+
* literals (no interpolation) return a single literal segment; an empty
|
|
43
|
+
* input returns no segments. Unbalanced delimiters fail fast with a typed
|
|
44
|
+
* `WorkflowDslError`.
|
|
45
|
+
*/
|
|
46
|
+
export function parseTemplate(
|
|
47
|
+
source: string,
|
|
48
|
+
authoredPath: readonly string[] = [],
|
|
49
|
+
): readonly TemplateSegment[] {
|
|
50
|
+
const segments: TemplateSegment[] = [];
|
|
51
|
+
let cursor = 0;
|
|
52
|
+
|
|
53
|
+
while (cursor < source.length) {
|
|
54
|
+
const openIdx = source.indexOf(OPEN_DELIM, cursor);
|
|
55
|
+
if (openIdx === -1) {
|
|
56
|
+
segments.push({
|
|
57
|
+
kind: TemplateSegmentKind.Literal,
|
|
58
|
+
value: source.slice(cursor),
|
|
59
|
+
});
|
|
60
|
+
return segments;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (openIdx > cursor) {
|
|
64
|
+
segments.push({
|
|
65
|
+
kind: TemplateSegmentKind.Literal,
|
|
66
|
+
value: source.slice(cursor, openIdx),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const bodyStart = openIdx + OPEN_DELIM.length;
|
|
71
|
+
const closeIdx = source.indexOf(CLOSE_DELIM, bodyStart);
|
|
72
|
+
if (closeIdx === -1) {
|
|
73
|
+
throw new WorkflowDslError(
|
|
74
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
75
|
+
`Unterminated template interpolation at ${pathLabel(authoredPath)} (offset ${openIdx}): missing closing '}}'.`,
|
|
76
|
+
{ authoredPath: [...authoredPath], offset: openIdx },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rawBody = source.slice(bodyStart, closeIdx);
|
|
81
|
+
const trimmed = rawBody.trim();
|
|
82
|
+
if (trimmed.length === 0) {
|
|
83
|
+
throw new WorkflowDslError(
|
|
84
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
85
|
+
`Empty template interpolation at ${pathLabel(authoredPath)} (offset ${openIdx}): '\${{ }}' must contain an expression.`,
|
|
86
|
+
{ authoredPath: [...authoredPath], offset: openIdx },
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
segments.push({
|
|
91
|
+
kind: TemplateSegmentKind.Expression,
|
|
92
|
+
source: trimmed,
|
|
93
|
+
start: openIdx,
|
|
94
|
+
end: closeIdx + CLOSE_DELIM.length,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
cursor = closeIdx + CLOSE_DELIM.length;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return segments;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* True iff the parsed template has at least one expression segment. Cheap
|
|
105
|
+
* shortcut for callers that need to distinguish "static literal" from
|
|
106
|
+
* "needs evaluation" without re-parsing.
|
|
107
|
+
*/
|
|
108
|
+
export function templateHasExpressions(segments: readonly TemplateSegment[]): boolean {
|
|
109
|
+
for (const segment of segments) {
|
|
110
|
+
if (segment.kind === TemplateSegmentKind.Expression) return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function pathLabel(path: readonly string[]): string {
|
|
116
|
+
return path.length === 0 ? '<root>' : path.join('.');
|
|
117
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { WorkflowErrorCode } from '@xemahq/kernel-contracts/workflow';
|
|
2
|
+
import { WorkflowDslError } from '../errors';
|
|
3
|
+
import { type Token, TokenKind } from './tokens';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hand-rolled tokenizer for the expression subset. Deterministic: same
|
|
7
|
+
* input always produces the same token stream. Fails fast on any character
|
|
8
|
+
* outside the grammar.
|
|
9
|
+
*
|
|
10
|
+
* Grammar (surface):
|
|
11
|
+
* identifiers: [A-Za-z_][A-Za-z0-9_-]*
|
|
12
|
+
* numbers: [0-9]+(\.[0-9]+)?
|
|
13
|
+
* strings: '...' (single-quoted; no escapes — keep grammar simple
|
|
14
|
+
* and deterministic. If escapes are needed later, add once
|
|
15
|
+
* with full test coverage; do not retrofit ad-hoc.)
|
|
16
|
+
* operators: == != && || ! ( ) [ ] , .
|
|
17
|
+
* booleans: true | false
|
|
18
|
+
* null: null
|
|
19
|
+
* whitespace: spaces and tabs (ignored)
|
|
20
|
+
*/
|
|
21
|
+
export function tokenize(source: string): readonly Token[] {
|
|
22
|
+
const tokens: Token[] = [];
|
|
23
|
+
const length = source.length;
|
|
24
|
+
let index = 0;
|
|
25
|
+
|
|
26
|
+
while (index < length) {
|
|
27
|
+
const ch = source.charAt(index);
|
|
28
|
+
|
|
29
|
+
if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
|
|
30
|
+
index++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const column = index + 1;
|
|
35
|
+
|
|
36
|
+
if (ch === '(') {
|
|
37
|
+
tokens.push({ kind: TokenKind.LPAREN, value: '(', column });
|
|
38
|
+
index++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (ch === ')') {
|
|
42
|
+
tokens.push({ kind: TokenKind.RPAREN, value: ')', column });
|
|
43
|
+
index++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (ch === '[') {
|
|
47
|
+
tokens.push({ kind: TokenKind.LBRACKET, value: '[', column });
|
|
48
|
+
index++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (ch === ']') {
|
|
52
|
+
tokens.push({ kind: TokenKind.RBRACKET, value: ']', column });
|
|
53
|
+
index++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === ',') {
|
|
57
|
+
tokens.push({ kind: TokenKind.COMMA, value: ',', column });
|
|
58
|
+
index++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (ch === '.') {
|
|
62
|
+
tokens.push({ kind: TokenKind.DOT, value: '.', column });
|
|
63
|
+
index++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (ch === '=' && source.charAt(index + 1) === '=') {
|
|
68
|
+
tokens.push({ kind: TokenKind.EQ, value: '==', column });
|
|
69
|
+
index += 2;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (ch === '!' && source.charAt(index + 1) === '=') {
|
|
74
|
+
tokens.push({ kind: TokenKind.NEQ, value: '!=', column });
|
|
75
|
+
index += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (ch === '!') {
|
|
80
|
+
tokens.push({ kind: TokenKind.BANG, value: '!', column });
|
|
81
|
+
index++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (ch === '&' && source.charAt(index + 1) === '&') {
|
|
86
|
+
tokens.push({ kind: TokenKind.AND, value: '&&', column });
|
|
87
|
+
index += 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (ch === '|' && source.charAt(index + 1) === '|') {
|
|
92
|
+
tokens.push({ kind: TokenKind.OR, value: '||', column });
|
|
93
|
+
index += 2;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (ch === "'") {
|
|
98
|
+
const { value, next } = readString(source, index);
|
|
99
|
+
tokens.push({ kind: TokenKind.STRING, value, column });
|
|
100
|
+
index = next;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isDigit(ch)) {
|
|
105
|
+
const { value, next } = readNumber(source, index);
|
|
106
|
+
tokens.push({ kind: TokenKind.NUMBER, value, column });
|
|
107
|
+
index = next;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isIdentStart(ch)) {
|
|
112
|
+
const { value, next } = readIdentifier(source, index);
|
|
113
|
+
if (value === 'true' || value === 'false') {
|
|
114
|
+
tokens.push({ kind: TokenKind.BOOLEAN, value, column });
|
|
115
|
+
} else if (value === 'null') {
|
|
116
|
+
tokens.push({ kind: TokenKind.NULL, value, column });
|
|
117
|
+
} else {
|
|
118
|
+
tokens.push({ kind: TokenKind.IDENTIFIER, value, column });
|
|
119
|
+
}
|
|
120
|
+
index = next;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new WorkflowDslError(
|
|
125
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
126
|
+
`Unexpected character '${ch}' at column ${column}`,
|
|
127
|
+
{ source, column },
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
tokens.push({ kind: TokenKind.EOF, value: '', column: length + 1 });
|
|
132
|
+
return tokens;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function readString(source: string, start: number): { value: string; next: number } {
|
|
136
|
+
const length = source.length;
|
|
137
|
+
let end = start + 1;
|
|
138
|
+
while (end < length && source.charAt(end) !== "'") {
|
|
139
|
+
if (source.charAt(end) === '\\') {
|
|
140
|
+
throw new WorkflowDslError(
|
|
141
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
142
|
+
`String escapes are not supported in expression literals (column ${end + 1}).`,
|
|
143
|
+
{ source, column: end + 1 },
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
end++;
|
|
147
|
+
}
|
|
148
|
+
if (end >= length) {
|
|
149
|
+
throw new WorkflowDslError(
|
|
150
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
151
|
+
`Unterminated string literal starting at column ${start + 1}`,
|
|
152
|
+
{ source, column: start + 1 },
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return { value: source.slice(start + 1, end), next: end + 1 };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readNumber(source: string, start: number): { value: string; next: number } {
|
|
159
|
+
const length = source.length;
|
|
160
|
+
let end = start;
|
|
161
|
+
while (end < length && isDigit(source.charAt(end))) {
|
|
162
|
+
end++;
|
|
163
|
+
}
|
|
164
|
+
if (end < length && source.charAt(end) === '.') {
|
|
165
|
+
end++;
|
|
166
|
+
const fractionStart = end;
|
|
167
|
+
while (end < length && isDigit(source.charAt(end))) {
|
|
168
|
+
end++;
|
|
169
|
+
}
|
|
170
|
+
if (end === fractionStart) {
|
|
171
|
+
throw new WorkflowDslError(
|
|
172
|
+
WorkflowErrorCode.DSL_EXPRESSION_INVALID,
|
|
173
|
+
`Number literal with trailing dot at column ${start + 1}`,
|
|
174
|
+
{ source, column: start + 1 },
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { value: source.slice(start, end), next: end };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function readIdentifier(source: string, start: number): { value: string; next: number } {
|
|
182
|
+
const length = source.length;
|
|
183
|
+
let end = start;
|
|
184
|
+
while (end < length && isIdentPart(source.charAt(end))) {
|
|
185
|
+
end++;
|
|
186
|
+
}
|
|
187
|
+
return { value: source.slice(start, end), next: end };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isDigit(ch: string): boolean {
|
|
191
|
+
return ch >= '0' && ch <= '9';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isIdentStart(ch: string): boolean {
|
|
195
|
+
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch === '_';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isIdentPart(ch: string): boolean {
|
|
199
|
+
return isIdentStart(ch) || isDigit(ch) || ch === '-';
|
|
200
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token kinds for the Xema expression subset. Closed set; every token
|
|
3
|
+
* produced by the tokenizer picks one of these. Unknown input is rejected.
|
|
4
|
+
*/
|
|
5
|
+
export enum TokenKind {
|
|
6
|
+
IDENTIFIER = 'identifier',
|
|
7
|
+
NUMBER = 'number',
|
|
8
|
+
STRING = 'string',
|
|
9
|
+
BOOLEAN = 'boolean',
|
|
10
|
+
NULL = 'null',
|
|
11
|
+
LPAREN = '(',
|
|
12
|
+
RPAREN = ')',
|
|
13
|
+
LBRACKET = '[',
|
|
14
|
+
RBRACKET = ']',
|
|
15
|
+
COMMA = ',',
|
|
16
|
+
DOT = '.',
|
|
17
|
+
BANG = '!',
|
|
18
|
+
EQ = '==',
|
|
19
|
+
NEQ = '!=',
|
|
20
|
+
AND = '&&',
|
|
21
|
+
OR = '||',
|
|
22
|
+
EOF = 'eof',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Token {
|
|
26
|
+
readonly kind: TokenKind;
|
|
27
|
+
readonly value: string;
|
|
28
|
+
/** 1-based column in the source expression for diagnostic output. */
|
|
29
|
+
readonly column: number;
|
|
30
|
+
}
|