@forgerapi/parser 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/analyzer.d.ts +5 -0
- package/dist/analyzer.js +108 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +11 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/lexer.d.ts +2 -0
- package/dist/lexer.js +86 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +314 -0
- package/dist/tokenStream.d.ts +9 -0
- package/dist/tokenStream.js +31 -0
- package/dist/tokens.d.ts +7 -0
- package/dist/tokens.js +2 -0
- package/package.json +24 -0
package/dist/analyzer.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForgerSemanticError = void 0;
|
|
4
|
+
exports.analyze = analyze;
|
|
5
|
+
class ForgerSemanticError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(`[Semantic Error] ${message}`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ForgerSemanticError = ForgerSemanticError;
|
|
11
|
+
function analyze(schema) {
|
|
12
|
+
const allNames = new Set();
|
|
13
|
+
for (const enm of schema.enums) {
|
|
14
|
+
if (allNames.has(enm.name)) {
|
|
15
|
+
throw new ForgerSemanticError(`Duplicate enum name: ${enm.name}`);
|
|
16
|
+
}
|
|
17
|
+
if (enm.values.length === 0) {
|
|
18
|
+
throw new ForgerSemanticError(`Enum ${enm.name} has no values`);
|
|
19
|
+
}
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
for (const v of enm.values) {
|
|
22
|
+
if (seen.has(v)) {
|
|
23
|
+
throw new ForgerSemanticError(`Duplicate value ${v} in enum ${enm.name}`);
|
|
24
|
+
}
|
|
25
|
+
seen.add(v);
|
|
26
|
+
}
|
|
27
|
+
allNames.add(enm.name);
|
|
28
|
+
}
|
|
29
|
+
for (const model of schema.models) {
|
|
30
|
+
if (allNames.has(model.name)) {
|
|
31
|
+
throw new ForgerSemanticError(`Duplicate model name: ${model.name}`);
|
|
32
|
+
}
|
|
33
|
+
allNames.add(model.name);
|
|
34
|
+
}
|
|
35
|
+
for (const model of schema.models) {
|
|
36
|
+
const fieldNames = new Set();
|
|
37
|
+
for (const field of model.fields) {
|
|
38
|
+
if (fieldNames.has(field.name)) {
|
|
39
|
+
throw new ForgerSemanticError(`Duplicate field '${field.name}' in model ${model.name}`);
|
|
40
|
+
}
|
|
41
|
+
fieldNames.add(field.name);
|
|
42
|
+
const rel = field.relation;
|
|
43
|
+
if (rel && !allNames.has(rel.target)) {
|
|
44
|
+
throw new ForgerSemanticError(`Model ${model.name}: relation target '${rel.target}' not found`);
|
|
45
|
+
}
|
|
46
|
+
if (rel?.field && !fieldNames.has(rel.field)) {
|
|
47
|
+
throw new ForgerSemanticError(`Model ${model.name}: relation field '${rel.field}' not found in model`);
|
|
48
|
+
}
|
|
49
|
+
const hasId = field.constraints.some((c) => c.type === "id");
|
|
50
|
+
if (hasId && field.type !== "uuid") {
|
|
51
|
+
throw new ForgerSemanticError(`Model ${model.name}: @id field '${field.name}' must be uuid type`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const action of schema.actions) {
|
|
56
|
+
if (action.emits) {
|
|
57
|
+
for (const eventName of action.emits) {
|
|
58
|
+
const hasEvent = schema.events.some((e) => e.name === eventName);
|
|
59
|
+
if (!hasEvent) {
|
|
60
|
+
schema.events.push({ name: eventName });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const guard of schema.guards) {
|
|
66
|
+
if (!guard.import) {
|
|
67
|
+
throw new ForgerSemanticError(`Guard ${guard.name} has no import — custom guard stubs not yet supported`);
|
|
68
|
+
}
|
|
69
|
+
for (const g of schema.guards) {
|
|
70
|
+
if (g.name === guard.name && g !== guard) {
|
|
71
|
+
throw new ForgerSemanticError(`Duplicate guard name: ${guard.name}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const guardNames = new Set(schema.guards.map((g) => g.name));
|
|
75
|
+
if (guardNames.size !== schema.guards.length) {
|
|
76
|
+
// already handled above
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const modelNames = new Set(schema.models.map((m) => m.name));
|
|
80
|
+
const guardNames = new Set(schema.guards.map((g) => g.name));
|
|
81
|
+
for (const mod of schema.modules) {
|
|
82
|
+
for (const mName of mod.models) {
|
|
83
|
+
if (!modelNames.has(mName)) {
|
|
84
|
+
throw new ForgerSemanticError(`Module ${mod.name}: model '${mName}' not found`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const gName of mod.guards) {
|
|
88
|
+
if (!guardNames.has(gName)) {
|
|
89
|
+
throw new ForgerSemanticError(`Module ${mod.name}: guard '${gName}' not found`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const actionNames = new Set();
|
|
93
|
+
for (const action of mod.actions) {
|
|
94
|
+
if (actionNames.has(action.name)) {
|
|
95
|
+
throw new ForgerSemanticError(`Module ${mod.name}: duplicate action name '${action.name}'`);
|
|
96
|
+
}
|
|
97
|
+
actionNames.add(action.name);
|
|
98
|
+
if (action.emits) {
|
|
99
|
+
for (const eventName of action.emits) {
|
|
100
|
+
const hasEvent = schema.events.some((e) => e.name === eventName);
|
|
101
|
+
if (!hasEvent) {
|
|
102
|
+
schema.events.push({ name: eventName });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForgerError = void 0;
|
|
4
|
+
class ForgerError extends Error {
|
|
5
|
+
constructor(message, line, column) {
|
|
6
|
+
super(`[ForgerError ${line}:${column}] ${message}`);
|
|
7
|
+
this.line = line;
|
|
8
|
+
this.column = column;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.ForgerError = ForgerError;
|
package/dist/fs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseFile(path: string): import("@forgerapi/ast").ForgerSchema;
|
package/dist/fs.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseFile = parseFile;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const analyzer_1 = require("./analyzer");
|
|
6
|
+
const parser_1 = require("./parser");
|
|
7
|
+
function parseFile(path) {
|
|
8
|
+
const source = (0, node_fs_1.readFileSync)(path, "utf-8");
|
|
9
|
+
const schema = (0, parser_1.parse)(source);
|
|
10
|
+
(0, analyzer_1.analyze)(schema);
|
|
11
|
+
return schema;
|
|
12
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./analyzer"), exports);
|
|
18
|
+
__exportStar(require("./fs"), exports);
|
|
19
|
+
__exportStar(require("./lexer"), exports);
|
|
20
|
+
__exportStar(require("./parser"), exports);
|
package/dist/lexer.d.ts
ADDED
package/dist/lexer.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tokenize = tokenize;
|
|
4
|
+
const KEYWORDS = new Set([
|
|
5
|
+
"model",
|
|
6
|
+
"enum",
|
|
7
|
+
"action",
|
|
8
|
+
"guard",
|
|
9
|
+
"module",
|
|
10
|
+
"input",
|
|
11
|
+
"steps",
|
|
12
|
+
"emits",
|
|
13
|
+
"route",
|
|
14
|
+
"method",
|
|
15
|
+
"import",
|
|
16
|
+
"guards",
|
|
17
|
+
"models",
|
|
18
|
+
]);
|
|
19
|
+
const TYPES = new Set(["string", "number", "boolean", "uuid", "datetime"]);
|
|
20
|
+
const SYMBOLS = new Set(["{", "}", ":", "(", ")", "[", "]", ","]);
|
|
21
|
+
function tokenize(input) {
|
|
22
|
+
const tokens = [];
|
|
23
|
+
const lines = input.split("\n");
|
|
24
|
+
for (let line = 0; line < lines.length; line++) {
|
|
25
|
+
const raw = lines[line];
|
|
26
|
+
let i = 0;
|
|
27
|
+
while (i < raw.length) {
|
|
28
|
+
if (raw[i] === "/" && i + 1 < raw.length && raw[i + 1] === "/") {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (raw[i] === '"') {
|
|
32
|
+
const close = raw.indexOf('"', i + 1);
|
|
33
|
+
const end = close === -1 ? raw.length : close + 1;
|
|
34
|
+
const value = raw.slice(i, end);
|
|
35
|
+
tokens.push({
|
|
36
|
+
type: classify(value),
|
|
37
|
+
value,
|
|
38
|
+
line: line + 1,
|
|
39
|
+
column: i + 1,
|
|
40
|
+
});
|
|
41
|
+
i = end;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (SYMBOLS.has(raw[i])) {
|
|
45
|
+
tokens.push({
|
|
46
|
+
type: "symbol",
|
|
47
|
+
value: raw[i],
|
|
48
|
+
line: line + 1,
|
|
49
|
+
column: i + 1,
|
|
50
|
+
});
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (raw[i] === " " || raw[i] === "\t" || raw[i] === "\r") {
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
let end = i + 1;
|
|
59
|
+
while (end < raw.length &&
|
|
60
|
+
!(SYMBOLS.has(raw[end]) || raw[end] === " " || raw[end] === "\t" || raw[end] === "\r")) {
|
|
61
|
+
end++;
|
|
62
|
+
}
|
|
63
|
+
const value = raw.slice(i, end);
|
|
64
|
+
tokens.push({
|
|
65
|
+
type: classify(value),
|
|
66
|
+
value,
|
|
67
|
+
line: line + 1,
|
|
68
|
+
column: i + 1,
|
|
69
|
+
});
|
|
70
|
+
i = end;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
tokens.push({ type: "eof", value: "", line: lines.length + 1, column: 0 });
|
|
74
|
+
return tokens;
|
|
75
|
+
}
|
|
76
|
+
function classify(word) {
|
|
77
|
+
if (word.startsWith("@"))
|
|
78
|
+
return "annotation";
|
|
79
|
+
if (KEYWORDS.has(word))
|
|
80
|
+
return "keyword";
|
|
81
|
+
if (TYPES.has(word))
|
|
82
|
+
return "type";
|
|
83
|
+
if (SYMBOLS.has(word))
|
|
84
|
+
return "symbol";
|
|
85
|
+
return "identifier";
|
|
86
|
+
}
|
package/dist/parser.d.ts
ADDED
package/dist/parser.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parse = parse;
|
|
4
|
+
const lexer_1 = require("./lexer");
|
|
5
|
+
function parse(input) {
|
|
6
|
+
const tokens = (0, lexer_1.tokenize)(input);
|
|
7
|
+
let pos = 0;
|
|
8
|
+
function peek(offset = 0) {
|
|
9
|
+
return tokens[pos + offset] ?? null;
|
|
10
|
+
}
|
|
11
|
+
function next() {
|
|
12
|
+
const token = tokens[pos++];
|
|
13
|
+
if (!token)
|
|
14
|
+
throw new Error("Unexpected end of input");
|
|
15
|
+
return token;
|
|
16
|
+
}
|
|
17
|
+
function expect(type, value) {
|
|
18
|
+
const token = next();
|
|
19
|
+
if (token.type !== type) {
|
|
20
|
+
throw new Error(`Expected ${type}, got ${token.type} (${token.value}) at ${token.line}:${token.column}`);
|
|
21
|
+
}
|
|
22
|
+
if (value && token.value !== value) {
|
|
23
|
+
throw new Error(`Expected '${value}', got '${token.value}' at ${token.line}:${token.column}`);
|
|
24
|
+
}
|
|
25
|
+
return token;
|
|
26
|
+
}
|
|
27
|
+
function parseSchema() {
|
|
28
|
+
const enums = [];
|
|
29
|
+
const models = [];
|
|
30
|
+
const actions = [];
|
|
31
|
+
const events = [];
|
|
32
|
+
const guards = [];
|
|
33
|
+
const modules = [];
|
|
34
|
+
while (peek() && peek()?.type !== "eof") {
|
|
35
|
+
const kw = peek();
|
|
36
|
+
if (!kw)
|
|
37
|
+
break;
|
|
38
|
+
if (kw.type === "keyword" && kw.value === "enum") {
|
|
39
|
+
enums.push(parseEnum());
|
|
40
|
+
}
|
|
41
|
+
else if (kw.type === "keyword" && kw.value === "model") {
|
|
42
|
+
models.push(parseModel());
|
|
43
|
+
}
|
|
44
|
+
else if (kw.type === "keyword" && kw.value === "action") {
|
|
45
|
+
actions.push(parseAction());
|
|
46
|
+
}
|
|
47
|
+
else if (kw.type === "keyword" && kw.value === "guard") {
|
|
48
|
+
guards.push(parseGuard());
|
|
49
|
+
}
|
|
50
|
+
else if (kw.type === "keyword" && kw.value === "module") {
|
|
51
|
+
modules.push(parseModule());
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
next();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { enums, models, actions, events, guards, modules };
|
|
58
|
+
}
|
|
59
|
+
function parseEnum() {
|
|
60
|
+
expect("keyword", "enum");
|
|
61
|
+
const name = expect("identifier").value;
|
|
62
|
+
expect("symbol", "{");
|
|
63
|
+
const values = [];
|
|
64
|
+
while (peek() && !(peek()?.type === "symbol" && peek()?.value === "}")) {
|
|
65
|
+
const token = next();
|
|
66
|
+
if (token.type !== "identifier") {
|
|
67
|
+
throw new Error(`Expected identifier, got ${token.type} at ${token.line}:${token.column}`);
|
|
68
|
+
}
|
|
69
|
+
values.push(token.value);
|
|
70
|
+
}
|
|
71
|
+
expect("symbol", "}");
|
|
72
|
+
return { name, values };
|
|
73
|
+
}
|
|
74
|
+
function parseModel() {
|
|
75
|
+
expect("keyword", "model");
|
|
76
|
+
const name = expect("identifier").value;
|
|
77
|
+
expect("symbol", "{");
|
|
78
|
+
const fields = [];
|
|
79
|
+
while (peek() && !(peek()?.type === "symbol" && peek()?.value === "}")) {
|
|
80
|
+
fields.push(parseField());
|
|
81
|
+
}
|
|
82
|
+
expect("symbol", "}");
|
|
83
|
+
return { name, fields };
|
|
84
|
+
}
|
|
85
|
+
function parseField() {
|
|
86
|
+
const fieldName = expect("identifier").value;
|
|
87
|
+
expect("symbol", ":");
|
|
88
|
+
const { type, relation } = parseFieldType();
|
|
89
|
+
const constraints = parseConstraints();
|
|
90
|
+
const relField = parseRelationField();
|
|
91
|
+
const result = {
|
|
92
|
+
name: fieldName,
|
|
93
|
+
type,
|
|
94
|
+
constraints,
|
|
95
|
+
relation,
|
|
96
|
+
};
|
|
97
|
+
if (relField && result.relation) {
|
|
98
|
+
result.relation.field = relField;
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function parseFieldType() {
|
|
103
|
+
const token = next();
|
|
104
|
+
if (token.type === "type") {
|
|
105
|
+
return { type: token.value };
|
|
106
|
+
}
|
|
107
|
+
if (token.type === "identifier") {
|
|
108
|
+
const isArray = peek()?.type === "symbol" && peek()?.value === "[";
|
|
109
|
+
if (isArray) {
|
|
110
|
+
next();
|
|
111
|
+
expect("symbol", "]");
|
|
112
|
+
return {
|
|
113
|
+
type: "uuid",
|
|
114
|
+
relation: { target: token.value, isArray: true },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
type: token.value,
|
|
119
|
+
relation: { target: token.value, isArray: false },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
throw new Error(`Expected type or identifier, got ${token.type} at ${token.line}:${token.column}`);
|
|
123
|
+
}
|
|
124
|
+
function parseConstraints() {
|
|
125
|
+
const constraints = [];
|
|
126
|
+
while (peek()?.type === "annotation" &&
|
|
127
|
+
!isRelationAnnotation(peek().value)) {
|
|
128
|
+
const annotation = next();
|
|
129
|
+
const name = annotation.value.slice(1);
|
|
130
|
+
if (name === "id") {
|
|
131
|
+
constraints.push({ type: "id" });
|
|
132
|
+
}
|
|
133
|
+
else if (name === "unique") {
|
|
134
|
+
constraints.push({ type: "unique" });
|
|
135
|
+
}
|
|
136
|
+
else if (name === "default") {
|
|
137
|
+
expect("symbol", "(");
|
|
138
|
+
const valueToken = next();
|
|
139
|
+
expect("symbol", ")");
|
|
140
|
+
constraints.push({ type: "default", value: valueToken.value });
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
throw new Error(`Unknown annotation @${name} at ${annotation.line}:${annotation.column}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return constraints;
|
|
147
|
+
}
|
|
148
|
+
function parseRelationField() {
|
|
149
|
+
if (peek()?.type === "annotation" && isRelationAnnotation(peek().value)) {
|
|
150
|
+
next();
|
|
151
|
+
expect("symbol", "(");
|
|
152
|
+
const fieldValue = expect("identifier").value;
|
|
153
|
+
expect("symbol", ")");
|
|
154
|
+
return fieldValue;
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
function isRelationAnnotation(value) {
|
|
159
|
+
return value === "@relation";
|
|
160
|
+
}
|
|
161
|
+
function parseAction() {
|
|
162
|
+
expect("keyword", "action");
|
|
163
|
+
const name = expect("identifier").value;
|
|
164
|
+
expect("symbol", "{");
|
|
165
|
+
let inputs = [];
|
|
166
|
+
let steps = [];
|
|
167
|
+
let emits = [];
|
|
168
|
+
let route;
|
|
169
|
+
let method;
|
|
170
|
+
while (peek() && !(peek()?.type === "symbol" && peek()?.value === "}")) {
|
|
171
|
+
const kw = peek();
|
|
172
|
+
if (!kw)
|
|
173
|
+
break;
|
|
174
|
+
if (kw.type === "keyword" && kw.value === "route") {
|
|
175
|
+
next();
|
|
176
|
+
route = parseStringValue();
|
|
177
|
+
}
|
|
178
|
+
else if (kw.type === "keyword" && kw.value === "method") {
|
|
179
|
+
next();
|
|
180
|
+
expect("symbol", ":");
|
|
181
|
+
method = expect("identifier").value;
|
|
182
|
+
}
|
|
183
|
+
else if (kw.type === "keyword" && kw.value === "input") {
|
|
184
|
+
next();
|
|
185
|
+
inputs = parseActionInputs();
|
|
186
|
+
}
|
|
187
|
+
else if (kw.type === "keyword" && kw.value === "steps") {
|
|
188
|
+
next();
|
|
189
|
+
steps = parseBlock();
|
|
190
|
+
}
|
|
191
|
+
else if (kw.type === "keyword" && kw.value === "emits") {
|
|
192
|
+
next();
|
|
193
|
+
emits = parseBlock();
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
next();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
expect("symbol", "}");
|
|
200
|
+
const result = { name, inputs, steps };
|
|
201
|
+
if (emits.length > 0)
|
|
202
|
+
result.emits = emits;
|
|
203
|
+
if (route !== undefined)
|
|
204
|
+
result.route = route;
|
|
205
|
+
if (method !== undefined)
|
|
206
|
+
result.method = method;
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
function parseActionInputs() {
|
|
210
|
+
expect("symbol", ":");
|
|
211
|
+
if (peek()?.type === "symbol" && peek()?.value === "[") {
|
|
212
|
+
next();
|
|
213
|
+
const inputs = [];
|
|
214
|
+
while (peek() &&
|
|
215
|
+
!(peek()?.type === "symbol" && peek()?.value === "]")) {
|
|
216
|
+
const name = expect("identifier").value;
|
|
217
|
+
expect("symbol", ":");
|
|
218
|
+
const typeToken = next();
|
|
219
|
+
if (typeToken.type !== "type" && typeToken.type !== "identifier") {
|
|
220
|
+
throw new Error(`Expected type for input '${name}', got ${typeToken.type} (${typeToken.value})`);
|
|
221
|
+
}
|
|
222
|
+
inputs.push({ name, type: typeToken.value });
|
|
223
|
+
if (peek()?.type === "symbol" && peek()?.value === ",") {
|
|
224
|
+
next();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
expect("symbol", "]");
|
|
228
|
+
return inputs;
|
|
229
|
+
}
|
|
230
|
+
const names = [];
|
|
231
|
+
while (peek() && peek()?.type === "identifier") {
|
|
232
|
+
names.push(next().value);
|
|
233
|
+
}
|
|
234
|
+
return names.map((n) => ({ name: n, type: "string" }));
|
|
235
|
+
}
|
|
236
|
+
function parseBlock() {
|
|
237
|
+
expect("symbol", ":");
|
|
238
|
+
const items = [];
|
|
239
|
+
while (peek() && peek()?.type === "identifier") {
|
|
240
|
+
items.push(next().value);
|
|
241
|
+
}
|
|
242
|
+
return items;
|
|
243
|
+
}
|
|
244
|
+
function parseStringValue() {
|
|
245
|
+
expect("symbol", ":");
|
|
246
|
+
const token = next();
|
|
247
|
+
let value = token.value;
|
|
248
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
249
|
+
value = value.slice(1, -1);
|
|
250
|
+
}
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function parseGuard() {
|
|
254
|
+
expect("keyword", "guard");
|
|
255
|
+
const name = expect("identifier").value;
|
|
256
|
+
expect("symbol", "{");
|
|
257
|
+
let importStr;
|
|
258
|
+
while (peek() && !(peek()?.type === "symbol" && peek()?.value === "}")) {
|
|
259
|
+
const kw = peek();
|
|
260
|
+
if (!kw)
|
|
261
|
+
break;
|
|
262
|
+
if (kw.type === "keyword" && kw.value === "import") {
|
|
263
|
+
next();
|
|
264
|
+
importStr = parseStringValue();
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
next();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
expect("symbol", "}");
|
|
271
|
+
return { name, import: importStr };
|
|
272
|
+
}
|
|
273
|
+
function parseModule() {
|
|
274
|
+
expect("keyword", "module");
|
|
275
|
+
const name = expect("identifier").value;
|
|
276
|
+
expect("symbol", "{");
|
|
277
|
+
let route = "";
|
|
278
|
+
const models = [];
|
|
279
|
+
const guards = [];
|
|
280
|
+
const actions = [];
|
|
281
|
+
while (peek() && !(peek()?.type === "symbol" && peek()?.value === "}")) {
|
|
282
|
+
const kw = peek();
|
|
283
|
+
if (!kw)
|
|
284
|
+
break;
|
|
285
|
+
if (kw.type === "keyword" && kw.value === "route") {
|
|
286
|
+
next();
|
|
287
|
+
route = parseStringValue();
|
|
288
|
+
}
|
|
289
|
+
else if (kw.type === "keyword" && kw.value === "models") {
|
|
290
|
+
next();
|
|
291
|
+
expect("symbol", ":");
|
|
292
|
+
while (peek() && peek()?.type === "identifier") {
|
|
293
|
+
models.push(next().value);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (kw.type === "keyword" && kw.value === "guards") {
|
|
297
|
+
next();
|
|
298
|
+
expect("symbol", ":");
|
|
299
|
+
while (peek() && peek()?.type === "identifier") {
|
|
300
|
+
guards.push(next().value);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if (kw.type === "keyword" && kw.value === "action") {
|
|
304
|
+
actions.push(parseAction());
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
next();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
expect("symbol", "}");
|
|
311
|
+
return { name, route, models, guards, actions };
|
|
312
|
+
}
|
|
313
|
+
return parseSchema();
|
|
314
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenStream = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
class TokenStream {
|
|
6
|
+
constructor(tokens) {
|
|
7
|
+
this.tokens = tokens;
|
|
8
|
+
this.pos = 0;
|
|
9
|
+
}
|
|
10
|
+
peek(offset = 0) {
|
|
11
|
+
return this.tokens[this.pos + offset] ?? null;
|
|
12
|
+
}
|
|
13
|
+
next() {
|
|
14
|
+
const token = this.tokens[this.pos++];
|
|
15
|
+
if (!token) {
|
|
16
|
+
throw new Error("Unexpected end of input");
|
|
17
|
+
}
|
|
18
|
+
return token;
|
|
19
|
+
}
|
|
20
|
+
expect(type, value) {
|
|
21
|
+
const token = this.next();
|
|
22
|
+
if (token.type !== type) {
|
|
23
|
+
throw new errors_1.ForgerError(`Expected ${type}, got ${token.type}`, token.line, token.column);
|
|
24
|
+
}
|
|
25
|
+
if (value && token.value !== value) {
|
|
26
|
+
throw new errors_1.ForgerError(`Expected '${value}', got '${token.value}'`, token.line, token.column);
|
|
27
|
+
}
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.TokenStream = TokenStream;
|
package/dist/tokens.d.ts
ADDED
package/dist/tokens.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgerapi/parser",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/linecodesxx/forgerapi.git",
|
|
16
|
+
"directory": "packages/parser"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@forgerapi/ast": "^0.0.1"
|
|
23
|
+
}
|
|
24
|
+
}
|