@woosh/meep-engine 2.49.6 → 2.49.8

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 (85) hide show
  1. package/editor/enableEditor.js +1 -8
  2. package/package.json +1 -1
  3. package/samples/terrain/editor.js +2 -2
  4. package/src/core/cache/Cache.js +1 -1
  5. package/src/core/cache/LoadingCache.d.ts +2 -0
  6. package/src/core/cache/LoadingCache.js +45 -18
  7. package/src/core/collection/HashSet.js +1 -1
  8. package/src/core/collection/table/RowFirstTableSpec.js +110 -92
  9. package/src/core/collection/table/RowFirstTableSpec.spec.js +49 -0
  10. package/src/core/color/Color.d.ts +4 -2
  11. package/src/core/color/Color.js +7 -8
  12. package/src/core/color/Color.spec.js +93 -0
  13. package/src/core/color/hex2rgb.spec.js +10 -0
  14. package/src/core/color/rgb2hex.js +1 -1
  15. package/src/core/color/rgb2hex.spec.js +13 -0
  16. package/src/core/fsm/simple/SimpleStateMachine.spec.js +75 -0
  17. package/src/core/fsm/simple/SimpleStateMachineDescription.js +1 -1
  18. package/src/core/fsm/simple/SimpleStateMachineDescription.spec.js +32 -0
  19. package/src/core/geom/2d/Rectangle.spec.js +51 -0
  20. package/src/core/geom/3d/aabb/aabb3_intersects_ray.spec.js +90 -0
  21. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.spec.js +61 -0
  22. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +1 -34
  23. package/src/core/geom/3d/morton/split_by_2.js +17 -0
  24. package/src/core/geom/3d/morton/split_by_3.js +16 -0
  25. package/src/core/geom/3d/plane/computePlaneLineIntersection.js +6 -1
  26. package/src/core/geom/3d/sphere/sphere_intersects_ray.spec.js +11 -0
  27. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.spec.js +8 -0
  28. package/src/core/geom/3d/topology/samples/sampleFloodFill.js +3 -3
  29. package/src/core/geom/3d/topology/struct/binary/io/OrderedEdge.js +1 -1
  30. package/src/core/geom/random/randomPointOnBox.spec.js +57 -0
  31. package/src/core/math/computeGreatestCommonDivisor.spec.js +9 -0
  32. package/src/core/math/statistics/computeStatisticalMean.spec.js +9 -0
  33. package/src/core/math/statistics/halton_sequence.spec.js +6 -1
  34. package/src/core/model/node-graph/node/Port.spec.js +10 -1
  35. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +5 -8
  36. package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.spec.js +41 -0
  37. package/src/core/model/reactive/trigger/BlackboardTrigger.js +5 -0
  38. package/src/core/model/reactive/trigger/BlackboardTrigger.spec.js +24 -0
  39. package/src/core/parser/simple/readBooleanToken.js +38 -0
  40. package/src/core/parser/simple/readHexToken.js +95 -0
  41. package/src/core/parser/simple/readHexToken.spec.js +21 -0
  42. package/src/core/parser/simple/readIdentifierToken.js +43 -0
  43. package/src/core/parser/simple/readIdentifierToken.spec.js +32 -0
  44. package/src/core/parser/simple/readLiteralToken.js +96 -0
  45. package/src/core/parser/simple/readNumberToken.js +73 -0
  46. package/src/core/parser/simple/readNumberToken.spec.js +17 -0
  47. package/src/core/parser/simple/readReferenceToken.js +48 -0
  48. package/src/core/parser/simple/readReferenceToken.spec.js +18 -0
  49. package/src/core/parser/simple/readStringToken.js +94 -0
  50. package/src/core/parser/simple/readStringToken.spec.js +57 -0
  51. package/src/core/parser/simple/{readUnsignedInteger.js → readUnsignedIntegerToken.js} +1 -1
  52. package/src/core/parser/simple/readUnsignedIntegerToken.spec.js +6 -0
  53. package/src/core/process/executor/ConcurrentExecutor.js +147 -234
  54. package/src/engine/Engine.d.ts +4 -2
  55. package/src/engine/Engine.js +62 -41
  56. package/src/engine/Engine.spec.js +11 -0
  57. package/src/engine/InputEngine.js +12 -0
  58. package/src/engine/graphics/GraphicsEngine.js +2 -13
  59. package/src/engine/graphics/ecs/compileAllMaterials.js +1 -1
  60. package/src/engine/graphics/ecs/mesh/createTaskWaitForMeshesToLoad.js +1 -1
  61. package/src/engine/graphics/impostors/octahedral/ImpostorBaker.js +1 -1
  62. package/src/engine/graphics/load_and_set_cubemap_v0.js +21 -0
  63. package/src/engine/graphics/material/optimization/MaterialOptimizationContext.js +1 -1
  64. package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +3 -5
  65. package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +2 -4
  66. package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
  67. package/src/engine/graphics/render/forward_plus/prototype/prototypeLightManager.js +1 -1
  68. package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +2 -4
  69. package/src/engine/graphics/sh3/prototypeSH3Probe.js +4 -4
  70. package/src/engine/graphics/texture/sampler/copy_Sampler2D_channel_data.js +4 -3
  71. package/src/engine/graphics/texture/virtual/VirtualTexture.js +9 -0
  72. package/src/engine/graphics/texture/virtual/VirtualTexture.spec.js +5 -4
  73. package/src/engine/graphics/texture/virtual/tile/TileLoader.js +5 -0
  74. package/src/engine/input/devices/PointerDevice.spec.js +7 -0
  75. package/src/engine/intelligence/blackboard/Blackboard.d.ts +4 -0
  76. package/src/engine/intelligence/blackboard/Blackboard.js +11 -0
  77. package/src/engine/platform/InMemoryEnginePlatform.js +20 -0
  78. package/src/engine/save/Storage.d.ts +7 -7
  79. package/src/engine/save/storage/InMemoryStorage.js +34 -0
  80. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +2 -2
  81. package/src/view/tooltip/gml/parser/readReferenceValueToken.js +2 -2
  82. package/src/core/parser/simple/SimpleParser.js +0 -464
  83. package/src/core/parser/simple/SimpleParser.spec.js +0 -105
  84. /package/src/core/collection/{IteratorUtils.js → collectIteratorValueToArray.js} +0 -0
  85. /package/src/core/color/{ColorUtils.js → parseColor.js} +0 -0
@@ -56,6 +56,11 @@ export class BlackboardTrigger {
56
56
 
57
57
  if (this.isLinked) {
58
58
  //already linked
59
+
60
+ if (this.blackboard !== blackboard) {
61
+ throw new Error(`Already linked to another blackboard`);
62
+ }
63
+
59
64
  return;
60
65
  }
61
66
 
@@ -0,0 +1,24 @@
1
+ import { BlackboardTrigger } from "./BlackboardTrigger.js";
2
+ import { Blackboard } from "../../../../engine/intelligence/blackboard/Blackboard.js";
3
+
4
+ test("constructor does not throw", () => {
5
+ expect(() => new BlackboardTrigger()).not.toThrow();
6
+ });
7
+
8
+ test("numeric equality", () => {
9
+
10
+ const trigger = new BlackboardTrigger();
11
+ trigger.code = "a == 7";
12
+
13
+ const bb = Blackboard.fromJSON({
14
+ a: 7
15
+ });
16
+
17
+ trigger.link(bb);
18
+
19
+ expect(trigger.getExpression().getValue()).toBe(true);
20
+
21
+ bb.acquireNumber("a").set(1);
22
+
23
+ expect(trigger.getExpression().getValue()).toBe(false);
24
+ });
@@ -0,0 +1,38 @@
1
+ import ParserError from "./ParserError.js";
2
+ import Token from "./Token.js";
3
+ import TokenType from "./TokenType.js";
4
+ import DataType from "./DataType.js";
5
+
6
+ /**
7
+ *
8
+ * @param {string} text
9
+ * @param {number} cursor
10
+ * @param {number} length
11
+ * @returns {Token}
12
+ */
13
+ export function readBooleanToken(text, cursor, length) {
14
+ const firstChar = text.charAt(cursor);
15
+
16
+ let value;
17
+ let end = cursor;
18
+
19
+ if (firstChar === 't' && length - cursor >= 4) {
20
+ if (text.substring(cursor + 1, cursor + 4) === 'rue') {
21
+ value = true;
22
+ end = cursor + 4;
23
+ } else {
24
+ throw new ParserError(cursor, `expected 'true', instead got '${text.substring(cursor, cursor + 4)}'`, text);
25
+ }
26
+ } else if (firstChar === 'f' && length - cursor >= 5) {
27
+ if (text.substring(cursor + 1, cursor + 5) === 'alse') {
28
+ value = false;
29
+ end = cursor + 5;
30
+ } else {
31
+ throw new ParserError(cursor, `expected 'false', instead got '${text.substring(cursor, cursor + 5)}'`, text);
32
+ }
33
+ } else {
34
+ throw new ParserError(cursor, `expected 't' or 'f', instead got '${firstChar}'`, text);
35
+ }
36
+
37
+ return new Token(value, cursor, end, TokenType.LiteralBoolean, DataType.Boolean);
38
+ }
@@ -0,0 +1,95 @@
1
+ import ParserError from "./ParserError.js";
2
+ import Token from "./Token.js";
3
+ import DataType from "./DataType.js";
4
+
5
+ /**
6
+ *
7
+ * @param {string} text
8
+ * @param {number} cursor
9
+ * @param {number} length
10
+ * @returns {Token}
11
+ */
12
+ export function readHexToken(text, cursor, length) {
13
+ const c0 = text.charAt(cursor);
14
+ const c1 = text.charAt(cursor + 1);
15
+
16
+ if (c0 !== '0' || !(c1 === 'x' || c1 === 'X')) {
17
+ throw new ParserError(cursor, 'Expected hex prefix 0x', text);
18
+ }
19
+
20
+ let i = cursor + 2;
21
+ let value = 0;
22
+
23
+ main_loop: for (; i < length;) {
24
+ const char = text.charAt(i);
25
+ let digit;
26
+ switch (char) {
27
+ case '0':
28
+ digit = 0;
29
+ break;
30
+ case '1':
31
+ digit = 1;
32
+ break;
33
+ case '2':
34
+ digit = 2;
35
+ break;
36
+ case '3':
37
+ digit = 3;
38
+ break;
39
+ case '4':
40
+ digit = 4;
41
+ break;
42
+ case '5':
43
+ digit = 5;
44
+ break;
45
+ case '6':
46
+ digit = 6;
47
+ break;
48
+ case '7':
49
+ digit = 7;
50
+ break;
51
+ case '8':
52
+ digit = 8;
53
+ break;
54
+ case '9':
55
+ digit = 9;
56
+ break;
57
+ case 'a':
58
+ case 'A':
59
+ digit = 10;
60
+ break;
61
+ case 'b':
62
+ case 'B':
63
+ digit = 11;
64
+ break;
65
+ case 'c':
66
+ case 'C':
67
+ digit = 12;
68
+ break;
69
+ case 'd':
70
+ case 'D':
71
+ digit = 13;
72
+ break;
73
+ case 'e':
74
+ case 'E':
75
+ digit = 14;
76
+ break;
77
+ case 'f':
78
+ case 'F':
79
+ digit = 15;
80
+ break;
81
+ default:
82
+ if (i === cursor) {
83
+ //first character is not a digit
84
+ throw new ParserError(i, `Expected a digit [0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F] but got '${char}' instead`, text);
85
+ }
86
+ //not a digit
87
+ break main_loop;
88
+ }
89
+ i++;
90
+ value = value * 16 + digit;
91
+ }
92
+
93
+
94
+ return new Token(value, cursor, i, null, DataType.Number);
95
+ }
@@ -0,0 +1,21 @@
1
+ import { readHexToken } from "./readHexToken.js";
2
+
3
+ test("0x7", () => {
4
+ const token = readHexToken("0x7", 0, 3);
5
+ expect(token.value).toBe(7);
6
+ });
7
+
8
+ test("0xFF", () => {
9
+ const token = readHexToken("0xFF", 0, 4);
10
+ expect(token.value).toBe(0xFF);
11
+ });
12
+
13
+ test("0x1234567890", () => {
14
+ const token = readHexToken("0x1234567890", 0, 12);
15
+ expect(token.value).toBe(0x1234567890);
16
+ });
17
+
18
+ test("0xABCDEF", () => {
19
+ const token = readHexToken("0xABCDEF", 0, 8);
20
+ expect(token.value).toBe(0xABCDEF);
21
+ });
@@ -0,0 +1,43 @@
1
+ import ParserError from "./ParserError.js";
2
+ import Token from "./Token.js";
3
+ import TokenType from "./TokenType.js";
4
+ import DataType from "./DataType.js";
5
+
6
+ const RX_IDENTIFIER_CHAR = /^[a-zA-Z0-9_]/;
7
+ /**
8
+ * @readonly
9
+ * @type {RegExp}
10
+ */
11
+ export const RX_IDENTIFIER_FIRST_CHAR = /^[a-zA-Z_]/;
12
+
13
+ /**
14
+ * Read C99 style IDENTIFIER token
15
+ * @param {string} text
16
+ * @param {number} cursor
17
+ * @param {number} length
18
+ * @returns {Token}
19
+ */
20
+ export function readIdentifierToken(text, cursor, length) {
21
+ let i = cursor;
22
+
23
+
24
+ const firstChar = text.charAt(i);
25
+
26
+ if (!RX_IDENTIFIER_FIRST_CHAR.test(firstChar)) {
27
+ throw new ParserError(i, `Expected first character to match /${RX_IDENTIFIER_FIRST_CHAR.toString()}/, instead got '${firstChar}'`, text);
28
+ }
29
+
30
+ i++;
31
+
32
+ for (; i < length; i++) {
33
+ const char = text.charAt(i);
34
+
35
+ if (!RX_IDENTIFIER_CHAR.test(char)) {
36
+ break;
37
+ }
38
+ }
39
+
40
+ const value = text.substring(cursor, i);
41
+
42
+ return new Token(value, cursor, i, TokenType.Identifier, DataType.String);
43
+ }
@@ -0,0 +1,32 @@
1
+ import { readIdentifierToken } from "./readIdentifierToken.js";
2
+
3
+ test('parse identifier token starting with a number', () => {
4
+ expect(() => readIdentifierToken('0', 0, 1)).toThrow();
5
+ expect(() => readIdentifierToken('1', 0, 1)).toThrow();
6
+ expect(() => readIdentifierToken('2', 0, 1)).toThrow();
7
+ expect(() => readIdentifierToken('3', 0, 1)).toThrow();
8
+ expect(() => readIdentifierToken('4', 0, 1)).toThrow();
9
+ expect(() => readIdentifierToken('5', 0, 1)).toThrow();
10
+ expect(() => readIdentifierToken('6', 0, 1)).toThrow();
11
+ expect(() => readIdentifierToken('7', 0, 1)).toThrow();
12
+ expect(() => readIdentifierToken('8', 0, 1)).toThrow();
13
+ expect(() => readIdentifierToken('9', 0, 1)).toThrow();
14
+
15
+
16
+ expect(() => readIdentifierToken('9a', 0, 2)).toThrow();
17
+ });
18
+
19
+ test('parse identifier token with length 1', () => {
20
+ expect(readIdentifierToken('a', 0, 1).value).toBe('a');
21
+ expect(readIdentifierToken('A', 0, 1).value).toBe('A');
22
+ expect(readIdentifierToken('z', 0, 1).value).toBe('z');
23
+ expect(readIdentifierToken('Z', 0, 1).value).toBe('Z');
24
+ expect(readIdentifierToken('_', 0, 1).value).toBe('_');
25
+ });
26
+
27
+ test('parse identifier valid tokens', () => {
28
+ expect(readIdentifierToken('aa', 0, 2).value).toBe('aa');
29
+ expect(readIdentifierToken('a1', 0, 2).value).toBe('a1');
30
+ expect(readIdentifierToken('z ', 0, 2).value).toBe('z');
31
+ expect(readIdentifierToken('___ ', 0, 4).value).toBe('___');
32
+ });
@@ -0,0 +1,96 @@
1
+ import { skipWhitespace } from "./skipWhitespace.js";
2
+ import Token from "./Token.js";
3
+ import DataType from "./DataType.js";
4
+ import ParserError from "./ParserError.js";
5
+ import { readBooleanToken } from "./readBooleanToken.js";
6
+ import { readStringToken } from "./readStringToken.js";
7
+ import { readNumberToken } from "./readNumberToken.js";
8
+
9
+ /**
10
+ *
11
+ * @param {string} text
12
+ * @param {number} cursor
13
+ * @param {number} length
14
+ * @return {Token}
15
+ */
16
+ function readArrayLiteral(text, cursor, length) {
17
+ let i = cursor;
18
+
19
+ const firstChar = text.charAt(i);
20
+ if (firstChar !== '[') {
21
+ throw new ParserError(cursor, `expected array start: '[', got '${firstChar}' instead`, text);
22
+ }
23
+
24
+ i++;
25
+
26
+ const values = [];
27
+ while (i < length) {
28
+ i = skipWhitespace(text, i, length);
29
+
30
+ const token = readLiteralToken(text, i, length);
31
+
32
+ i = token.end;
33
+
34
+ values.push(token);
35
+
36
+ //try find separator
37
+ i = skipWhitespace(text, i, length);
38
+ if (i < length) {
39
+ const char = text.charAt(i);
40
+ if (char === ',') {
41
+ //separator
42
+ i++;
43
+ } else if (char === ']') {
44
+ //end of sequence
45
+ i++;
46
+ break;
47
+ } else {
48
+ //unexpected input
49
+ throw new ParserError(i, `Unexpected input '${char}', expected either separator ',' or end of sequence ']'`, text);
50
+ }
51
+ } else {
52
+ throw new ParserError(i, `Unterminated array literal`, text);
53
+ }
54
+ }
55
+
56
+ return new Token(values, cursor, i, 'array', DataType.Array);
57
+ }
58
+
59
+ /**
60
+ *
61
+ * @param {string} text
62
+ * @param {number} cursor
63
+ * @param {number} length
64
+ * @returns {Token}
65
+ */
66
+ function readLiteralToken(text, cursor, length) {
67
+ const firstChar = text.charAt(cursor);
68
+
69
+ switch (firstChar) {
70
+ case 't':
71
+ case 'f':
72
+ return readBooleanToken(text, cursor, length);
73
+ case '\"':
74
+ case "\'":
75
+ return readStringToken(text, cursor, length);
76
+ case '-':
77
+ case '+':
78
+ case '0':
79
+ case '1':
80
+ case '2':
81
+ case '3':
82
+ case '4':
83
+ case '5':
84
+ case '6':
85
+ case '7':
86
+ case '8':
87
+ case '9':
88
+ return readNumberToken(text, cursor, length);
89
+ case '[':
90
+ return readArrayLiteral(text, cursor, length);
91
+ default:
92
+ throw new ParserError(cursor, "Expected literal start, but found '" + firstChar + "'", text);
93
+ }
94
+ }
95
+
96
+ export { readLiteralToken };
@@ -0,0 +1,73 @@
1
+ import { skipWhitespace } from "./skipWhitespace.js";
2
+ import { readHexToken } from "./readHexToken.js";
3
+ import { readUnsignedIntegerToken } from "./readUnsignedIntegerToken.js";
4
+ import Token from "./Token.js";
5
+ import TokenType from "./TokenType.js";
6
+ import DataType from "./DataType.js";
7
+
8
+ /**
9
+ *
10
+ * @param {string} text
11
+ * @param {number} cursor
12
+ * @param {number} length
13
+ * @returns {Token}
14
+ */
15
+ export function readNumberToken(text, cursor, length) {
16
+ let i = cursor;
17
+
18
+ //read optional sign
19
+ function readSign(text) {
20
+ let sign = 1;
21
+ const firstChar = text.charAt(i);
22
+ if (firstChar === '-') {
23
+ sign = -1;
24
+ } else if (firstChar === '+') {
25
+ sign = 1;
26
+ } else {
27
+ return 1;
28
+ }
29
+
30
+ i = skipWhitespace(text, i + 1, length);
31
+
32
+ return sign;
33
+ }
34
+
35
+ const sign = readSign(text);
36
+
37
+ let value;
38
+
39
+ if (text.charAt(i) === '0' && (text.charAt(i + 1) === 'x' || text.charAt(i + 1) === 'X')) {
40
+ // hex number
41
+ const token = readHexToken(text, i, length);
42
+
43
+ i = token.end;
44
+
45
+ value = token.value;
46
+
47
+ } else {
48
+
49
+ const wholePart = readUnsignedIntegerToken(text, i, length);
50
+
51
+ i = wholePart.end;
52
+
53
+ value = wholePart.value;
54
+
55
+ if (i < length) {
56
+ const dot = text.charAt(i);
57
+ if (dot === '.') {
58
+ i++;
59
+ const fraction = readUnsignedIntegerToken(text, i, length);
60
+
61
+ const digits = (fraction.end - fraction.start);
62
+
63
+ value += fraction.value / Math.pow(10, digits);
64
+
65
+ i = fraction.end;
66
+ }
67
+ }
68
+ }
69
+
70
+ value *= sign;
71
+
72
+ return new Token(value, cursor, i, TokenType.LiteralNumber, DataType.Number);
73
+ }
@@ -0,0 +1,17 @@
1
+ import { readNumberToken } from "./readNumberToken.js";
2
+
3
+ test("hex", () => {
4
+ expect(readNumberToken("0x3", 0, 3).value).toBe(3);
5
+ });
6
+
7
+ test("signed integer", () => {
8
+ expect(readNumberToken("+3", 0, 2).value).toBe(3);
9
+ expect(readNumberToken("3", 0, 1).value).toBe(3);
10
+ expect(readNumberToken("-7", 0, 2).value).toBe(-7);
11
+ });
12
+
13
+ test("decimal", () => {
14
+ expect(readNumberToken("1.0", 0, 3).value).toBe(1);
15
+ expect(readNumberToken("0.1", 0, 3).value).toBe(0.1);
16
+ expect(readNumberToken("-0.830", 0, 6).value).toBe(-0.830);
17
+ });
@@ -0,0 +1,48 @@
1
+ import DataType from "./DataType.js";
2
+ import Token from "./Token.js";
3
+ import TokenType from "./TokenType.js";
4
+ import { readIdentifierToken } from "./readIdentifierToken.js";
5
+
6
+
7
+ /**
8
+ *
9
+ * @param {String} text
10
+ * @param {number} cursor
11
+ * @param {number} length
12
+ * @returns {Token}
13
+ */
14
+ export function readReferenceToken(text, cursor, length) {
15
+ let i = cursor;
16
+
17
+ const identifiers = [];
18
+
19
+ let identifier;
20
+
21
+ identifier = readIdentifierToken(text, i, length);
22
+
23
+ identifiers.push(identifier);
24
+
25
+ i = identifier.end;
26
+
27
+ for (; ;) {
28
+
29
+ const firstChar = text.charAt(i);
30
+
31
+ if (firstChar !== '.') {
32
+ break;
33
+ }
34
+
35
+ //skip over the separator
36
+ i++;
37
+
38
+ identifier = readIdentifierToken(text, i, length);
39
+
40
+ identifiers.push(identifier);
41
+
42
+ i = identifier.end;
43
+ }
44
+
45
+ return new Token(identifiers, cursor, i, TokenType.Reference, DataType.String);
46
+ }
47
+
48
+
@@ -0,0 +1,18 @@
1
+ import { readReferenceToken } from "./readReferenceToken.js";
2
+ import TokenType from "./TokenType.js";
3
+
4
+ test('parse reference a.b', () => {
5
+ const token = readReferenceToken('a.b', 0, 3);
6
+
7
+ expect(token.end).toBe(3);
8
+ expect(token.start).toBe(0);
9
+ expect(token.name).toBe(TokenType.Reference);
10
+
11
+ const identifiers = token.value;
12
+
13
+ expect(Array.isArray(identifiers)).toBe(true);
14
+ expect(identifiers.length).toBe(2);
15
+
16
+ expect(identifiers[0].value).toBe('a');
17
+ expect(identifiers[1].value).toBe('b');
18
+ });
@@ -0,0 +1,94 @@
1
+ import ParserError from "./ParserError.js";
2
+ import Token from "./Token.js";
3
+ import TokenType from "./TokenType.js";
4
+ import DataType from "./DataType.js";
5
+
6
+ /**
7
+ *
8
+ * @param {string} text
9
+ * @param {number} cursor
10
+ * @returns {string}
11
+ */
12
+ function readQuote(text, cursor) {
13
+ const char = text.charAt(cursor);
14
+
15
+ if (char !== '"' && char !== '\'') {
16
+ throw new ParserError(cursor, "Expected \", found " + char + " instead", text);
17
+ }
18
+
19
+ return char;
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @param {string} text
25
+ * @param {number} cursor
26
+ * @param {number} length
27
+ * @returns {Token}
28
+ */
29
+ export function readStringToken(text, cursor, length) {
30
+ let i = cursor;
31
+
32
+ const openingQuote = readQuote(text, i);
33
+
34
+ i++;
35
+
36
+ let char;
37
+
38
+ let value = '';
39
+ const lastPossibleChar = length - 1;
40
+
41
+ for (; i < lastPossibleChar; i++) {
42
+ char = text.charAt(i);
43
+
44
+ if (char === '\\') {
45
+ i++;
46
+ //read escape sequence
47
+ char = text.charAt(i);
48
+
49
+ switch (char) {
50
+ case 'n':
51
+ value += '\n';
52
+ break;
53
+ case 't':
54
+ value += '\t';
55
+ break;
56
+ case 'r':
57
+ value += '\r';
58
+ break;
59
+ case 'b':
60
+ value += '\b';
61
+ break;
62
+ case 'f':
63
+ value += '\f';
64
+ break;
65
+ case 'v':
66
+ value += '\v';
67
+ break;
68
+ case '0':
69
+ value += '\0';
70
+ break;
71
+ case '\\':
72
+ case "'":
73
+ case '"':
74
+ default:
75
+ value += char;
76
+ break;
77
+ }
78
+ } else if (char !== openingQuote) {
79
+ value += char;
80
+ } else {
81
+ break;
82
+ }
83
+ }
84
+
85
+ char = text.charAt(i);
86
+
87
+ if (char !== openingQuote) {
88
+ throw new ParserError(cursor, "Expected string terminator : " + openingQuote + ", but found '" + char + "' instead", text);
89
+ }
90
+
91
+ i++;
92
+
93
+ return new Token(value, cursor, i, TokenType.LiteralString, DataType.String);
94
+ }
@@ -0,0 +1,57 @@
1
+ import { readStringToken } from "./readStringToken.js";
2
+
3
+ test('parse empty string literal', () => {
4
+ const token = readStringToken('""', 0, 2);
5
+
6
+ expect(token.value).toBe("");
7
+ expect(token.start).toBe(0);
8
+ expect(token.end).toBe(2);
9
+ });
10
+
11
+ test('parse short string literal', () => {
12
+ const token = readStringToken('"1"', 0, 3);
13
+
14
+ expect(token.value).toBe("1");
15
+ expect(token.start).toBe(0);
16
+ expect(token.end).toBe(3);
17
+ });
18
+
19
+ test('parse short string literal from longer input', () => {
20
+ const token = readStringToken('"1" ', 0, 5);
21
+
22
+ expect(token.value).toBe("1");
23
+ expect(token.start).toBe(0);
24
+ expect(token.end).toBe(3);
25
+ });
26
+
27
+ test('parse long string literal', () => {
28
+ const token = readStringToken('"hello world"', 0, 13);
29
+
30
+ expect(token.value).toBe("hello world");
31
+ expect(token.start).toBe(0);
32
+ expect(token.end).toBe(13);
33
+ });
34
+
35
+ test('parse a single escaped quote string literal', () => {
36
+ const token = readStringToken('"\\""', 0, 4);
37
+
38
+ expect(token.value).toBe('\"');
39
+ expect(token.start).toBe(0);
40
+ expect(token.end).toBe(4);
41
+ });
42
+
43
+ test('parse string literal with escaped quotes', () => {
44
+ const token = readStringToken('"what is \\"?"', 0, 13);
45
+
46
+ expect(token.value).toBe('what is "?');
47
+ expect(token.start).toBe(0);
48
+ expect(token.end).toBe(13);
49
+ });
50
+
51
+ test('parse string literal sequential escape sequences', () => {
52
+ const token = readStringToken('"\\n\\t"', 0, 6);
53
+
54
+ expect(token.value).toBe('\n\t');
55
+ expect(token.start).toBe(0);
56
+ expect(token.end).toBe(6);
57
+ });
@@ -9,7 +9,7 @@ import DataType from "./DataType.js";
9
9
  * @param {number} length
10
10
  * @returns {Token}
11
11
  */
12
- export function readUnsignedInteger(text, cursor, length) {
12
+ export function readUnsignedIntegerToken(text, cursor, length) {
13
13
  let i = cursor;
14
14
 
15
15
  let value = 0;