@plurnk/plurnk-grammar 0.2.0 → 0.5.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/bin/plurnk.js +1 -1
- package/{schema → dist/schema}/LogEntry.json +1 -1
- package/{schema → dist/schema}/Packet.json +15 -2
- package/{schema → dist/schema}/Run.json +6 -1
- package/{schema → dist/schema}/SchemeRegistration.json +6 -0
- package/{schema → dist/schema}/Session.json +1 -1
- package/dist/src/AstBuilder.d.ts +19 -0
- package/dist/src/AstBuilder.d.ts.map +1 -0
- package/{src/AstBuilder.ts → dist/src/AstBuilder.js} +176 -161
- package/dist/src/AstBuilder.js.map +1 -0
- package/dist/src/PlurnkErrorStrategy.d.ts +10 -0
- package/dist/src/PlurnkErrorStrategy.d.ts.map +1 -0
- package/{src/PlurnkErrorStrategy.ts → dist/src/PlurnkErrorStrategy.js} +41 -49
- package/dist/src/PlurnkErrorStrategy.js.map +1 -0
- package/dist/src/PlurnkParseError.d.ts +14 -0
- package/dist/src/PlurnkParseError.d.ts.map +1 -0
- package/{src/PlurnkParseError.ts → dist/src/PlurnkParseError.js} +6 -9
- package/dist/src/PlurnkParseError.js.map +1 -0
- package/dist/src/PlurnkParser.d.ts +6 -0
- package/dist/src/PlurnkParser.d.ts.map +1 -0
- package/{src/PlurnkParser.ts → dist/src/PlurnkParser.js} +32 -40
- package/dist/src/PlurnkParser.js.map +1 -0
- package/dist/src/RecordingListener.d.ts +9 -0
- package/dist/src/RecordingListener.d.ts.map +1 -0
- package/dist/src/RecordingListener.js +19 -0
- package/dist/src/RecordingListener.js.map +1 -0
- package/dist/src/Validator.d.ts +28 -0
- package/dist/src/Validator.d.ts.map +1 -0
- package/dist/src/Validator.js +86 -0
- package/dist/src/Validator.js.map +1 -0
- package/dist/src/generated/plurnkLexer.d.ts +88 -0
- package/dist/src/generated/plurnkLexer.d.ts.map +1 -0
- package/dist/src/generated/plurnkLexer.js +440 -0
- package/dist/src/generated/plurnkLexer.js.map +1 -0
- package/dist/src/generated/plurnkParser.d.ts +274 -0
- package/dist/src/generated/plurnkParser.d.ts.map +1 -0
- package/dist/src/generated/plurnkParser.js +1833 -0
- package/dist/src/generated/plurnkParser.js.map +1 -0
- package/{src/generated/plurnkParserVisitor.ts → dist/src/generated/plurnkParserVisitor.d.ts} +2 -7
- package/dist/src/generated/plurnkParserVisitor.d.ts.map +1 -0
- package/dist/src/generated/plurnkParserVisitor.js +131 -0
- package/dist/src/generated/plurnkParserVisitor.js.map +1 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +23 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.generated.d.ts +465 -0
- package/dist/src/types.generated.d.ts.map +1 -0
- package/dist/src/types.generated.js +4 -0
- package/dist/src/types.generated.js.map +1 -0
- package/dist/src/types.js +4 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +10 -7
- package/plurnk.md +1 -1
- package/src/RecordingListener.ts +0 -34
- package/src/Validator.ts +0 -94
- package/src/generated/plurnkLexer.ts +0 -467
- package/src/generated/plurnkParser.ts +0 -1891
- package/src/index.ts +0 -29
- package/src/types.generated.ts +0 -491
- package/src/types.ts +0 -30
- /package/{schema → dist/schema}/Agent.json +0 -0
- /package/{schema → dist/schema}/ChannelContent.json +0 -0
- /package/{schema → dist/schema}/Entry.json +0 -0
- /package/{schema → dist/schema}/LineMarker.json +0 -0
- /package/{schema → dist/schema}/Loop.json +0 -0
- /package/{schema → dist/schema}/MatcherBody.json +0 -0
- /package/{schema → dist/schema}/Params.json +0 -0
- /package/{schema → dist/schema}/ParsedPath.json +0 -0
- /package/{schema → dist/schema}/PlurnkStatement.json +0 -0
- /package/{schema → dist/schema}/Position.json +0 -0
- /package/{schema → dist/schema}/ProviderDeclaration.json +0 -0
- /package/{schema → dist/schema}/SendBody.json +0 -0
- /package/{schema → dist/schema}/Turn.json +0 -0
- /package/{schema → dist/schema}/Visibility.json +0 -0
package/bin/plurnk.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
|
-
import { PlurnkParser } from "../src/index.
|
|
4
|
+
import { PlurnkParser } from "../dist/src/index.js";
|
|
5
5
|
|
|
6
6
|
const USAGE = `Usage:
|
|
7
7
|
plurnk [file] Parse plurnk source from a file (or stdin if omitted or '-')
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
]
|
|
59
59
|
},
|
|
60
60
|
"tx": { "type": "string", "description": "Raw request payload. For origin=model: the literal plurnk DSL substring of assistant.content. For origin=system/client/plugin: whatever the originator emitted, with `mimetype_tx` declaring the structure." },
|
|
61
|
-
"mimetype_tx": { "type": "string", "minLength": 1, "description": "Mimetype of `tx`. Typically `text/
|
|
61
|
+
"mimetype_tx": { "type": "string", "minLength": 1, "description": "Mimetype of `tx`. Typically `text/vnd.plurnk` for model-origin rows; arbitrary per-origin for system/client/plugin." },
|
|
62
62
|
"rx": { "type": "string", "description": "Raw response payload bytes/string." },
|
|
63
63
|
"mimetype_rx": { "type": "string", "minLength": 1 },
|
|
64
64
|
"status_rx": { "type": "integer", "minimum": 100, "maximum": 599 },
|
|
@@ -32,12 +32,25 @@
|
|
|
32
32
|
},
|
|
33
33
|
"user": {
|
|
34
34
|
"type": "object",
|
|
35
|
-
"required": ["tokens", "prompt", "
|
|
35
|
+
"required": ["tokens", "prompt", "telemetry", "system_requirements"],
|
|
36
36
|
"additionalProperties": false,
|
|
37
37
|
"properties": {
|
|
38
38
|
"tokens": { "type": "integer", "minimum": 0 },
|
|
39
39
|
"prompt": { "type": "string", "description": "Copy of loop.prompt — never null on a continuation turn." },
|
|
40
|
-
"
|
|
40
|
+
"telemetry": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"description": "Per-turn instrumentation surfaced to the model. `budget` is renderer-provided markdown describing remaining context / cost / etc. `errors` carries actionless failures (parse errors and similar) that the model should confront on this turn. Inner shapes intentionally open at v0; consumers populate as needs solidify.",
|
|
43
|
+
"required": ["budget", "errors"],
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"properties": {
|
|
46
|
+
"budget": { "type": "string", "description": "text/markdown — budget surface (token/cost/etc. left to spend). Empty string when nothing to surface." },
|
|
47
|
+
"errors": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "object" },
|
|
50
|
+
"description": "TelemetryError[] — element shape TBD. Empty array when no errors to surface."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
41
54
|
"system_requirements": { "type": "string", "description": "text/markdown — per-turn rules." }
|
|
42
55
|
}
|
|
43
56
|
},
|
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
"title": "Run",
|
|
5
5
|
"description": "A stretch of work within a session. Forkable via parent_run_id. Owns per-run logs and visibility state.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": ["id", "version", "session_id", "created_at", "parent_run_id", "cost_pico"],
|
|
7
|
+
"required": ["id", "version", "session_id", "name", "created_at", "parent_run_id", "cost_pico"],
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
10
|
"id": { "type": "integer", "minimum": 1 },
|
|
11
11
|
"version": { "type": "integer", "minimum": 0 },
|
|
12
12
|
"session_id": { "type": "integer", "minimum": 1 },
|
|
13
|
+
"name": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"minLength": 1,
|
|
16
|
+
"description": "Unique within the session. Auto-populated default form `run-{unixtime}` until renamed. Clients display this; cross-table references use `id` so renames don't cascade-break."
|
|
17
|
+
},
|
|
13
18
|
"created_at": { "type": "string", "format": "date-time" },
|
|
14
19
|
"parent_run_id": {
|
|
15
20
|
"type": ["integer", "null"],
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"pattern": "^[a-z][a-z0-9_-]*$",
|
|
21
21
|
"description": "Channel name selected when an op against this scheme has no fragment. Conventionally `body`; exec schemes typically declare `stdout`."
|
|
22
22
|
},
|
|
23
|
+
"channel_orientations": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"description": "Per-channel content-orientation hints. `head` = whole-document or front-anchored (readers care about the beginning); `tail` = append-temporal stream (readers care about the latest content). Channels not listed default to `head`. Renderers use this to pick truncation direction; the amount to render is a core/runtime concern, not a contract field.",
|
|
26
|
+
"propertyNames": { "pattern": "^[a-z][a-z0-9_-]*$" },
|
|
27
|
+
"additionalProperties": { "enum": ["head", "tail"] }
|
|
28
|
+
},
|
|
23
29
|
"writable_by": {
|
|
24
30
|
"type": "array",
|
|
25
31
|
"items": { "enum": ["model", "client", "system", "plugin"] },
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"properties": {
|
|
10
10
|
"id": { "type": "integer", "minimum": 1 },
|
|
11
11
|
"version": { "type": "integer", "minimum": 0 },
|
|
12
|
-
"name": { "type": "string", "minLength": 1, "description": "Unique within the agent.
|
|
12
|
+
"name": { "type": "string", "minLength": 1, "description": "Unique within the agent. Auto-populated default form `session-{unixtime}` until renamed. Clients display this; cross-table references use `id` so renames don't cascade-break." },
|
|
13
13
|
"created_at": { "type": "string", "format": "date-time" },
|
|
14
14
|
"cost_pico": { "type": "integer", "minimum": 0, "description": "Cumulative cost across this session's runs." },
|
|
15
15
|
"scheme_registry_additions": {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ParsedPath, PlurnkStatement, Position } from "./types.ts";
|
|
2
|
+
import type { StatementContext } from "./generated/plurnkParser.ts";
|
|
3
|
+
declare module "xpath" {
|
|
4
|
+
function parse(expression: string): unknown;
|
|
5
|
+
}
|
|
6
|
+
export default class AstBuilder {
|
|
7
|
+
#private;
|
|
8
|
+
static build(ctx: StatementContext): PlurnkStatement;
|
|
9
|
+
/**
|
|
10
|
+
* Parse a path string into a ParsedPath, mirroring how the AST visitor
|
|
11
|
+
* decomposes path slots inside HEREDOC statements. Public for consumers
|
|
12
|
+
* (RPC layers, scheme handlers) that need to honor the grammar's
|
|
13
|
+
* authority-vs-opaque cleavage without round-tripping through a fake
|
|
14
|
+
* HEREDOC. Returns null when `raw` is empty. Throws PlurnkParseError
|
|
15
|
+
* when `raw` starts with `scheme://` but WHATWG URL rejects it.
|
|
16
|
+
*/
|
|
17
|
+
static parsePath(raw: string, pos?: Position): ParsedPath | null;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=AstBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AstBuilder.d.ts","sourceRoot":"","sources":["../../src/AstBuilder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EASR,UAAU,EAEV,eAAe,EACf,QAAQ,EAKX,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAYR,gBAAgB,EAEnB,MAAM,6BAA6B,CAAC;AAWrC,OAAO,QAAQ,OAAO,CAAC;IACnB,SAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;CACtD;AAQD,MAAM,CAAC,OAAO,OAAO,UAAU;;IAa3B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,gBAAgB,GAAG,eAAe;IA6NpD;;;;;;;OAOG;IACH,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,QAAiC,GAAG,UAAU,GAAG,IAAI;CA6H3F"}
|
|
@@ -1,76 +1,50 @@
|
|
|
1
1
|
import { ParserRuleContext } from "antlr4ng";
|
|
2
2
|
import * as xpath from "xpath";
|
|
3
3
|
import { JSONPath } from "jsonpath-plus";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
EditStatement,
|
|
7
|
-
ExecStatement,
|
|
8
|
-
FindStatement,
|
|
9
|
-
HideStatement,
|
|
10
|
-
LineMarker,
|
|
11
|
-
MatcherBody,
|
|
12
|
-
MoveStatement,
|
|
13
|
-
ParsedPath,
|
|
14
|
-
PlurnkOp,
|
|
15
|
-
PlurnkStatement,
|
|
16
|
-
Position,
|
|
17
|
-
ReadStatement,
|
|
18
|
-
SendBody,
|
|
19
|
-
SendStatement,
|
|
20
|
-
ShowStatement,
|
|
21
|
-
} from "./types.ts";
|
|
22
|
-
import type {
|
|
23
|
-
CopyStatementContext,
|
|
24
|
-
EditStatementContext,
|
|
25
|
-
ExecModifiersContext,
|
|
26
|
-
ExecStatementContext,
|
|
27
|
-
FindStatementContext,
|
|
28
|
-
HideStatementContext,
|
|
29
|
-
MoveStatementContext,
|
|
30
|
-
ReadStatementContext,
|
|
31
|
-
SendModifiersContext,
|
|
32
|
-
SendStatementContext,
|
|
33
|
-
ShowStatementContext,
|
|
34
|
-
StatementContext,
|
|
35
|
-
TagOpModifiersContext,
|
|
36
|
-
} from "./generated/plurnkParser.ts";
|
|
37
|
-
import {
|
|
38
|
-
IdentSignalContext,
|
|
39
|
-
IntSignalContext,
|
|
40
|
-
LineMarkerContext,
|
|
41
|
-
PathContext,
|
|
42
|
-
TagSignalContext,
|
|
43
|
-
} from "./generated/plurnkParser.ts";
|
|
44
|
-
import PlurnkParseError from "./PlurnkParseError.ts";
|
|
45
|
-
|
|
46
|
-
// The xpath package's .d.ts omits its `parse` function; augment here.
|
|
47
|
-
declare module "xpath" {
|
|
48
|
-
export function parse(expression: string): unknown;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type Ctor<T> = new (...args: any[]) => T;
|
|
52
|
-
|
|
53
|
-
type TagSlots = { signal: string[] | null; path: ParsedPath | null; lineMarker: LineMarker | null };
|
|
54
|
-
type SendSlots = { signal: number | null; path: ParsedPath | null };
|
|
55
|
-
type ExecSlots = { signal: string | null; path: ParsedPath | null };
|
|
56
|
-
|
|
4
|
+
import { IdentSignalContext, IntSignalContext, LineMarkerContext, PathContext, TagSignalContext, } from "./generated/plurnkParser.js";
|
|
5
|
+
import PlurnkParseError from "./PlurnkParseError.js";
|
|
57
6
|
export default class AstBuilder {
|
|
58
7
|
static #SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
8
|
+
// Schemes whose `://X/Y` form actually carries an authority (host/userinfo/port).
|
|
9
|
+
// Everything else is opaque: the first segment after `://` is just the first
|
|
10
|
+
// segment of the path, not a host.
|
|
11
|
+
static #AUTHORITY_SCHEMES = new Set([
|
|
12
|
+
"http", "https",
|
|
13
|
+
"ws", "wss",
|
|
14
|
+
"ftp", "ftps",
|
|
15
|
+
"file",
|
|
16
|
+
]);
|
|
17
|
+
static build(ctx) {
|
|
18
|
+
const find = ctx.findStatement();
|
|
19
|
+
if (find)
|
|
20
|
+
return AstBuilder.#buildFind(find);
|
|
21
|
+
const read = ctx.readStatement();
|
|
22
|
+
if (read)
|
|
23
|
+
return AstBuilder.#buildRead(read);
|
|
24
|
+
const edit = ctx.editStatement();
|
|
25
|
+
if (edit)
|
|
26
|
+
return AstBuilder.#buildEdit(edit);
|
|
27
|
+
const copy = ctx.copyStatement();
|
|
28
|
+
if (copy)
|
|
29
|
+
return AstBuilder.#buildCopy(copy);
|
|
30
|
+
const move = ctx.moveStatement();
|
|
31
|
+
if (move)
|
|
32
|
+
return AstBuilder.#buildMove(move);
|
|
33
|
+
const show = ctx.showStatement();
|
|
34
|
+
if (show)
|
|
35
|
+
return AstBuilder.#buildShow(show);
|
|
36
|
+
const hide = ctx.hideStatement();
|
|
37
|
+
if (hide)
|
|
38
|
+
return AstBuilder.#buildHide(hide);
|
|
39
|
+
const send = ctx.sendStatement();
|
|
40
|
+
if (send)
|
|
41
|
+
return AstBuilder.#buildSend(send);
|
|
42
|
+
const exec = ctx.execStatement();
|
|
43
|
+
if (exec)
|
|
44
|
+
return AstBuilder.#buildExec(exec);
|
|
70
45
|
throw new Error("statement context has no recognized alternative");
|
|
71
46
|
}
|
|
72
|
-
|
|
73
|
-
static #buildFind(ctx: FindStatementContext): FindStatement {
|
|
47
|
+
static #buildFind(ctx) {
|
|
74
48
|
const position = AstBuilder.#positionOf(ctx);
|
|
75
49
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
76
50
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -82,8 +56,7 @@ export default class AstBuilder {
|
|
|
82
56
|
position,
|
|
83
57
|
};
|
|
84
58
|
}
|
|
85
|
-
|
|
86
|
-
static #buildRead(ctx: ReadStatementContext): ReadStatement {
|
|
59
|
+
static #buildRead(ctx) {
|
|
87
60
|
const position = AstBuilder.#positionOf(ctx);
|
|
88
61
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
89
62
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -95,8 +68,7 @@ export default class AstBuilder {
|
|
|
95
68
|
position,
|
|
96
69
|
};
|
|
97
70
|
}
|
|
98
|
-
|
|
99
|
-
static #buildShow(ctx: ShowStatementContext): ShowStatement {
|
|
71
|
+
static #buildShow(ctx) {
|
|
100
72
|
const position = AstBuilder.#positionOf(ctx);
|
|
101
73
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
102
74
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -108,8 +80,7 @@ export default class AstBuilder {
|
|
|
108
80
|
position,
|
|
109
81
|
};
|
|
110
82
|
}
|
|
111
|
-
|
|
112
|
-
static #buildHide(ctx: HideStatementContext): HideStatement {
|
|
83
|
+
static #buildHide(ctx) {
|
|
113
84
|
const position = AstBuilder.#positionOf(ctx);
|
|
114
85
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
115
86
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -121,8 +92,7 @@ export default class AstBuilder {
|
|
|
121
92
|
position,
|
|
122
93
|
};
|
|
123
94
|
}
|
|
124
|
-
|
|
125
|
-
static #buildEdit(ctx: EditStatementContext): EditStatement {
|
|
95
|
+
static #buildEdit(ctx) {
|
|
126
96
|
const position = AstBuilder.#positionOf(ctx);
|
|
127
97
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
128
98
|
return {
|
|
@@ -133,8 +103,7 @@ export default class AstBuilder {
|
|
|
133
103
|
position,
|
|
134
104
|
};
|
|
135
105
|
}
|
|
136
|
-
|
|
137
|
-
static #buildCopy(ctx: CopyStatementContext): CopyStatement {
|
|
106
|
+
static #buildCopy(ctx) {
|
|
138
107
|
const position = AstBuilder.#positionOf(ctx);
|
|
139
108
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
140
109
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -142,12 +111,11 @@ export default class AstBuilder {
|
|
|
142
111
|
op: "COPY",
|
|
143
112
|
suffix: AstBuilder.#splitSuffix(ctx.OPEN_COPY().getText(), "COPY"),
|
|
144
113
|
...slots,
|
|
145
|
-
body: raw !== null ? AstBuilder
|
|
114
|
+
body: raw !== null ? AstBuilder.parsePath(raw, position) : null,
|
|
146
115
|
position,
|
|
147
116
|
};
|
|
148
117
|
}
|
|
149
|
-
|
|
150
|
-
static #buildMove(ctx: MoveStatementContext): MoveStatement {
|
|
118
|
+
static #buildMove(ctx) {
|
|
151
119
|
const position = AstBuilder.#positionOf(ctx);
|
|
152
120
|
const slots = AstBuilder.#extractTagSlots(ctx.tagOpModifiers(), position);
|
|
153
121
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -155,12 +123,11 @@ export default class AstBuilder {
|
|
|
155
123
|
op: "MOVE",
|
|
156
124
|
suffix: AstBuilder.#splitSuffix(ctx.OPEN_MOVE().getText(), "MOVE"),
|
|
157
125
|
...slots,
|
|
158
|
-
body: raw !== null ? AstBuilder
|
|
126
|
+
body: raw !== null ? AstBuilder.parsePath(raw, position) : null,
|
|
159
127
|
position,
|
|
160
128
|
};
|
|
161
129
|
}
|
|
162
|
-
|
|
163
|
-
static #buildSend(ctx: SendStatementContext): SendStatement {
|
|
130
|
+
static #buildSend(ctx) {
|
|
164
131
|
const position = AstBuilder.#positionOf(ctx);
|
|
165
132
|
const slots = AstBuilder.#extractSendSlots(ctx.sendModifiers(), position);
|
|
166
133
|
const raw = AstBuilder.#bodyTextOf(ctx);
|
|
@@ -173,8 +140,7 @@ export default class AstBuilder {
|
|
|
173
140
|
position,
|
|
174
141
|
};
|
|
175
142
|
}
|
|
176
|
-
|
|
177
|
-
static #buildExec(ctx: ExecStatementContext): ExecStatement {
|
|
143
|
+
static #buildExec(ctx) {
|
|
178
144
|
const position = AstBuilder.#positionOf(ctx);
|
|
179
145
|
const slots = AstBuilder.#extractExecSlots(ctx.execModifiers(), position);
|
|
180
146
|
return {
|
|
@@ -186,16 +152,14 @@ export default class AstBuilder {
|
|
|
186
152
|
position,
|
|
187
153
|
};
|
|
188
154
|
}
|
|
189
|
-
|
|
190
|
-
static #extractTagSlots(modCtx: TagOpModifiersContext | null, pos: Position): TagSlots {
|
|
155
|
+
static #extractTagSlots(modCtx, pos) {
|
|
191
156
|
return {
|
|
192
157
|
signal: AstBuilder.#tagsFromSignal(AstBuilder.#findFirst(modCtx, TagSignalContext)),
|
|
193
158
|
path: AstBuilder.#pathFromCtx(AstBuilder.#findFirst(modCtx, PathContext), pos),
|
|
194
159
|
lineMarker: AstBuilder.#lineMarkerFromCtx(AstBuilder.#findFirst(modCtx, LineMarkerContext)),
|
|
195
160
|
};
|
|
196
161
|
}
|
|
197
|
-
|
|
198
|
-
static #extractSendSlots(modCtx: SendModifiersContext | null, pos: Position): SendSlots {
|
|
162
|
+
static #extractSendSlots(modCtx, pos) {
|
|
199
163
|
const intCtx = AstBuilder.#findFirst(modCtx, IntSignalContext);
|
|
200
164
|
const intNode = intCtx?.INT() ?? null;
|
|
201
165
|
return {
|
|
@@ -203,8 +167,7 @@ export default class AstBuilder {
|
|
|
203
167
|
path: AstBuilder.#pathFromCtx(AstBuilder.#findFirst(modCtx, PathContext), pos),
|
|
204
168
|
};
|
|
205
169
|
}
|
|
206
|
-
|
|
207
|
-
static #extractExecSlots(modCtx: ExecModifiersContext | null, pos: Position): ExecSlots {
|
|
170
|
+
static #extractExecSlots(modCtx, pos) {
|
|
208
171
|
const identCtx = AstBuilder.#findFirst(modCtx, IdentSignalContext);
|
|
209
172
|
const identNode = identCtx?.IDENT() ?? null;
|
|
210
173
|
return {
|
|
@@ -212,120 +175,167 @@ export default class AstBuilder {
|
|
|
212
175
|
path: AstBuilder.#pathFromCtx(AstBuilder.#findFirst(modCtx, PathContext), pos),
|
|
213
176
|
};
|
|
214
177
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
type
|
|
219
|
-
|
|
220
|
-
if (root === null) return null;
|
|
221
|
-
if (root instanceof type) return root;
|
|
178
|
+
static #findFirst(root, type) {
|
|
179
|
+
if (root === null)
|
|
180
|
+
return null;
|
|
181
|
+
if (root instanceof type)
|
|
182
|
+
return root;
|
|
222
183
|
const children = root.children;
|
|
223
|
-
if (!children)
|
|
184
|
+
if (!children)
|
|
185
|
+
return null;
|
|
224
186
|
for (const child of children) {
|
|
225
187
|
if (child instanceof ParserRuleContext) {
|
|
226
188
|
const found = AstBuilder.#findFirst(child, type);
|
|
227
|
-
if (found !== null)
|
|
189
|
+
if (found !== null)
|
|
190
|
+
return found;
|
|
228
191
|
}
|
|
229
192
|
}
|
|
230
193
|
return null;
|
|
231
194
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
195
|
+
static #tagsFromSignal(ctx) {
|
|
196
|
+
if (ctx === null)
|
|
197
|
+
return null;
|
|
235
198
|
const tags = ctx.TAG();
|
|
236
199
|
return Array.isArray(tags) ? tags.map((t) => t.getText()) : [];
|
|
237
200
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
201
|
+
static #pathFromCtx(ctx, pos) {
|
|
202
|
+
if (ctx === null)
|
|
203
|
+
return null;
|
|
241
204
|
const text = ctx.PATH_TEXT()?.getText() ?? "";
|
|
242
|
-
return AstBuilder
|
|
205
|
+
return AstBuilder.parsePath(text, pos);
|
|
243
206
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
207
|
+
static #lineMarkerFromCtx(ctx) {
|
|
208
|
+
if (ctx === null)
|
|
209
|
+
return null;
|
|
247
210
|
const text = ctx.L_MARKER()?.getText() ?? "";
|
|
248
211
|
return AstBuilder.#parseLineMarker(text);
|
|
249
212
|
}
|
|
250
|
-
|
|
251
|
-
static #positionOf(ctx: { start: { line: number; column: number } | null }): Position {
|
|
213
|
+
static #positionOf(ctx) {
|
|
252
214
|
const start = ctx.start;
|
|
253
215
|
return { line: start?.line ?? 0, column: start?.column ?? 0 };
|
|
254
216
|
}
|
|
255
|
-
|
|
256
|
-
static #bodyTextOf(ctx: { body(): { getText(): string } | null }): string | null {
|
|
217
|
+
static #bodyTextOf(ctx) {
|
|
257
218
|
const bodyCtx = ctx.body();
|
|
258
219
|
return bodyCtx ? bodyCtx.getText() : null;
|
|
259
220
|
}
|
|
260
|
-
|
|
261
|
-
static #splitSuffix(openTagText: string, op: PlurnkOp): string {
|
|
221
|
+
static #splitSuffix(openTagText, op) {
|
|
262
222
|
return openTagText.slice(2 + op.length);
|
|
263
223
|
}
|
|
264
|
-
|
|
265
|
-
static #isDigit(c: string | undefined): boolean {
|
|
224
|
+
static #isDigit(c) {
|
|
266
225
|
return c !== undefined && c >= "0" && c <= "9";
|
|
267
226
|
}
|
|
268
|
-
|
|
269
|
-
static #parseLineMarker(text: string): LineMarker {
|
|
227
|
+
static #parseLineMarker(text) {
|
|
270
228
|
const inner = text.slice(1, -1);
|
|
271
229
|
let i = 0;
|
|
272
|
-
if (inner[i] === "-")
|
|
273
|
-
|
|
230
|
+
if (inner[i] === "-")
|
|
231
|
+
i++;
|
|
232
|
+
while (AstBuilder.#isDigit(inner[i]))
|
|
233
|
+
i++;
|
|
274
234
|
const first = Number.parseInt(inner.slice(0, i), 10);
|
|
275
|
-
if (i >= inner.length)
|
|
235
|
+
if (i >= inner.length)
|
|
236
|
+
return { first, last: null };
|
|
276
237
|
i++;
|
|
277
238
|
const last = Number.parseInt(inner.slice(i), 10);
|
|
278
239
|
return { first, last };
|
|
279
240
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Parse a path string into a ParsedPath, mirroring how the AST visitor
|
|
243
|
+
* decomposes path slots inside HEREDOC statements. Public for consumers
|
|
244
|
+
* (RPC layers, scheme handlers) that need to honor the grammar's
|
|
245
|
+
* authority-vs-opaque cleavage without round-tripping through a fake
|
|
246
|
+
* HEREDOC. Returns null when `raw` is empty. Throws PlurnkParseError
|
|
247
|
+
* when `raw` starts with `scheme://` but WHATWG URL rejects it.
|
|
248
|
+
*/
|
|
249
|
+
static parsePath(raw, pos = { line: 0, column: 0 }) {
|
|
250
|
+
if (raw.length === 0)
|
|
251
|
+
return null;
|
|
283
252
|
if (!AstBuilder.#SCHEME_PATTERN.test(raw)) {
|
|
284
253
|
return { kind: "local", raw };
|
|
285
254
|
}
|
|
286
|
-
let url
|
|
255
|
+
let url;
|
|
287
256
|
try {
|
|
288
257
|
url = new URL(raw);
|
|
289
|
-
}
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
290
260
|
throw new PlurnkParseError(pos.line, pos.column, "visitor", `invalid URI in path: ${e?.message ?? raw}`);
|
|
291
261
|
}
|
|
292
|
-
const
|
|
293
|
-
|
|
262
|
+
const scheme = url.protocol.replace(/:$/, "");
|
|
263
|
+
const params = AstBuilder.#paramsFrom(url.searchParams);
|
|
264
|
+
const fragment = url.hash ? url.hash.slice(1) : null;
|
|
265
|
+
if (AstBuilder.#AUTHORITY_SCHEMES.has(scheme)) {
|
|
266
|
+
return {
|
|
267
|
+
kind: "url",
|
|
268
|
+
raw,
|
|
269
|
+
scheme,
|
|
270
|
+
username: url.username || null,
|
|
271
|
+
password: url.password || null,
|
|
272
|
+
hostname: url.hostname || null,
|
|
273
|
+
port: url.port ? Number.parseInt(url.port, 10) : null,
|
|
274
|
+
pathname: url.pathname,
|
|
275
|
+
params,
|
|
276
|
+
fragment,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Opaque scheme: the first segment after `://` is just the first segment
|
|
280
|
+
// of the path, not a host. Take the substring between `scheme://` and
|
|
281
|
+
// the first `?`/`#` boundary as the pathname; authority fields are null.
|
|
282
|
+
const afterScheme = raw.slice(scheme.length + 3);
|
|
283
|
+
const qIdx = afterScheme.indexOf("?");
|
|
284
|
+
const hIdx = afterScheme.indexOf("#");
|
|
285
|
+
let pathnameEnd = afterScheme.length;
|
|
286
|
+
if (qIdx >= 0 && (hIdx < 0 || qIdx < hIdx)) {
|
|
287
|
+
pathnameEnd = qIdx;
|
|
288
|
+
}
|
|
289
|
+
else if (hIdx >= 0) {
|
|
290
|
+
pathnameEnd = hIdx;
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
kind: "url",
|
|
294
|
+
raw,
|
|
295
|
+
scheme,
|
|
296
|
+
username: null,
|
|
297
|
+
password: null,
|
|
298
|
+
hostname: null,
|
|
299
|
+
port: null,
|
|
300
|
+
pathname: afterScheme.slice(0, pathnameEnd),
|
|
301
|
+
params,
|
|
302
|
+
fragment,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
static #paramsFrom(sp) {
|
|
306
|
+
const params = {};
|
|
307
|
+
for (const [key, value] of sp) {
|
|
294
308
|
const existing = params[key];
|
|
295
309
|
if (existing === undefined) {
|
|
296
310
|
params[key] = value;
|
|
297
|
-
}
|
|
311
|
+
}
|
|
312
|
+
else if (Array.isArray(existing)) {
|
|
298
313
|
existing.push(value);
|
|
299
|
-
}
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
300
316
|
params[key] = [existing, value];
|
|
301
317
|
}
|
|
302
318
|
}
|
|
303
|
-
return
|
|
304
|
-
kind: "url",
|
|
305
|
-
raw,
|
|
306
|
-
scheme: url.protocol.replace(/:$/, ""),
|
|
307
|
-
username: url.username || null,
|
|
308
|
-
password: url.password || null,
|
|
309
|
-
hostname: url.hostname || null,
|
|
310
|
-
port: url.port ? Number.parseInt(url.port, 10) : null,
|
|
311
|
-
pathname: url.pathname,
|
|
312
|
-
params,
|
|
313
|
-
fragment: url.hash ? url.hash.slice(1) : null,
|
|
314
|
-
};
|
|
319
|
+
return params;
|
|
315
320
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (body.startsWith("/"))
|
|
320
|
-
|
|
321
|
+
static #detectMatcherDialect(body) {
|
|
322
|
+
if (body.startsWith("//"))
|
|
323
|
+
return "xpath";
|
|
324
|
+
if (body.startsWith("/"))
|
|
325
|
+
return "regex";
|
|
326
|
+
if (body.startsWith("$"))
|
|
327
|
+
return "jsonpath";
|
|
321
328
|
return "glob";
|
|
322
329
|
}
|
|
323
|
-
|
|
324
|
-
static #parseRegexLiteral(body: string, pos: Position): { pattern: string; flags: string } {
|
|
330
|
+
static #parseRegexLiteral(body, pos) {
|
|
325
331
|
let i = 1;
|
|
326
332
|
while (i < body.length) {
|
|
327
|
-
if (body[i] === "\\") {
|
|
328
|
-
|
|
333
|
+
if (body[i] === "\\") {
|
|
334
|
+
i += 2;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (body[i] === "/")
|
|
338
|
+
break;
|
|
329
339
|
i++;
|
|
330
340
|
}
|
|
331
341
|
if (i >= body.length) {
|
|
@@ -333,14 +343,14 @@ export default class AstBuilder {
|
|
|
333
343
|
}
|
|
334
344
|
return { pattern: body.slice(1, i), flags: body.slice(i + 1) };
|
|
335
345
|
}
|
|
336
|
-
|
|
337
|
-
static #parseMatcherBody(body: string, pos: Position): MatcherBody {
|
|
346
|
+
static #parseMatcherBody(body, pos) {
|
|
338
347
|
const dialect = AstBuilder.#detectMatcherDialect(body);
|
|
339
348
|
if (dialect === "regex") {
|
|
340
349
|
const { pattern, flags } = AstBuilder.#parseRegexLiteral(body, pos);
|
|
341
350
|
try {
|
|
342
351
|
new RegExp(pattern, flags);
|
|
343
|
-
}
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
344
354
|
throw new PlurnkParseError(pos.line, pos.column, "visitor", `invalid regex: ${e?.message ?? body}`);
|
|
345
355
|
}
|
|
346
356
|
return { dialect: "regex", raw: body, pattern, flags };
|
|
@@ -348,7 +358,8 @@ export default class AstBuilder {
|
|
|
348
358
|
if (dialect === "xpath") {
|
|
349
359
|
try {
|
|
350
360
|
xpath.parse(body);
|
|
351
|
-
}
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
352
363
|
throw new PlurnkParseError(pos.line, pos.column, "visitor", `invalid xpath: ${e?.message ?? body}`);
|
|
353
364
|
}
|
|
354
365
|
return { dialect: "xpath", raw: body };
|
|
@@ -356,17 +367,21 @@ export default class AstBuilder {
|
|
|
356
367
|
if (dialect === "jsonpath") {
|
|
357
368
|
try {
|
|
358
369
|
JSONPath({ path: body, json: {} });
|
|
359
|
-
}
|
|
370
|
+
}
|
|
371
|
+
catch (e) {
|
|
360
372
|
throw new PlurnkParseError(pos.line, pos.column, "visitor", `invalid jsonpath: ${e?.message ?? body}`);
|
|
361
373
|
}
|
|
362
374
|
return { dialect: "jsonpath", raw: body };
|
|
363
375
|
}
|
|
364
376
|
return { dialect: "glob", raw: body };
|
|
365
377
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
378
|
+
static #parseSendBody(raw) {
|
|
379
|
+
let json = null;
|
|
380
|
+
try {
|
|
381
|
+
json = JSON.parse(raw);
|
|
382
|
+
}
|
|
383
|
+
catch { /* best-effort */ }
|
|
370
384
|
return { raw, json };
|
|
371
385
|
}
|
|
372
386
|
}
|
|
387
|
+
//# sourceMappingURL=AstBuilder.js.map
|