@naiv/swan-dsl 0.0.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/dist/src/index.d.ts +25 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +48 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lexer.d.ts +6 -0
- package/dist/src/lexer.d.ts.map +1 -0
- package/dist/src/lexer.js +68 -0
- package/dist/src/lexer.js.map +1 -0
- package/dist/src/parser.d.ts +57 -0
- package/dist/src/parser.d.ts.map +1 -0
- package/dist/src/parser.js +466 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/semantic.d.ts +7 -0
- package/dist/src/semantic.d.ts.map +1 -0
- package/dist/src/semantic.js +269 -0
- package/dist/src/semantic.js.map +1 -0
- package/dist/src/types.d.ts +160 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +31 -0
- package/dist/src/types.js.map +1 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +47 -0
- package/dist/test.js.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ParseOptions, Program } from "./types";
|
|
2
|
+
export type { ParseOptions, Program } from "./types";
|
|
3
|
+
export type { AppDecl, BinaryExpr, BinaryOperator, BooleanLiteral, ButtonStmt, ClickStmt, ComponentDecl, ConditionalStmt, Expression, FieldStmt, HandlerStmt, HeaderStmt, IdentifierExpr, InputStmt, LinkStmt, Literal, MemberExpr, NavTarget, NumberLiteral, OutcomeClause, PageDecl, Position, SemanticRuleCode, Statement, StringLiteral, SubmitStmt, TextStmt, UnaryExpr, UseStmt, } from "./types.js";
|
|
4
|
+
export { ParseError, SemanticError } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Parse and semantically validate a SWAN DSL source string.
|
|
7
|
+
*
|
|
8
|
+
* @param source - The DSL program text
|
|
9
|
+
* @param options - Optional: `{ strictMode: true }` enables SR-4 orphan page check
|
|
10
|
+
* @returns A fully-typed `Program` AST
|
|
11
|
+
* @throws {ParseError} on syntax errors
|
|
12
|
+
* @throws {SemanticError} on semantic violations (SR-1 through SR-6)
|
|
13
|
+
*/
|
|
14
|
+
export declare function parse(source: string, options?: ParseOptions): Program;
|
|
15
|
+
/**
|
|
16
|
+
* Read a SWAN DSL file from disk, then parse and semantically validate it.
|
|
17
|
+
*
|
|
18
|
+
* @param filePath - Absolute or relative path to the `.swan` / `.dsl` file
|
|
19
|
+
* @param options - Optional: `{ strictMode: true }` enables SR-4 orphan page check
|
|
20
|
+
* @returns A fully-typed `Program` AST
|
|
21
|
+
* @throws {ParseError} on syntax errors
|
|
22
|
+
* @throws {SemanticError} on semantic violations (SR-1 through SR-6)
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseFile(filePath: string, options?: ParseOptions): Promise<Program>;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAErD,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACrD,YAAY,EACR,OAAO,EACP,UAAU,EACV,cAAc,EACd,cAAc,EACd,UAAU,EACV,SAAS,EACT,aAAa,EACb,eAAe,EACf,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,cAAc,EACd,SAAS,EACT,QAAQ,EACR,OAAO,EACP,UAAU,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,UAAU,EACV,QAAQ,EACR,SAAS,EACT,OAAO,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMvD;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAIzE;AAMD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,YAAiB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAGlB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// SWAN DSL — Public API
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SemanticError = exports.ParseError = void 0;
|
|
7
|
+
exports.parse = parse;
|
|
8
|
+
exports.parseFile = parseFile;
|
|
9
|
+
const promises_1 = require("node:fs/promises");
|
|
10
|
+
const parser_1 = require("./parser");
|
|
11
|
+
const semantic_1 = require("./semantic");
|
|
12
|
+
var types_js_1 = require("./types.js");
|
|
13
|
+
Object.defineProperty(exports, "ParseError", { enumerable: true, get: function () { return types_js_1.ParseError; } });
|
|
14
|
+
Object.defineProperty(exports, "SemanticError", { enumerable: true, get: function () { return types_js_1.SemanticError; } });
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// parse — from a DSL source string
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Parse and semantically validate a SWAN DSL source string.
|
|
20
|
+
*
|
|
21
|
+
* @param source - The DSL program text
|
|
22
|
+
* @param options - Optional: `{ strictMode: true }` enables SR-4 orphan page check
|
|
23
|
+
* @returns A fully-typed `Program` AST
|
|
24
|
+
* @throws {ParseError} on syntax errors
|
|
25
|
+
* @throws {SemanticError} on semantic violations (SR-1 through SR-6)
|
|
26
|
+
*/
|
|
27
|
+
function parse(source, options = {}) {
|
|
28
|
+
const program = (0, parser_1.parse)(source);
|
|
29
|
+
(0, semantic_1.checkSemantics)(program, { strictMode: options.strictMode });
|
|
30
|
+
return program;
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// parseFile — from a file path
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Read a SWAN DSL file from disk, then parse and semantically validate it.
|
|
37
|
+
*
|
|
38
|
+
* @param filePath - Absolute or relative path to the `.swan` / `.dsl` file
|
|
39
|
+
* @param options - Optional: `{ strictMode: true }` enables SR-4 orphan page check
|
|
40
|
+
* @returns A fully-typed `Program` AST
|
|
41
|
+
* @throws {ParseError} on syntax errors
|
|
42
|
+
* @throws {SemanticError} on semantic violations (SR-1 through SR-6)
|
|
43
|
+
*/
|
|
44
|
+
async function parseFile(filePath, options = {}) {
|
|
45
|
+
const source = await (0, promises_1.readFile)(filePath, "utf-8");
|
|
46
|
+
return parse(source, options);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,wBAAwB;AACxB,gFAAgF;;;AAsDhF,sBAIC;AAeD,8BAMC;AA7ED,+CAA4C;AAC5C,qCAAgD;AAChD,yCAA4C;AAmC5C,uCAAuD;AAA9C,sGAAA,UAAU,OAAA;AAAE,yGAAA,aAAa,OAAA;AAElC,8EAA8E;AAC9E,mCAAmC;AACnC,8EAA8E;AAE9E;;;;;;;;GAQG;AACH,SAAgB,KAAK,CAAC,MAAc,EAAE,UAAwB,EAAE;IAC5D,MAAM,OAAO,GAAG,IAAA,cAAW,EAAC,MAAM,CAAC,CAAC;IACpC,IAAA,yBAAc,EAAC,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5D,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AAE9E;;;;;;;;GAQG;AACI,KAAK,UAAU,SAAS,CAC3B,QAAgB,EAChB,UAAwB,EAAE;IAE1B,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAQ,EAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import moo from "moo";
|
|
2
|
+
export declare const KEYWORDS: readonly ["app", "page", "component", "entry", "use", "header", "text", "button", "link", "field", "input", "submit", "click", "on", "if", "true", "false"];
|
|
3
|
+
export type Keyword = (typeof KEYWORDS)[number];
|
|
4
|
+
export type TokenType = Keyword | "IDENT" | "STRING" | "NUMBER" | "ARROW" | "LBRACE" | "RBRACE" | "DOT" | "EQ" | "NEQ" | "LTE" | "GTE" | "LT" | "GT" | "AND" | "OR" | "BANG" | "NL" | "WS" | "COMMENT";
|
|
5
|
+
export declare const lexer: moo.Lexer;
|
|
6
|
+
//# sourceMappingURL=lexer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/lexer.ts"],"names":[],"mappings":"AAIA,OAAO,GAAG,MAAM,KAAK,CAAC;AAItB,eAAO,MAAM,QAAQ,6JAkBX,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,MAAM,MAAM,SAAS,GACf,OAAO,GACP,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,KAAK,GACL,IAAI,GACJ,KAAK,GACL,KAAK,GACL,KAAK,GACL,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,MAAM,GACN,IAAI,GACJ,IAAI,GACJ,SAAS,CAAC;AAMhB,eAAO,MAAM,KAAK,WAoChB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
// SWAN DSL — Moo Lexer
|
|
4
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.lexer = exports.KEYWORDS = void 0;
|
|
10
|
+
const moo_1 = __importDefault(require("moo"));
|
|
11
|
+
// All DSL keywords — must come before the general IDENT rule so moo
|
|
12
|
+
// matches them by their longest-match keyword priority.
|
|
13
|
+
exports.KEYWORDS = [
|
|
14
|
+
"app",
|
|
15
|
+
"page",
|
|
16
|
+
"component",
|
|
17
|
+
"entry",
|
|
18
|
+
"use",
|
|
19
|
+
"header",
|
|
20
|
+
"text",
|
|
21
|
+
"button",
|
|
22
|
+
"link",
|
|
23
|
+
"field",
|
|
24
|
+
"input",
|
|
25
|
+
"submit",
|
|
26
|
+
"click",
|
|
27
|
+
"on",
|
|
28
|
+
"if",
|
|
29
|
+
"true",
|
|
30
|
+
"false",
|
|
31
|
+
];
|
|
32
|
+
// Build the moo lexer rules.
|
|
33
|
+
// moo uses longest-match semantics and processes rules top-to-bottom
|
|
34
|
+
// within equal-length matches, so operators with shared prefixes must
|
|
35
|
+
// be listed longest-first (e.g. "<=" before "<").
|
|
36
|
+
exports.lexer = moo_1.default.compile({
|
|
37
|
+
WS: { match: /[ \t]+/, lineBreaks: false },
|
|
38
|
+
NL: { match: /\r?\n/, lineBreaks: true },
|
|
39
|
+
COMMENT: /\/\/[^\n]*/,
|
|
40
|
+
// Multi-char operators (longest first to avoid prefix collisions)
|
|
41
|
+
ARROW: "->",
|
|
42
|
+
EQ: "==",
|
|
43
|
+
NEQ: "!=",
|
|
44
|
+
LTE: "<=",
|
|
45
|
+
GTE: ">=",
|
|
46
|
+
AND: "&&",
|
|
47
|
+
OR: "||",
|
|
48
|
+
// Single-char operators / punctuation
|
|
49
|
+
LT: "<",
|
|
50
|
+
GT: ">",
|
|
51
|
+
BANG: "!",
|
|
52
|
+
LBRACE: "{",
|
|
53
|
+
RBRACE: "}",
|
|
54
|
+
DOT: ".",
|
|
55
|
+
// Literals
|
|
56
|
+
STRING: { match: /"[^"]*"/, value: (s) => s.slice(1, -1) },
|
|
57
|
+
NUMBER: {
|
|
58
|
+
match: /[0-9]+(?:\.[0-9]+)?/,
|
|
59
|
+
value: (s) => s,
|
|
60
|
+
},
|
|
61
|
+
// Keywords and identifiers — moo.keywords maps specific values
|
|
62
|
+
// of the IDENT rule to their keyword token type automatically.
|
|
63
|
+
IDENT: {
|
|
64
|
+
match: /[A-Za-z][A-Za-z0-9_]*/,
|
|
65
|
+
type: moo_1.default.keywords(Object.fromEntries(exports.KEYWORDS.map((k) => [k, k]))),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=lexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.js","sourceRoot":"","sources":["../../src/lexer.ts"],"names":[],"mappings":";AAAA,gFAAgF;AAChF,uBAAuB;AACvB,gFAAgF;;;;;;AAEhF,8CAAsB;AAEtB,oEAAoE;AACpE,wDAAwD;AAC3C,QAAA,QAAQ,GAAG;IACpB,KAAK;IACL,MAAM;IACN,WAAW;IACX,OAAO;IACP,KAAK;IACL,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,OAAO;IACP,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,OAAO;CACD,CAAC;AA0BX,6BAA6B;AAC7B,qEAAqE;AACrE,sEAAsE;AACtE,kDAAkD;AACrC,QAAA,KAAK,GAAG,aAAG,CAAC,OAAO,CAAC;IAC7B,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE;IAC1C,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE;IACxC,OAAO,EAAE,YAAY;IACrB,kEAAkE;IAClE,KAAK,EAAE,IAAI;IACX,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,EAAE,EAAE,IAAI;IACR,sCAAsC;IACtC,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,GAAG;IACX,GAAG,EAAE,GAAG;IACR,WAAW;IACX,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;IAC1D,MAAM,EAAE;QACJ,KAAK,EAAE,qBAAqB;QAC5B,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;KAClB;IACD,+DAA+D;IAC/D,+DAA+D;IAC/D,KAAK,EAAE;QACH,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE,aAAG,CAAC,QAAQ,CACd,MAAM,CAAC,WAAW,CAAC,gBAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAG7C,CACJ;KACJ;CACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Expression, type Program } from "./types";
|
|
2
|
+
export declare class Parser {
|
|
3
|
+
private tokens;
|
|
4
|
+
private pos;
|
|
5
|
+
constructor(input: string);
|
|
6
|
+
private peek;
|
|
7
|
+
private advance;
|
|
8
|
+
private isAtEnd;
|
|
9
|
+
private tokenPos;
|
|
10
|
+
private currentPos;
|
|
11
|
+
/**
|
|
12
|
+
* Expect a token of the given type (or value) and advance.
|
|
13
|
+
* Throws ParseError if the next token doesn't match.
|
|
14
|
+
*/
|
|
15
|
+
private expect;
|
|
16
|
+
/**
|
|
17
|
+
* Consume a token only if it matches the given type/value.
|
|
18
|
+
*/
|
|
19
|
+
private match;
|
|
20
|
+
private check;
|
|
21
|
+
parseProgram(): Program;
|
|
22
|
+
private parseAppDecl;
|
|
23
|
+
private parsePageDecl;
|
|
24
|
+
private parseComponentDecl;
|
|
25
|
+
private parseBlock;
|
|
26
|
+
private parseStatement;
|
|
27
|
+
private parseHeaderStmt;
|
|
28
|
+
private parseTextStmt;
|
|
29
|
+
private parseButtonStmt;
|
|
30
|
+
private parseLinkStmt;
|
|
31
|
+
private parseFieldStmt;
|
|
32
|
+
private parseInputStmt;
|
|
33
|
+
private parseUseStmt;
|
|
34
|
+
private parseSubmitStmt;
|
|
35
|
+
private parseClickStmt;
|
|
36
|
+
private parseHandlerStmt;
|
|
37
|
+
private parseOutcomeClause;
|
|
38
|
+
private parseConditionalStmt;
|
|
39
|
+
private parseNavTarget;
|
|
40
|
+
parseExpression(): Expression;
|
|
41
|
+
private parseOr;
|
|
42
|
+
private parseAnd;
|
|
43
|
+
private readonly COMPARISON_OPS;
|
|
44
|
+
private parseComparison;
|
|
45
|
+
private parseUnary;
|
|
46
|
+
private parsePrimary;
|
|
47
|
+
/**
|
|
48
|
+
* Expect an IDENT token (not a keyword).
|
|
49
|
+
* Moo assigns keyword token types to matched words, so we need to accept
|
|
50
|
+
* both generic IDENT tokens and keyword tokens when used as identifiers
|
|
51
|
+
* (e.g. outcome labels like "success", "error").
|
|
52
|
+
*/
|
|
53
|
+
private expectIdent;
|
|
54
|
+
private expectString;
|
|
55
|
+
}
|
|
56
|
+
export declare function parse(source: string): Program;
|
|
57
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAMA,OAAO,EAUH,KAAK,UAAU,EAaf,KAAK,OAAO,EAOf,MAAM,SAAS,CAAC;AASjB,qBAAa,MAAM;IACf,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,GAAG,CAAK;gBAEJ,KAAK,EAAE,MAAM;IAWzB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAKlB;;;OAGG;IACH,OAAO,CAAC,MAAM;IAiBd;;OAEG;IACH,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,KAAK;IAQb,YAAY,IAAI,OAAO;IAwBvB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,cAAc;IA+BtB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,gBAAgB;IAoBxB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,cAAc;IActB,eAAe,IAAI,UAAU;IAI7B,OAAO,CAAC,OAAO;IAiBf,OAAO,CAAC,QAAQ;IAiBhB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAO5B;IAEH,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,YAAY;IAwEpB;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IA+BnB,OAAO,CAAC,YAAY;CASvB;AAMD,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG7C"}
|