@foxystar/molang 0.1.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/README.md +82 -0
- package/esm/LRUCache.d.ts +6 -0
- package/esm/LRUCache.js +30 -0
- package/esm/diagnostics/error.d.ts +12 -0
- package/esm/diagnostics/error.js +37 -0
- package/esm/diagnostics/suggestions.d.ts +2 -0
- package/esm/diagnostics/suggestions.js +35 -0
- package/esm/index.d.ts +8 -0
- package/esm/index.js +6 -0
- package/esm/molang.d.ts +2 -0
- package/esm/molang.js +22 -0
- package/esm/package.json +3 -0
- package/esm/parser/expression.d.ts +87 -0
- package/esm/parser/expression.js +19 -0
- package/esm/parser/parser.d.ts +20 -0
- package/esm/parser/parser.js +490 -0
- package/esm/runtime/context.d.ts +19 -0
- package/esm/runtime/context.js +51 -0
- package/esm/runtime/math.d.ts +32 -0
- package/esm/runtime/math.js +89 -0
- package/esm/runtime/runtime.d.ts +34 -0
- package/esm/runtime/runtime.js +537 -0
- package/package.json +39 -0
- package/script/LRUCache.d.ts +6 -0
- package/script/LRUCache.js +33 -0
- package/script/diagnostics/error.d.ts +12 -0
- package/script/diagnostics/error.js +42 -0
- package/script/diagnostics/suggestions.d.ts +2 -0
- package/script/diagnostics/suggestions.js +40 -0
- package/script/index.d.ts +8 -0
- package/script/index.js +27 -0
- package/script/molang.d.ts +2 -0
- package/script/molang.js +29 -0
- package/script/package.json +3 -0
- package/script/parser/expression.d.ts +87 -0
- package/script/parser/expression.js +22 -0
- package/script/parser/parser.d.ts +20 -0
- package/script/parser/parser.js +494 -0
- package/script/runtime/context.d.ts +19 -0
- package/script/runtime/context.js +57 -0
- package/script/runtime/math.d.ts +32 -0
- package/script/runtime/math.js +92 -0
- package/script/runtime/runtime.d.ts +34 -0
- package/script/runtime/runtime.js +567 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MolangParser = void 0;
|
|
4
|
+
const error_js_1 = require("../diagnostics/error.js");
|
|
5
|
+
const runtime_js_1 = require("../runtime/runtime.js");
|
|
6
|
+
const expression_js_1 = require("./expression.js");
|
|
7
|
+
class MolangParser {
|
|
8
|
+
constructor(input) {
|
|
9
|
+
Object.defineProperty(this, "input", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: input
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "tokens", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: []
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "position", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: 0
|
|
26
|
+
});
|
|
27
|
+
this.tokenize();
|
|
28
|
+
}
|
|
29
|
+
tokenize() {
|
|
30
|
+
let str = this.input;
|
|
31
|
+
let offset = 0;
|
|
32
|
+
while (str.length > 0) {
|
|
33
|
+
const match = expression_js_1.TOKEN_REGEX.exec(str);
|
|
34
|
+
if (match === null || match.index !== 0) {
|
|
35
|
+
throw new error_js_1.MolangParseError(`Unexpected token '${str[0]}'`, offset, this.input);
|
|
36
|
+
}
|
|
37
|
+
const raw = match[0];
|
|
38
|
+
for (const type of Object.keys(match.groups)) {
|
|
39
|
+
if (match.groups[type]) {
|
|
40
|
+
if (type !== "whitespace") {
|
|
41
|
+
this.tokens.push({
|
|
42
|
+
type: type, value: raw,
|
|
43
|
+
start: offset,
|
|
44
|
+
end: offset + raw.length
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
offset += raw.length;
|
|
48
|
+
str = str.slice(raw.length);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
this.tokens.push({
|
|
54
|
+
type: "eof", value: "",
|
|
55
|
+
start: offset, end: offset
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
peek() {
|
|
59
|
+
return this.tokens[this.position];
|
|
60
|
+
}
|
|
61
|
+
consume(expectedType) {
|
|
62
|
+
const token = this.tokens[this.position++];
|
|
63
|
+
if (expectedType && token.type !== expectedType) {
|
|
64
|
+
throw new Error(`Expected token type ${expectedType}, but got ${token.type}`);
|
|
65
|
+
}
|
|
66
|
+
return token;
|
|
67
|
+
}
|
|
68
|
+
parseProgram() {
|
|
69
|
+
const body = [];
|
|
70
|
+
const start = this.peek().start;
|
|
71
|
+
while (this.peek().type !== "eof") {
|
|
72
|
+
body.push(this.parseStatement());
|
|
73
|
+
if (this.peek().type === "semicolon") {
|
|
74
|
+
this.consume("semicolon");
|
|
75
|
+
}
|
|
76
|
+
else if (this.peek().type !== "eof") {
|
|
77
|
+
throw new error_js_1.MolangParseError("Expected ';' between statements", this.peek().start, this.input);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const end = this.peek().end;
|
|
81
|
+
return { type: "Program", body, start, end };
|
|
82
|
+
}
|
|
83
|
+
parseStatement() {
|
|
84
|
+
const token = this.peek();
|
|
85
|
+
// loop(...)
|
|
86
|
+
if (token.type === "identifier" && token.value === "loop") {
|
|
87
|
+
const start = token.start;
|
|
88
|
+
this.consume("identifier"); // loop
|
|
89
|
+
this.consume("parenthesis"); // (
|
|
90
|
+
const count = this.parseExpression();
|
|
91
|
+
if (this.peek().type !== "comma") {
|
|
92
|
+
throw new error_js_1.MolangParseError("Expected ',' after loop count", this.peek().start, this.input);
|
|
93
|
+
}
|
|
94
|
+
this.consume("comma");
|
|
95
|
+
// body is a STATEMENT, not an expression
|
|
96
|
+
const body = this.parseStatement();
|
|
97
|
+
this.consume("parenthesis"); // )
|
|
98
|
+
return {
|
|
99
|
+
type: "LoopStatement",
|
|
100
|
+
count,
|
|
101
|
+
body,
|
|
102
|
+
start,
|
|
103
|
+
end: body.end
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// for_each(...)
|
|
107
|
+
if (token.type === "identifier" && token.value === "for_each") {
|
|
108
|
+
const start = token.start;
|
|
109
|
+
this.consume("identifier"); // for_each
|
|
110
|
+
this.consume("parenthesis"); // (
|
|
111
|
+
// iterator (must be assignable)
|
|
112
|
+
const iterator = this.parseExpression();
|
|
113
|
+
if (iterator.type !== "Identifier" &&
|
|
114
|
+
iterator.type !== "IndexExpression") {
|
|
115
|
+
throw new error_js_1.MolangParseError("'for_each' iterator must be an assignable variable", iterator.start, this.input);
|
|
116
|
+
}
|
|
117
|
+
if (this.peek().type !== "comma") {
|
|
118
|
+
throw new error_js_1.MolangParseError("Expected ',' after for_each iterator", this.peek().start, this.input);
|
|
119
|
+
}
|
|
120
|
+
this.consume("comma");
|
|
121
|
+
const iterable = this.parseExpression();
|
|
122
|
+
if (this.peek().type !== "comma") {
|
|
123
|
+
throw new error_js_1.MolangParseError("Expected ',' after for_each iterable", this.peek().start, this.input);
|
|
124
|
+
}
|
|
125
|
+
this.consume("comma");
|
|
126
|
+
// body is a STATEMENT
|
|
127
|
+
const body = this.parseStatement();
|
|
128
|
+
this.consume("parenthesis"); // )
|
|
129
|
+
return {
|
|
130
|
+
type: "ForEachStatement",
|
|
131
|
+
iterator,
|
|
132
|
+
iterable,
|
|
133
|
+
body,
|
|
134
|
+
start,
|
|
135
|
+
end: body.end
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (token.type === "brace" && token.value === "{") {
|
|
139
|
+
const start = token.start;
|
|
140
|
+
this.consume("brace"); // {
|
|
141
|
+
const body = [];
|
|
142
|
+
while (!(this.peek().type === "brace" && this.peek().value === "}")) {
|
|
143
|
+
body.push(this.parseStatement());
|
|
144
|
+
if (this.peek().type === "semicolon") {
|
|
145
|
+
this.consume("semicolon");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const end = this.consume("brace").end; // }
|
|
149
|
+
return { type: "BlockStatement", body, start, end };
|
|
150
|
+
}
|
|
151
|
+
if (token.type === "identifier" && token.value === "return") {
|
|
152
|
+
const start = token.start;
|
|
153
|
+
this.consume("identifier");
|
|
154
|
+
// return;
|
|
155
|
+
if (this.peek().type === "semicolon" || this.peek().type === "eof") {
|
|
156
|
+
return {
|
|
157
|
+
type: "ReturnStatement",
|
|
158
|
+
start,
|
|
159
|
+
end: token.end
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// return <expr>
|
|
163
|
+
const value = this.parseExpression();
|
|
164
|
+
return {
|
|
165
|
+
type: "ReturnStatement",
|
|
166
|
+
value,
|
|
167
|
+
start,
|
|
168
|
+
end: value.end
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// break
|
|
172
|
+
if (token.type === "identifier" && token.value === "break") {
|
|
173
|
+
const t = this.consume("identifier");
|
|
174
|
+
return { type: "BreakStatement", start: t.start, end: t.end };
|
|
175
|
+
}
|
|
176
|
+
// continue
|
|
177
|
+
if (token.type === "identifier" && token.value === "continue") {
|
|
178
|
+
const t = this.consume("identifier");
|
|
179
|
+
return { type: "ContinueStatement", start: t.start, end: t.end };
|
|
180
|
+
}
|
|
181
|
+
const expr = this.parseBinaryExpression();
|
|
182
|
+
if (this.peek().type === "operator" && this.peek().value === "?") {
|
|
183
|
+
this.consume("operator");
|
|
184
|
+
// Only allow a STATEMENT here
|
|
185
|
+
const thenStmt = this.parseStatement();
|
|
186
|
+
let elseStmt;
|
|
187
|
+
if (this.peek().type === "operator" && this.peek().value === ":") {
|
|
188
|
+
this.consume("operator"); // :
|
|
189
|
+
elseStmt = this.parseStatement();
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
type: "ConditionalStatement",
|
|
193
|
+
condition: expr,
|
|
194
|
+
then: thenStmt,
|
|
195
|
+
else: elseStmt,
|
|
196
|
+
start: expr.start,
|
|
197
|
+
end: (elseStmt ?? thenStmt).end
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Assignment
|
|
201
|
+
if (this.peek().type === "assignment") {
|
|
202
|
+
this.consume("assignment");
|
|
203
|
+
const value = this.parseExpression();
|
|
204
|
+
return {
|
|
205
|
+
type: "AssignStatement",
|
|
206
|
+
target: expr,
|
|
207
|
+
value,
|
|
208
|
+
start: expr.start,
|
|
209
|
+
end: value.end
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Expression statement
|
|
213
|
+
return {
|
|
214
|
+
type: "ExprStatement",
|
|
215
|
+
expr,
|
|
216
|
+
start: expr.start,
|
|
217
|
+
end: expr.end
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
parseExpression() {
|
|
221
|
+
return this.parseConditionalExpression();
|
|
222
|
+
}
|
|
223
|
+
parseBinaryExpression(precedence = 0) {
|
|
224
|
+
let left = this.parsePrimaryExpression();
|
|
225
|
+
while (true) {
|
|
226
|
+
const token = this.peek();
|
|
227
|
+
if (token.type === "comma" || token.type === "parenthesis" || token.type === "eof") {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
const tokenPrecedence = this.getOperatorPrecedence(token.value);
|
|
231
|
+
if (tokenPrecedence < precedence) {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
this.consume("operator");
|
|
235
|
+
const right = this.parseBinaryExpression(tokenPrecedence + 1);
|
|
236
|
+
if (token.value === "->") {
|
|
237
|
+
left = {
|
|
238
|
+
type: "ArrowExpression",
|
|
239
|
+
left, right,
|
|
240
|
+
start: left.start,
|
|
241
|
+
end: right.end
|
|
242
|
+
};
|
|
243
|
+
continue; // Skip
|
|
244
|
+
}
|
|
245
|
+
if (left.type === "Literal" && right.type === "Literal") {
|
|
246
|
+
const folded = this.tryFold(token.value, left.value, right.value);
|
|
247
|
+
if (folded !== undefined) {
|
|
248
|
+
left = {
|
|
249
|
+
type: "Literal",
|
|
250
|
+
value: folded,
|
|
251
|
+
start: left.start,
|
|
252
|
+
end: right.end
|
|
253
|
+
};
|
|
254
|
+
continue; // keep parsing higher-precedence ops
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
left = {
|
|
258
|
+
type: "BinaryExpression",
|
|
259
|
+
operator: token.value,
|
|
260
|
+
left, right,
|
|
261
|
+
start: left.start,
|
|
262
|
+
end: right.end
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return left;
|
|
266
|
+
}
|
|
267
|
+
tryFold(op, left, right) {
|
|
268
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
switch (op) {
|
|
272
|
+
case "+": return left + right;
|
|
273
|
+
case "-": return left - right;
|
|
274
|
+
case "*": return left * right;
|
|
275
|
+
case "/": return left / right;
|
|
276
|
+
case "<": return left < right;
|
|
277
|
+
case ">": return left > right;
|
|
278
|
+
case "<=": return left <= right;
|
|
279
|
+
case ">=": return left >= right;
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
getOperatorPrecedence(operator) {
|
|
284
|
+
switch (operator) {
|
|
285
|
+
case "?": return -1; // handled outside binary parsing
|
|
286
|
+
case ":": return -1;
|
|
287
|
+
case "||": return 1;
|
|
288
|
+
case "&&": return 2;
|
|
289
|
+
case "??": return 0;
|
|
290
|
+
case "|": return 2;
|
|
291
|
+
case "^": return 3;
|
|
292
|
+
case "&": return 4;
|
|
293
|
+
case "==":
|
|
294
|
+
case "!=":
|
|
295
|
+
case "<":
|
|
296
|
+
case ">":
|
|
297
|
+
case "<=":
|
|
298
|
+
case ">=":
|
|
299
|
+
return 5;
|
|
300
|
+
case "<<":
|
|
301
|
+
case ">>":
|
|
302
|
+
case ">>>":
|
|
303
|
+
return 6;
|
|
304
|
+
case "+":
|
|
305
|
+
case "-":
|
|
306
|
+
return 7;
|
|
307
|
+
case "*":
|
|
308
|
+
case "/":
|
|
309
|
+
return 8;
|
|
310
|
+
case "->":
|
|
311
|
+
return 9;
|
|
312
|
+
default:
|
|
313
|
+
return -1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
parseConditionalExpression() {
|
|
317
|
+
const condition = this.parseBinaryExpression();
|
|
318
|
+
if (this.peek().type === "operator" &&
|
|
319
|
+
this.peek().value === "?") {
|
|
320
|
+
this.consume("operator"); // ?
|
|
321
|
+
const then = this.parseExpression();
|
|
322
|
+
// A ? B : C
|
|
323
|
+
if (this.peek().type === "operator" &&
|
|
324
|
+
this.peek().value === ":") {
|
|
325
|
+
this.consume("operator"); // :
|
|
326
|
+
const elseStmt = this.parseExpression();
|
|
327
|
+
return {
|
|
328
|
+
type: "ConditionalExpression",
|
|
329
|
+
condition,
|
|
330
|
+
then,
|
|
331
|
+
else: elseStmt,
|
|
332
|
+
start: condition.start,
|
|
333
|
+
end: elseStmt.end
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// Binary condition (A ? B)
|
|
337
|
+
return {
|
|
338
|
+
type: "ConditionalExpression",
|
|
339
|
+
condition,
|
|
340
|
+
then,
|
|
341
|
+
start: condition.start,
|
|
342
|
+
end: then.end
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return condition;
|
|
346
|
+
}
|
|
347
|
+
parsePrimaryExpression() {
|
|
348
|
+
return this.parseUnary();
|
|
349
|
+
}
|
|
350
|
+
parseUnary() {
|
|
351
|
+
const token = this.peek();
|
|
352
|
+
if (token.type === "operator" && (token.value === "-" || token.value === "!")) {
|
|
353
|
+
this.consume("operator");
|
|
354
|
+
const expr = this.parseUnary();
|
|
355
|
+
return {
|
|
356
|
+
type: "UnaryExpression",
|
|
357
|
+
operator: token.value,
|
|
358
|
+
expression: expr,
|
|
359
|
+
start: token.start,
|
|
360
|
+
end: expr.end
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return this.parseAtom();
|
|
364
|
+
}
|
|
365
|
+
parseAtom() {
|
|
366
|
+
const token = this.peek();
|
|
367
|
+
if (token.type === "number") {
|
|
368
|
+
this.consume("number");
|
|
369
|
+
return {
|
|
370
|
+
type: "Literal",
|
|
371
|
+
value: Number(token.value),
|
|
372
|
+
start: token.start,
|
|
373
|
+
end: token.end
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (token.type === "string") {
|
|
377
|
+
this.consume("string");
|
|
378
|
+
return {
|
|
379
|
+
type: "Literal",
|
|
380
|
+
value: token.value.slice(1, -1),
|
|
381
|
+
start: token.start,
|
|
382
|
+
end: token.end
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (token.type === "bracket" && token.value === "[") {
|
|
386
|
+
const start = token.start;
|
|
387
|
+
this.consume("bracket");
|
|
388
|
+
const elements = [];
|
|
389
|
+
while (!(this.peek().type === "bracket" && this.peek().value === "]")) {
|
|
390
|
+
elements.push(this.parseExpression());
|
|
391
|
+
if (this.peek().type !== "comma") {
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
this.consume("comma");
|
|
395
|
+
}
|
|
396
|
+
const close = this.consume("bracket");
|
|
397
|
+
return {
|
|
398
|
+
type: "ArrayLiteral",
|
|
399
|
+
elements,
|
|
400
|
+
start, end: close.end
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (token.type === "identifier" && (token.value === "true" || token.value === "false")) {
|
|
404
|
+
this.consume("identifier");
|
|
405
|
+
return {
|
|
406
|
+
type: "Literal",
|
|
407
|
+
value: token.value === "true",
|
|
408
|
+
start: token.start,
|
|
409
|
+
end: token.end
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
if (token.type === "identifier") {
|
|
413
|
+
this.consume("identifier");
|
|
414
|
+
const path = runtime_js_1.MolangRuntime.normalizePath(token.value);
|
|
415
|
+
let expr = {
|
|
416
|
+
type: "Identifier",
|
|
417
|
+
name: token.value,
|
|
418
|
+
path,
|
|
419
|
+
start: token.start,
|
|
420
|
+
end: token.end
|
|
421
|
+
};
|
|
422
|
+
// Check if this is a function call
|
|
423
|
+
if (this.peek().type === "parenthesis" && this.peek().value === "(") {
|
|
424
|
+
this.consume("parenthesis");
|
|
425
|
+
const args = [];
|
|
426
|
+
while (true) {
|
|
427
|
+
const next = this.peek();
|
|
428
|
+
if (next.type === "eof") {
|
|
429
|
+
throw new error_js_1.MolangParseError("Unclosed function call", token.start, this.input);
|
|
430
|
+
}
|
|
431
|
+
if (next.type === "parenthesis" && next.value === ")") {
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
if (next.type === "comma") {
|
|
435
|
+
throw new error_js_1.MolangParseError(`Unexpected token ','`, next.start, this.input);
|
|
436
|
+
}
|
|
437
|
+
args.push(this.parseExpression());
|
|
438
|
+
const afterArg = this.peek();
|
|
439
|
+
if (afterArg.type === "comma") {
|
|
440
|
+
this.consume("comma");
|
|
441
|
+
const lookahead = this.peek();
|
|
442
|
+
if (lookahead.type === "parenthesis" && lookahead.value === ")") {
|
|
443
|
+
throw new error_js_1.MolangParseError(`Unexpected token ')'`, lookahead.start, this.input);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else if (!(afterArg.type === "parenthesis" && afterArg.value === ")")) {
|
|
447
|
+
throw new error_js_1.MolangParseError(`Unexpected token '${afterArg.value}'`, afterArg.start, this.input);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
this.consume("parenthesis");
|
|
451
|
+
expr = {
|
|
452
|
+
type: "CallExpression",
|
|
453
|
+
callee: expr,
|
|
454
|
+
arguments: args,
|
|
455
|
+
start: expr.start,
|
|
456
|
+
end: this.tokens[this.position - 1].end
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
while (this.peek().type === "bracket" && this.peek().value === "[") {
|
|
460
|
+
this.consume("bracket");
|
|
461
|
+
const index = this.parseExpression();
|
|
462
|
+
const close = this.consume("bracket");
|
|
463
|
+
expr = {
|
|
464
|
+
type: "IndexExpression",
|
|
465
|
+
array: expr,
|
|
466
|
+
index,
|
|
467
|
+
start: expr.start,
|
|
468
|
+
end: close.end
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return expr;
|
|
472
|
+
}
|
|
473
|
+
if (token.type === "parenthesis" && token.value === "(") {
|
|
474
|
+
this.consume("parenthesis");
|
|
475
|
+
let expr = this.parseExpression();
|
|
476
|
+
this.consume("parenthesis");
|
|
477
|
+
while (this.peek().type === "bracket" && this.peek().value === "[") {
|
|
478
|
+
this.consume("bracket");
|
|
479
|
+
const index = this.parseExpression();
|
|
480
|
+
const close = this.consume("bracket");
|
|
481
|
+
expr = {
|
|
482
|
+
type: "IndexExpression",
|
|
483
|
+
array: expr,
|
|
484
|
+
index,
|
|
485
|
+
start: expr.start,
|
|
486
|
+
end: close.end
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return expr;
|
|
490
|
+
}
|
|
491
|
+
throw new Error(`Unexpected token: ${token.value}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
exports.MolangParser = MolangParser;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type MolangPrimitive = number | string | boolean;
|
|
2
|
+
export type MolangArray = MolangPrimitive[];
|
|
3
|
+
export type MolangValue = MolangPrimitive | MolangArray;
|
|
4
|
+
export type MolangFunction = (...args: never[]) => unknown;
|
|
5
|
+
export type MolangBinding = MolangValue | MolangFunction | MolangNamespace | undefined;
|
|
6
|
+
export type MolangNamespace = {
|
|
7
|
+
[key: string]: MolangBinding;
|
|
8
|
+
};
|
|
9
|
+
export interface MolangContext extends MolangNamespace {
|
|
10
|
+
math?: MolangNamespace;
|
|
11
|
+
variable?: MolangNamespace;
|
|
12
|
+
temp?: MolangNamespace;
|
|
13
|
+
query?: MolangNamespace;
|
|
14
|
+
context?: MolangNamespace;
|
|
15
|
+
}
|
|
16
|
+
export declare const DEFAULT_CONTEXT: MolangContext;
|
|
17
|
+
export declare function createMolangContext(initial?: MolangContext): MolangContext;
|
|
18
|
+
export declare function isNamespace(value: unknown): value is MolangNamespace;
|
|
19
|
+
export declare function mergeContext(base: MolangNamespace, user: MolangNamespace): MolangNamespace;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeContext = exports.isNamespace = exports.createMolangContext = exports.DEFAULT_CONTEXT = void 0;
|
|
4
|
+
const math_js_1 = require("./math.js");
|
|
5
|
+
const math_1 = require("@foxystar/math");
|
|
6
|
+
;
|
|
7
|
+
const PERLIN = new math_1.PerlinNoise();
|
|
8
|
+
exports.DEFAULT_CONTEXT = {
|
|
9
|
+
math: math_js_1.MolangMath,
|
|
10
|
+
variable: {},
|
|
11
|
+
temp: {},
|
|
12
|
+
context: {},
|
|
13
|
+
query: {
|
|
14
|
+
block_state: (_name) => {
|
|
15
|
+
throw new Error("This function call can only be used in block context");
|
|
16
|
+
},
|
|
17
|
+
noise: (x, y) => {
|
|
18
|
+
return PERLIN.noise({ x, y });
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
function createMolangContext(initial) {
|
|
23
|
+
const ctx = mergeContext(exports.DEFAULT_CONTEXT, initial ?? {});
|
|
24
|
+
ctx.variable = {
|
|
25
|
+
...(initial?.variable ?? {}),
|
|
26
|
+
};
|
|
27
|
+
ctx.temp = {
|
|
28
|
+
...(initial?.temp ?? {}),
|
|
29
|
+
};
|
|
30
|
+
return ctx;
|
|
31
|
+
}
|
|
32
|
+
exports.createMolangContext = createMolangContext;
|
|
33
|
+
const LOCKED_KEYS = new Set(["math"]);
|
|
34
|
+
function isNamespace(value) {
|
|
35
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
exports.isNamespace = isNamespace;
|
|
38
|
+
function mergeContext(base, user) {
|
|
39
|
+
const out = Object.create(base);
|
|
40
|
+
for (const key in user) {
|
|
41
|
+
if (base === exports.DEFAULT_CONTEXT && LOCKED_KEYS.has(key)) {
|
|
42
|
+
throw new Error(`Cannot override core Molang namespace '${key}'`);
|
|
43
|
+
}
|
|
44
|
+
const userValue = user[key];
|
|
45
|
+
const baseValue = base[key];
|
|
46
|
+
const baseIsNamespace = isNamespace(baseValue);
|
|
47
|
+
const userIsNamespace = isNamespace(userValue);
|
|
48
|
+
if (baseIsNamespace && userIsNamespace) {
|
|
49
|
+
out[key] = mergeContext(baseValue, userValue);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
out[key] = userValue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
exports.mergeContext = mergeContext;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const MolangMath: {
|
|
2
|
+
pi: number;
|
|
3
|
+
abs: (x: number) => number;
|
|
4
|
+
ceil: (x: number) => number;
|
|
5
|
+
exp: (x: number) => number;
|
|
6
|
+
ln: (x: number) => number;
|
|
7
|
+
floor: (x: number) => number;
|
|
8
|
+
pow: (x: number, y: number) => number;
|
|
9
|
+
round: (x: number) => number;
|
|
10
|
+
sign: (x: number) => number;
|
|
11
|
+
sqrt: (x: number) => number;
|
|
12
|
+
trunc: (x: number) => number;
|
|
13
|
+
clamp: (value: number, min: number, max: number) => number;
|
|
14
|
+
max: (...values: number[]) => number;
|
|
15
|
+
min: (...values: number[]) => number;
|
|
16
|
+
cos: (v: number) => number;
|
|
17
|
+
acos: (v: number) => number;
|
|
18
|
+
sin: (v: number) => number;
|
|
19
|
+
asin: (v: number) => number;
|
|
20
|
+
atan: (v: number) => number;
|
|
21
|
+
atan2: (y: number, x: number) => number;
|
|
22
|
+
copy_sign: (x: number, y: number) => number;
|
|
23
|
+
random: (low: number, high: number) => number;
|
|
24
|
+
random_integer: (low: number, high: number) => number;
|
|
25
|
+
die_roll: (num: number, low: number, high: number) => number;
|
|
26
|
+
die_roll_integer: (num: number, low: number, high: number) => number;
|
|
27
|
+
hermite_blend: (t: number) => number;
|
|
28
|
+
lerp: (start: number, end: number, t: number) => number;
|
|
29
|
+
lerprotate: (start: number, end: number, t: number) => number;
|
|
30
|
+
min_angle: (value: number) => number;
|
|
31
|
+
mod: (value: number, denom: number) => number;
|
|
32
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MolangMath = void 0;
|
|
4
|
+
const math_1 = require("@foxystar/math");
|
|
5
|
+
const rng = new math_1.XoroshiroRandom();
|
|
6
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
7
|
+
const RAD_TO_DEG = 180 / Math.PI;
|
|
8
|
+
function randomRange(low, high) {
|
|
9
|
+
return low + rng.nextDouble() * (high - low);
|
|
10
|
+
}
|
|
11
|
+
function randomIntRange(low, high) {
|
|
12
|
+
if (high < low) {
|
|
13
|
+
return low;
|
|
14
|
+
}
|
|
15
|
+
return low + rng.nextInt(high - low + 1);
|
|
16
|
+
}
|
|
17
|
+
exports.MolangMath = {
|
|
18
|
+
pi: Math.PI,
|
|
19
|
+
abs: Math.abs,
|
|
20
|
+
ceil: Math.ceil,
|
|
21
|
+
exp: Math.exp,
|
|
22
|
+
ln: Math.log,
|
|
23
|
+
floor: Math.floor,
|
|
24
|
+
pow: Math.pow,
|
|
25
|
+
round: Math.round,
|
|
26
|
+
sign: Math.sign,
|
|
27
|
+
sqrt: Math.sqrt,
|
|
28
|
+
trunc: Math.trunc,
|
|
29
|
+
clamp: (value, min, max) => {
|
|
30
|
+
return Math.min(Math.max(value, min), max);
|
|
31
|
+
},
|
|
32
|
+
max: Math.max,
|
|
33
|
+
min: Math.min,
|
|
34
|
+
cos: (v) => {
|
|
35
|
+
return Math.cos(v * DEG_TO_RAD);
|
|
36
|
+
},
|
|
37
|
+
acos: (v) => {
|
|
38
|
+
return Math.acos(v) * RAD_TO_DEG;
|
|
39
|
+
},
|
|
40
|
+
sin: (v) => {
|
|
41
|
+
return Math.sin(v * DEG_TO_RAD);
|
|
42
|
+
},
|
|
43
|
+
asin: (v) => {
|
|
44
|
+
return Math.asin(v) * RAD_TO_DEG;
|
|
45
|
+
},
|
|
46
|
+
atan: (v) => {
|
|
47
|
+
return Math.atan(v) * RAD_TO_DEG;
|
|
48
|
+
},
|
|
49
|
+
atan2: (y, x) => {
|
|
50
|
+
return Math.atan2(y, x) * RAD_TO_DEG;
|
|
51
|
+
},
|
|
52
|
+
copy_sign: (x, y) => {
|
|
53
|
+
return Math.abs(x) * Math.sign(y);
|
|
54
|
+
},
|
|
55
|
+
random: (low, high) => {
|
|
56
|
+
return randomRange(low, high);
|
|
57
|
+
},
|
|
58
|
+
random_integer: (low, high) => {
|
|
59
|
+
return randomIntRange(low, high);
|
|
60
|
+
},
|
|
61
|
+
die_roll: (num, low, high) => {
|
|
62
|
+
let sum = 0;
|
|
63
|
+
for (let i = 0; i < num; i++) {
|
|
64
|
+
sum += randomRange(low, high);
|
|
65
|
+
}
|
|
66
|
+
return sum;
|
|
67
|
+
},
|
|
68
|
+
die_roll_integer: (num, low, high) => {
|
|
69
|
+
let sum = 0;
|
|
70
|
+
for (let i = 0; i < num; i++) {
|
|
71
|
+
sum += randomIntRange(low, high);
|
|
72
|
+
}
|
|
73
|
+
return sum;
|
|
74
|
+
},
|
|
75
|
+
hermite_blend: (t) => {
|
|
76
|
+
return 3 * t * t - 2 * t * t * t;
|
|
77
|
+
},
|
|
78
|
+
lerp: (start, end, t) => {
|
|
79
|
+
return start + (end - start) * t;
|
|
80
|
+
},
|
|
81
|
+
lerprotate: (start, end, t) => {
|
|
82
|
+
const diff = ((end - start + 540) % 360) - 180;
|
|
83
|
+
return start + diff * t;
|
|
84
|
+
},
|
|
85
|
+
min_angle: (value) => {
|
|
86
|
+
const angle = ((value + 180) % 360 + 360) % 360 - 180;
|
|
87
|
+
return angle;
|
|
88
|
+
},
|
|
89
|
+
mod: (value, denom) => {
|
|
90
|
+
return ((value % denom) + denom) % denom;
|
|
91
|
+
},
|
|
92
|
+
};
|