affinirum 1.2.1 → 1.2.3

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 CHANGED
@@ -22,13 +22,15 @@ Target: ES2022 [browser+NodeJS][ESM+CJS].
22
22
 
23
23
  ## Specifications
24
24
 
25
- Script can contain multiple semicolon-separated expressions, and blocks of statements.
26
- The value of a block is determined by the value of the last expression.
25
+ A script may contain multiple expressions separated by semicolons, as well as blocks of statements.
26
+ The value of a block is defined by the value of its last expression.
27
27
  - Scientific notation is supported for floating point numbers, like *0.1281e+2*.
28
28
  - ISO Timestamps prefixed with **@**, like *@2025-05-11T19:09:21.320Z*.
29
- - Hexadecimal buffer values are enclosed in backtick (**\`**) quotes, like *\`10ab0901\`*.
30
- - String literals are enclosed in single (**'**), or double (**"**) quotes, like *'string value1'*, or *"string value2"*.
29
+ - Hexadecimal buffer values are enclosed in backticks (**\`**), like *\`10ab0901\`*.
30
+ - String literals may be enclosed in single (**'**), or double (**"**) quotes, like *'string value1'*, or *"string value2"*.
31
+ - Line comments begin with a double slash (**//**), while block comments are enclosed in triple slashes (**///**).
31
32
 
33
+ ### Arrays
32
34
  Array is an ordered sequence of values of any type.
33
35
  It is defined by comma-separated values enclosed in brackets (**[]**),
34
36
  like *[0,1,2]*, *["a","b","c"]*.
@@ -39,6 +41,7 @@ Array elements can be accessed using the access operator (**.**),
39
41
 
40
42
  Easy way to check if array contains an index is to use presence operator (**?**), like *theArray?50*.
41
43
 
44
+ ### Objects
42
45
  Object is a container of named values of any type.
43
46
  It is defined by comma-separated key-value pair enclosed in brackets (**[]**) where key is separated from value by colon (**:**),
44
47
  like *["key1":100, "key2":"abc"]*, *["a":0,"b":"str":"c":valueVar]*.
@@ -50,13 +53,14 @@ Object properties can be accessed using the access operator (**.**) with a strin
50
53
 
51
54
  Easy way to check if object contains a key is to use presence operator (**?**), like *theObject?myKey*.
52
55
 
56
+ ### Functions
53
57
  A function is a callable code unit that produces a value.
54
58
  The set of built-in functions can be extended through configuration entries.
55
59
  Additionally, subroutines (functions defined in code) can be created.
56
60
 
57
- Valid variable and function names must start with a letter or underscore (**\_**)
58
- and can be followed by any combination of alphanumeric characters or underscores,
59
- like *x*, *\_a1*, *abc25*.
61
+ Valid variable and function names must start with a letter, number sign (**\#**), dollar sign (**\$**), or underscore (**\_**)
62
+ and can be followed by any combination of alphanumeric characters, number signs, dollar signs, or underscores,
63
+ like *x*, *\_a1$*, *abc25*.
60
64
 
61
65
  Whitespace characters are ignored.
62
66
 
@@ -118,7 +122,7 @@ Unknown or variant type is declared as **??**.
118
122
  - Arithmetic division assignment: **/=**
119
123
  - Arithmetic remainder assignment: **%=**
120
124
 
121
- ### Constants
125
+ ### Predefined Constants
122
126
 
123
127
  #### Boolean
124
128
  - **boolean Boolean.Or(values:array...)** — Boolean disjunction
@@ -186,7 +190,7 @@ Unknown or variant type is declared as **??**.
186
190
  - **void | boolean | float | string | array | object JSON.Parse(value: string)** — Parse JSON-formatted string
187
191
 
188
192
 
189
- ### Functions
193
+ ### Predefined Functions
190
194
 
191
195
  #### General Functions
192
196
  - **?? ??.Coalesce(otherwise: ??)** — Null coalescence
@@ -54,7 +54,7 @@ export declare class Affinirum {
54
54
  @returns Calculated value.
55
55
  */
56
56
  evaluate(values?: Record<string, Value>): Value;
57
- protected _block(state: ParserState, scope: StaticScope): Node;
57
+ protected _list(state: ParserState, scope: StaticScope): Node;
58
58
  protected _unit(state: ParserState, scope: StaticScope): Node;
59
59
  protected _coalescence(state: ParserState, scope: StaticScope): Node;
60
60
  protected _disjunction(state: ParserState, scope: StaticScope): Node;
@@ -70,6 +70,5 @@ export declare class Affinirum {
70
70
  protected _function(state: ParserState, scope: StaticScope): Node;
71
71
  protected _loop(state: ParserState, scope: StaticScope): LoopNode;
72
72
  protected _switch(state: ParserState, scope: StaticScope): SwitchNode;
73
- protected _call(frame: ParserFrame, func: Constant, subnodes: Node[]): Node;
74
73
  protected _type(state: ParserState, scope: StaticScope): Type;
75
74
  }
package/dst/Affinirum.js CHANGED
@@ -12,13 +12,15 @@ import { Functions } from "./Functions.js";
12
12
  import { ArrayNode } from "./node/ArrayNode.js";
13
13
  import { BlockNode } from "./node/BlockNode.js";
14
14
  import { ConstantNode } from "./node/ConstantNode.js";
15
- import { CallNode } from "./node/CallNode.js";
15
+ import { FunctionNode } from "./node/FunctionNode.js";
16
+ import { JumpNode } from "./node/JumpNode.js";
16
17
  import { LoopNode } from "./node/LoopNode.js";
17
18
  import { ObjectNode } from "./node/ObjectNode.js";
18
19
  import { SwitchNode } from "./node/SwitchNode.js";
19
20
  import { VariableNode } from "./node/VariableNode.js";
20
21
  import { ParserState } from "./ParserState.js";
21
22
  import { StaticScope } from "./StaticScope.js";
23
+ import { JumpException } from "./JumpException.js";
22
24
  export class Affinirum {
23
25
  static keywords = [...Keywords, ...Constants.map((c) => c[0])];
24
26
  _script;
@@ -45,8 +47,8 @@ export class Affinirum {
45
47
  }
46
48
  }
47
49
  const state = new ParserState(this._script);
48
- this._root = this._block(state.next(), this._scope);
49
- if (!state.isVoid) {
50
+ this._root = this._list(state.next(), this._scope);
51
+ if (!state.isEmpty) {
50
52
  state.throwError("unexpected expression token or expression end");
51
53
  }
52
54
  this._root = this._root.compile(config?.type ?? Type.Unknown);
@@ -100,15 +102,34 @@ export class Affinirum {
100
102
  }
101
103
  variable.value = value;
102
104
  }
103
- return this._root.evaluate();
105
+ try {
106
+ return this._root.evaluate();
107
+ }
108
+ catch (e) {
109
+ if (e instanceof JumpException) {
110
+ if (e.jump === "exit") {
111
+ return e.value;
112
+ }
113
+ else {
114
+ throw this._root.throwError(`unexpected ${e.jump} jump`);
115
+ }
116
+ }
117
+ throw e;
118
+ }
104
119
  }
105
- _block(state, scope) {
120
+ _list(state, scope) {
106
121
  const frame = state.starts();
107
- const nodes = [this._unit(state, scope)];
108
- while (state.isSemicolon) {
109
- nodes.push(this._unit(state.next(), scope));
122
+ const subnodes = [];
123
+ while (!state.isEmpty) {
124
+ subnodes.push(this._unit(state, scope));
125
+ if (!state.isSemicolon) {
126
+ break;
127
+ }
128
+ else {
129
+ state.next();
130
+ }
110
131
  }
111
- return new BlockNode(frame.ends(state), nodes);
132
+ return new BlockNode(frame.ends(state), subnodes);
112
133
  }
113
134
  _unit(state, scope) {
114
135
  return this._coalescence(state, scope);
@@ -116,21 +137,24 @@ export class Affinirum {
116
137
  _coalescence(state, scope) {
117
138
  let node = this._disjunction(state, scope);
118
139
  while (state.operator === funcCoalesce) {
119
- node = this._call(state.starts(), state.operator, [node, this._disjunction(state.next(), scope)]);
140
+ const fnode = new ConstantNode(state, state.operator);
141
+ node = new FunctionNode(state, fnode, [node, this._disjunction(state.next(), scope)]);
120
142
  }
121
143
  return node;
122
144
  }
123
145
  _disjunction(state, scope) {
124
146
  let node = this._conjunction(state, scope);
125
147
  while (state.operator === funcOr) {
126
- node = this._call(state.starts(), state.operator, [node, this._conjunction(state.next(), scope)]);
148
+ const fnode = new ConstantNode(state, state.operator);
149
+ node = new FunctionNode(state, fnode, [node, this._conjunction(state.next(), scope)]);
127
150
  }
128
151
  return node;
129
152
  }
130
153
  _conjunction(state, scope) {
131
154
  let node = this._negation(state, scope);
132
155
  while (state.operator === funcAnd) {
133
- node = this._call(state.starts(), state.operator, [node, this._negation(state.next(), scope)]);
156
+ const fnode = new ConstantNode(state, state.operator);
157
+ node = new FunctionNode(state, fnode, [node, this._negation(state.next(), scope)]);
134
158
  }
135
159
  return node;
136
160
  }
@@ -144,7 +168,8 @@ export class Affinirum {
144
168
  }
145
169
  let node = this._comparison(state, scope);
146
170
  if (negate) {
147
- node = this._call(frame, funcNot, [node]);
171
+ const fnode = new ConstantNode(frame, funcNot);
172
+ node = new FunctionNode(frame, fnode, [node]);
148
173
  }
149
174
  return node;
150
175
  }
@@ -153,21 +178,24 @@ export class Affinirum {
153
178
  while (state.operator === funcGreaterThan || state.operator === funcLessThan
154
179
  || state.operator === funcGreaterOrEqual || state.operator === funcLessOrEqual
155
180
  || state.operator === funcEqual || state.operator === funcNotEqual) {
156
- node = this._call(state.starts(), state.operator, [node, this._aggregate(state.next(), scope)]);
181
+ const fnode = new ConstantNode(state, state.operator);
182
+ node = new FunctionNode(state, fnode, [node, this._aggregate(state.next(), scope)]);
157
183
  }
158
184
  return node;
159
185
  }
160
186
  _aggregate(state, scope) {
161
187
  let node = this._product(state, scope);
162
188
  while (state.operator === funcAdd || state.operator === funcSubtract) {
163
- node = this._call(state.starts(), state.operator, [node, this._product(state.next(), scope)]);
189
+ const fnode = new ConstantNode(state, state.operator);
190
+ node = new FunctionNode(state, fnode, [node, this._product(state.next(), scope)]);
164
191
  }
165
192
  return node;
166
193
  }
167
194
  _product(state, scope) {
168
195
  let node = this._signum(state, scope);
169
196
  while (state.operator === funcMultiply || state.operator === funcDivide || state.operator === funcRemainder) {
170
- node = this._call(state.starts(), state.operator, [node, this._signum(state.next(), scope)]);
197
+ const fnode = new ConstantNode(state, state.operator);
198
+ node = new FunctionNode(state, fnode, [node, this._signum(state.next(), scope)]);
171
199
  }
172
200
  return node;
173
201
  }
@@ -183,14 +211,16 @@ export class Affinirum {
183
211
  }
184
212
  let node = this._factor(state, scope);
185
213
  if (negate) {
186
- node = this._call(frame.ends(state), funcNegate, [node]);
214
+ const fnode = new ConstantNode(frame, funcNegate);
215
+ node = new FunctionNode(frame, fnode, [node]);
187
216
  }
188
217
  return node;
189
218
  }
190
219
  _factor(state, scope) {
191
220
  let node = this._accessor(state, scope);
192
221
  while (state.operator === funcPower) {
193
- node = this._call(state.starts(), state.operator, [node, this._accessor(state.next(), scope)]);
222
+ const fnode = new ConstantNode(state, state.operator);
223
+ node = new FunctionNode(state, fnode, [node, this._accessor(state.next(), scope)]);
194
224
  }
195
225
  return node;
196
226
  }
@@ -201,11 +231,11 @@ export class Affinirum {
201
231
  if (state.isDot || state.isQuestion) {
202
232
  const operator = state.isDot ? funcAt : funcHas;
203
233
  if (state.next().isLiteral && (typeof state.literal.value === "string" || typeof state.literal.value === "bigint")) {
204
- node = this._call(frame.ends(state), operator, [node, new ConstantNode(state, new Constant(state.literal.value))]);
234
+ const fnode = new ConstantNode(frame.ends(state), operator);
235
+ node = new FunctionNode(frame, fnode, [node, new ConstantNode(state, new Constant(state.literal.value))]);
205
236
  state.next();
206
237
  }
207
238
  else if (state.isToken) {
208
- frame.ends(state);
209
239
  const func = this._functions.get(state.token);
210
240
  if (func) {
211
241
  if (state.next().isParenthesesOpen) {
@@ -216,15 +246,18 @@ export class Affinirum {
216
246
  break;
217
247
  }
218
248
  }
219
- node = this._call(frame.ends(state), func, subnodes);
249
+ const fnode = new ConstantNode(frame.ends(state), func);
250
+ node = new FunctionNode(frame, fnode, subnodes);
220
251
  state.closeParentheses().next();
221
252
  }
222
253
  else {
223
- node = this._call(frame, func, [node]);
254
+ const fnode = new ConstantNode(frame.ends(state), func);
255
+ node = new FunctionNode(frame, fnode, [node]);
224
256
  }
225
257
  }
226
258
  else {
227
- node = this._call(frame, operator, [node, new ConstantNode(state, new Constant(state.token))]);
259
+ const fnode = new ConstantNode(frame.ends(), operator);
260
+ node = new FunctionNode(frame, fnode, [node, new ConstantNode(state, new Constant(state.token))]);
228
261
  state.next();
229
262
  }
230
263
  }
@@ -240,11 +273,12 @@ export class Affinirum {
240
273
  break;
241
274
  }
242
275
  }
243
- node = new CallNode(frame.ends(state), node, subnodes);
276
+ node = new FunctionNode(frame.ends(state), node, subnodes);
244
277
  state.closeParentheses().next();
245
278
  }
246
279
  else if (state.isBracketsOpen) {
247
- node = this._call(frame, funcAt, [node, this._unit(state.next(), scope)]);
280
+ const fnode = new ConstantNode(frame, funcAt);
281
+ node = new FunctionNode(frame, fnode, [node, this._unit(state.next(), scope)]);
248
282
  state.closeBrackets().next();
249
283
  }
250
284
  }
@@ -296,19 +330,29 @@ export class Affinirum {
296
330
  }
297
331
  if (state.assignment.operator) {
298
332
  const operator = state.assignment.operator;
299
- const subnodes = [new VariableNode(frame, variable), this._unit(state.next(), scope)];
300
- return new VariableNode(frame, variable, this._call(frame, operator, subnodes));
333
+ const fnode = new ConstantNode(frame, operator);
334
+ const subnode = new FunctionNode(frame, fnode, [new VariableNode(frame, variable), this._unit(state.next(), scope)]);
335
+ return new VariableNode(frame, variable, subnode);
301
336
  }
302
337
  else {
303
- return new VariableNode(frame, variable, this._unit(state.next(), scope));
338
+ const subnode = this._unit(state.next(), scope);
339
+ return new VariableNode(frame, variable, subnode);
304
340
  }
305
341
  }
306
342
  return new VariableNode(frame, variable);
307
343
  }
308
344
  else if (state.isBracesOpen) {
309
- const node = this._block(state.next(), scope);
310
- state.closeBraces().next();
311
- return node;
345
+ const frame = state.starts();
346
+ const subnodes = [];
347
+ while (!state.next().isBracesClose) {
348
+ subnodes.push(this._unit(state, scope));
349
+ if (!state.isSemicolon) {
350
+ break;
351
+ }
352
+ }
353
+ frame.ends(state);
354
+ state.next();
355
+ return new BlockNode(frame, subnodes);
312
356
  }
313
357
  else if (state.isBracesClose) {
314
358
  state.throwError("unexpected closing braces");
@@ -344,7 +388,7 @@ export class Affinirum {
344
388
  }
345
389
  }
346
390
  frame.ends(state);
347
- state.closeBrackets().next();
391
+ state.next();
348
392
  if (colon) {
349
393
  return new ObjectNode(frame, subnodes.map(([k, v]) => [typeof k === "number" ? new ConstantNode(v, new Constant(String(k))) : k, v]));
350
394
  }
@@ -353,8 +397,8 @@ export class Affinirum {
353
397
  else if (state.isBracketsClose) {
354
398
  state.throwError("unexpected closing brackets");
355
399
  }
356
- else if (state.isVariable || state.isValue) {
357
- const assignable = state.isVariable;
400
+ else if (state.isVar || state.isVal) {
401
+ const assignable = state.isVar;
358
402
  if (!state.next().isToken) {
359
403
  state.throwError("missing variable name");
360
404
  }
@@ -380,13 +424,29 @@ export class Affinirum {
380
424
  else if (state.isTilda) {
381
425
  return this._function(state, scope);
382
426
  }
427
+ else if (state.isIf) {
428
+ return this._switch(state, scope);
429
+ }
383
430
  else if (state.isWhile) {
384
431
  return this._loop(state, scope);
385
432
  }
386
- else if (state.isIf) {
387
- return this._switch(state, scope);
433
+ else if (state.isExit) {
434
+ return new JumpNode(state, "exit", this._unit(state.next(), scope));
435
+ }
436
+ else if (state.isReturn) {
437
+ return new JumpNode(state, "return", this._unit(state.next(), scope));
388
438
  }
389
- else if (state.isVoid) {
439
+ else if (state.isStop) {
440
+ const node = new JumpNode(state, "stop");
441
+ state.next();
442
+ return node;
443
+ }
444
+ else if (state.isNext) {
445
+ const node = new JumpNode(state, "next");
446
+ state.next();
447
+ return node;
448
+ }
449
+ else if (state.isEmpty) {
390
450
  state.throwError("unexpected end of expression");
391
451
  }
392
452
  state.throwError("unexpected expression token");
@@ -430,45 +490,29 @@ export class Affinirum {
430
490
  state.closeParentheses();
431
491
  frame.ends(state);
432
492
  const args = Array.from(variables.values());
433
- state.next().openBraces().next();
434
- const subnode = this._block(state, scope.subscope(variables));
435
- state.closeBraces().next();
436
- const value = (...values) => {
493
+ const subnode = this._unit(state.next(), scope.subscope(variables));
494
+ const func = (...values) => {
437
495
  args.forEach((arg, ix) => arg.value = values[ix]);
438
496
  return subnode.evaluate();
439
497
  };
440
- const constant = new Constant(value, Type.functionType(retType, args.map((v) => v.type), variadic));
498
+ const constant = new Constant(func, Type.functionType(retType, args.map((v) => v.type), variadic));
441
499
  return new ConstantNode(frame, constant, subnode);
442
500
  }
443
501
  _loop(state, scope) {
444
502
  const frame = state.starts();
445
503
  const cnode = this._unit(state.next(), scope);
446
504
  frame.ends(state);
447
- state.openBraces().next();
448
- const subnode = this._block(state, scope);
449
- state.closeBraces().next();
450
- return new LoopNode(frame, cnode, subnode);
505
+ return new LoopNode(frame, cnode, this._unit(state, scope));
451
506
  }
452
507
  _switch(state, scope) {
453
508
  const frame = state.starts();
454
509
  const cnode = this._unit(state.next(), scope);
455
510
  frame.ends(state);
456
- const subnodes = [];
457
- state.openBraces().next();
458
- subnodes.push(this._block(state, scope));
459
- state.closeBraces().next();
460
- if (state.isElse) {
461
- state.next().openBraces().next();
462
- subnodes.push(this._block(state, scope));
463
- state.closeBraces().next();
464
- }
465
- else {
466
- subnodes.push(new ConstantNode(state.starts(), Constant.Null));
467
- }
468
- return new SwitchNode(frame, cnode, subnodes);
469
- }
470
- _call(frame, func, subnodes) {
471
- return new CallNode(frame, new ConstantNode(frame, func), subnodes);
511
+ const cthen = this._unit(state, scope);
512
+ const celse = state.isElse
513
+ ? this._unit(state.next(), scope)
514
+ : new ConstantNode(state.starts(), Constant.Null);
515
+ return new SwitchNode(frame, cnode, [cthen, celse]);
472
516
  }
473
517
  _type(state, scope) {
474
518
  if (state.isType) {
package/dst/Jump.d.ts ADDED
@@ -0,0 +1 @@
1
+ export type Jump = "exit" | "return" | "stop" | "next";
package/dst/Jump.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { Jump } from "./Jump.js";
2
+ import { Value } from "./Value.js";
3
+ export declare class JumpException {
4
+ private readonly _jump;
5
+ private readonly _value?;
6
+ constructor(_jump: Jump, _value?: Value);
7
+ get jump(): Jump;
8
+ get value(): Value;
9
+ }
@@ -0,0 +1,14 @@
1
+ export class JumpException {
2
+ _jump;
3
+ _value;
4
+ constructor(_jump, _value) {
5
+ this._jump = _jump;
6
+ this._value = _value;
7
+ }
8
+ get jump() {
9
+ return this._jump;
10
+ }
11
+ get value() {
12
+ return this._value;
13
+ }
14
+ }
package/dst/Keywords.js CHANGED
@@ -10,8 +10,8 @@ export const Keywords = [
10
10
  "array",
11
11
  "object",
12
12
  "function",
13
- "variable", "var",
14
- "constant", "const",
15
- "while",
13
+ "var",
14
+ "val",
16
15
  "if", "else",
16
+ "while", "stop", "next",
17
17
  ];
package/dst/Node.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ParserFrame } from "./ParserFrame.js";
2
- import { Value } from "./Value.js";
3
2
  import { Type } from "./Type.js";
3
+ import { Value } from "./Value.js";
4
4
  export declare abstract class Node extends ParserFrame {
5
5
  constructor(frame: ParserFrame);
6
6
  abstract type: Type;
@@ -34,14 +34,18 @@ export declare class ParserState extends ParserFrame {
34
34
  get isComma(): boolean;
35
35
  get isDot(): boolean;
36
36
  get isQuestion(): boolean;
37
- get isTilda(): boolean;
38
37
  get isEllipsis(): boolean;
39
- get isVariable(): boolean;
40
- get isValue(): boolean;
41
- get isWhile(): boolean;
38
+ get isTilda(): boolean;
39
+ get isVar(): boolean;
40
+ get isVal(): boolean;
42
41
  get isIf(): boolean;
43
42
  get isElse(): boolean;
44
- get isVoid(): boolean;
43
+ get isWhile(): boolean;
44
+ get isExit(): boolean;
45
+ get isReturn(): boolean;
46
+ get isStop(): boolean;
47
+ get isNext(): boolean;
48
+ get isEmpty(): boolean;
45
49
  openParentheses(): this;
46
50
  closeParentheses(): this;
47
51
  openBrackets(): this;