@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.
Files changed (44) hide show
  1. package/README.md +82 -0
  2. package/esm/LRUCache.d.ts +6 -0
  3. package/esm/LRUCache.js +30 -0
  4. package/esm/diagnostics/error.d.ts +12 -0
  5. package/esm/diagnostics/error.js +37 -0
  6. package/esm/diagnostics/suggestions.d.ts +2 -0
  7. package/esm/diagnostics/suggestions.js +35 -0
  8. package/esm/index.d.ts +8 -0
  9. package/esm/index.js +6 -0
  10. package/esm/molang.d.ts +2 -0
  11. package/esm/molang.js +22 -0
  12. package/esm/package.json +3 -0
  13. package/esm/parser/expression.d.ts +87 -0
  14. package/esm/parser/expression.js +19 -0
  15. package/esm/parser/parser.d.ts +20 -0
  16. package/esm/parser/parser.js +490 -0
  17. package/esm/runtime/context.d.ts +19 -0
  18. package/esm/runtime/context.js +51 -0
  19. package/esm/runtime/math.d.ts +32 -0
  20. package/esm/runtime/math.js +89 -0
  21. package/esm/runtime/runtime.d.ts +34 -0
  22. package/esm/runtime/runtime.js +537 -0
  23. package/package.json +39 -0
  24. package/script/LRUCache.d.ts +6 -0
  25. package/script/LRUCache.js +33 -0
  26. package/script/diagnostics/error.d.ts +12 -0
  27. package/script/diagnostics/error.js +42 -0
  28. package/script/diagnostics/suggestions.d.ts +2 -0
  29. package/script/diagnostics/suggestions.js +40 -0
  30. package/script/index.d.ts +8 -0
  31. package/script/index.js +27 -0
  32. package/script/molang.d.ts +2 -0
  33. package/script/molang.js +29 -0
  34. package/script/package.json +3 -0
  35. package/script/parser/expression.d.ts +87 -0
  36. package/script/parser/expression.js +22 -0
  37. package/script/parser/parser.d.ts +20 -0
  38. package/script/parser/parser.js +494 -0
  39. package/script/runtime/context.d.ts +19 -0
  40. package/script/runtime/context.js +57 -0
  41. package/script/runtime/math.d.ts +32 -0
  42. package/script/runtime/math.js +92 -0
  43. package/script/runtime/runtime.d.ts +34 -0
  44. 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
+ };