@woosh/meep-engine 2.49.4 → 2.49.7
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/package.json +1 -1
- package/src/core/cache/LoadingCache.d.ts +10 -7
- package/src/core/cache/LoadingCache.js +68 -12
- package/src/core/cache/LoadingCache.spec.js +47 -0
- package/src/core/math/computeGreatestCommonDivisor.spec.js +9 -0
- package/src/core/math/statistics/computeStatisticalMean.spec.js +9 -0
- package/src/core/math/statistics/halton_sequence.spec.js +6 -1
- package/src/core/model/node-graph/node/Port.spec.js +10 -1
- package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +5 -8
- package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.spec.js +41 -0
- package/src/core/model/reactive/trigger/BlackboardTrigger.js +5 -0
- package/src/core/model/reactive/trigger/BlackboardTrigger.spec.js +24 -0
- package/src/core/parser/simple/readBooleanToken.js +38 -0
- package/src/core/parser/simple/readHexToken.js +95 -0
- package/src/core/parser/simple/readHexToken.spec.js +21 -0
- package/src/core/parser/simple/readIdentifierToken.js +43 -0
- package/src/core/parser/simple/readIdentifierToken.spec.js +32 -0
- package/src/core/parser/simple/readLiteralToken.js +96 -0
- package/src/core/parser/simple/readNumberToken.js +73 -0
- package/src/core/parser/simple/readNumberToken.spec.js +17 -0
- package/src/core/parser/simple/readReferenceToken.js +48 -0
- package/src/core/parser/simple/readReferenceToken.spec.js +18 -0
- package/src/core/parser/simple/readStringToken.js +94 -0
- package/src/core/parser/simple/readStringToken.spec.js +57 -0
- package/src/core/parser/simple/{readUnsignedInteger.js → readUnsignedIntegerToken.js} +1 -1
- package/src/core/parser/simple/readUnsignedIntegerToken.spec.js +6 -0
- package/src/core/process/executor/ConcurrentExecutor.js +147 -234
- package/src/engine/intelligence/blackboard/Blackboard.d.ts +4 -0
- package/src/engine/intelligence/blackboard/Blackboard.js +11 -0
- package/src/view/tooltip/gml/parser/readReferenceValueToken.js +2 -2
- package/src/core/parser/simple/SimpleParser.js +0 -464
- package/src/core/parser/simple/SimpleParser.spec.js +0 -105
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
export declare class LoadingCache<K,V>{
|
|
2
|
-
constructor(options:{
|
|
1
|
+
export declare class LoadingCache<K, V> {
|
|
2
|
+
constructor(options: {
|
|
3
3
|
maxWeight?: number,
|
|
4
4
|
keyWeigher?: (key: K) => number,
|
|
5
5
|
valueWeigher?: (value: V) => number,
|
|
6
6
|
keyHashFunction?: (key: K) => number,
|
|
7
7
|
keyEqualityFunction?: (a: K, b: K) => boolean,
|
|
8
8
|
capacity?: number,
|
|
9
|
-
timeToLive?:number,
|
|
10
|
-
load:(key:K)=> Promise<V
|
|
9
|
+
timeToLive?: number,
|
|
10
|
+
load: (key: K) => Promise<V>,
|
|
11
|
+
retryFailed?: boolean
|
|
11
12
|
})
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
refresh(key: K): Promise<V>
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
invalidate(key: K): void
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
clear(): void
|
|
19
|
+
|
|
20
|
+
get(key: K): Promise<V>
|
|
18
21
|
}
|
|
@@ -3,20 +3,25 @@
|
|
|
3
3
|
import { Cache } from "./Cache.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @template
|
|
6
|
+
* @template R
|
|
7
7
|
*/
|
|
8
8
|
class Record {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {
|
|
10
|
+
* @template R
|
|
11
|
+
* @param {R} value
|
|
12
12
|
* @param {number} time
|
|
13
13
|
*/
|
|
14
14
|
constructor(value, time) {
|
|
15
15
|
this.value = value;
|
|
16
16
|
this.time = time;
|
|
17
|
+
this.failed = false;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function current_time_in_seconds() {
|
|
22
|
+
return performance.now() * 1e-3;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
const DEFAULT_TIME_TO_LIVE = 10;
|
|
21
26
|
|
|
22
27
|
/**
|
|
@@ -38,6 +43,12 @@ export class LoadingCache {
|
|
|
38
43
|
*/
|
|
39
44
|
#load
|
|
40
45
|
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @type {boolean}
|
|
49
|
+
*/
|
|
50
|
+
#policyRetryFailed = true;
|
|
51
|
+
|
|
41
52
|
/**
|
|
42
53
|
* @see {@link Cache} for more details on what each parameter means
|
|
43
54
|
* @param maxWeight
|
|
@@ -48,6 +59,7 @@ export class LoadingCache {
|
|
|
48
59
|
* @param capacity
|
|
49
60
|
* @param {number} [timeToLive] in seconds
|
|
50
61
|
* @param load
|
|
62
|
+
* @param {boolean} [retryFailed]
|
|
51
63
|
*/
|
|
52
64
|
constructor({
|
|
53
65
|
maxWeight,
|
|
@@ -57,7 +69,8 @@ export class LoadingCache {
|
|
|
57
69
|
keyEqualityFunction,
|
|
58
70
|
capacity,
|
|
59
71
|
timeToLive = DEFAULT_TIME_TO_LIVE,
|
|
60
|
-
load
|
|
72
|
+
load,
|
|
73
|
+
retryFailed = true
|
|
61
74
|
}) {
|
|
62
75
|
|
|
63
76
|
this.#internal = new Cache({
|
|
@@ -71,6 +84,7 @@ export class LoadingCache {
|
|
|
71
84
|
|
|
72
85
|
this.#timeToLive = timeToLive;
|
|
73
86
|
this.#load = load;
|
|
87
|
+
this.#policyRetryFailed = retryFailed;
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
/**
|
|
@@ -88,24 +102,66 @@ export class LoadingCache {
|
|
|
88
102
|
this.#internal.clear();
|
|
89
103
|
}
|
|
90
104
|
|
|
105
|
+
/**
|
|
106
|
+
*
|
|
107
|
+
* @param {K} key
|
|
108
|
+
* @param {number} timestamp
|
|
109
|
+
* @return {Record<Promise<V>>}
|
|
110
|
+
*/
|
|
111
|
+
#load_value(key, timestamp) {
|
|
112
|
+
|
|
113
|
+
let promise;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
promise = this.#load(key);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
promise = Promise.reject(e);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const record = new Record(promise, timestamp);
|
|
122
|
+
|
|
123
|
+
this.#internal.put(key, record);
|
|
124
|
+
|
|
125
|
+
promise.catch(() => {
|
|
126
|
+
// mark as failure
|
|
127
|
+
record.failed = true;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return record;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Load new value for the key (happens asynchronously)
|
|
135
|
+
* @param {K} key
|
|
136
|
+
* @returns {Promise<V>}
|
|
137
|
+
*/
|
|
138
|
+
refresh(key) {
|
|
139
|
+
const record = this.#load_value(key, current_time_in_seconds());
|
|
140
|
+
|
|
141
|
+
return record.value;
|
|
142
|
+
}
|
|
143
|
+
|
|
91
144
|
/**
|
|
92
145
|
*
|
|
93
146
|
* @param {K} key
|
|
94
147
|
* @return {Promise<V>}
|
|
95
148
|
*/
|
|
96
149
|
async get(key) {
|
|
97
|
-
const currentTime =
|
|
150
|
+
const currentTime = current_time_in_seconds();
|
|
98
151
|
|
|
152
|
+
/**
|
|
153
|
+
*
|
|
154
|
+
* @type {Record<Promise<V>>}
|
|
155
|
+
*/
|
|
99
156
|
let record = this.#internal.get(key);
|
|
100
157
|
|
|
101
|
-
if (record === null
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
record = new Record(promise, currentTime);
|
|
158
|
+
if (record === null
|
|
159
|
+
|| (record.time + this.#timeToLive) < currentTime // timeout
|
|
160
|
+
|| (record.failed && this.#policyRetryFailed) // load failed and we're configured to retry
|
|
161
|
+
) {
|
|
107
162
|
|
|
108
|
-
|
|
163
|
+
// record needs to be loaded
|
|
164
|
+
record = this.#load_value(key, currentTime);
|
|
109
165
|
|
|
110
166
|
}
|
|
111
167
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { LoadingCache } from "./LoadingCache.js";
|
|
2
|
+
import { delay } from "../process/delay.js";
|
|
3
|
+
|
|
4
|
+
test("successful load", async () => {
|
|
5
|
+
const cache = new LoadingCache({
|
|
6
|
+
async load(key) {
|
|
7
|
+
return 17;
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
expect(await cache.get(1)).toEqual(17);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("record reuse", async () => {
|
|
15
|
+
const load = jest.fn(async () => 17);
|
|
16
|
+
|
|
17
|
+
const cache = new LoadingCache({
|
|
18
|
+
load
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(await cache.get(1)).toEqual(17);
|
|
22
|
+
expect(await cache.get(1)).toEqual(17);
|
|
23
|
+
|
|
24
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("timeout reload reuse", async () => {
|
|
28
|
+
|
|
29
|
+
const values = [3, 5, 11];
|
|
30
|
+
|
|
31
|
+
const load = jest.fn(async () => values.pop());
|
|
32
|
+
|
|
33
|
+
const cache = new LoadingCache({
|
|
34
|
+
load,
|
|
35
|
+
timeToLive: 0.00001
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(await cache.get(1)).toEqual(11);
|
|
39
|
+
|
|
40
|
+
await delay(1);
|
|
41
|
+
|
|
42
|
+
expect(await cache.get(1)).toEqual(5);
|
|
43
|
+
|
|
44
|
+
await delay(1);
|
|
45
|
+
|
|
46
|
+
expect(await cache.get(1)).toEqual(3);
|
|
47
|
+
});
|
|
@@ -5,7 +5,12 @@ test("unique values in base 10", () => {
|
|
|
5
5
|
const values = [];
|
|
6
6
|
|
|
7
7
|
for (let i = 0; i < 10; i++) {
|
|
8
|
-
|
|
8
|
+
const v = halton_sequence(10, i);
|
|
9
|
+
|
|
10
|
+
expect(v).toBeGreaterThanOrEqual(0);
|
|
11
|
+
expect(v).toBeLessThanOrEqual(1);
|
|
12
|
+
|
|
13
|
+
values.push(v);
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
values.sort();
|
|
@@ -40,5 +40,14 @@ test("equality works as intended", () => {
|
|
|
40
40
|
b.id = a.id;
|
|
41
41
|
|
|
42
42
|
expect(b.equals(a)).toEqual(true);
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("toString on newly crated instance produces a valid string", () => {
|
|
47
|
+
const port = new Port();
|
|
48
|
+
|
|
49
|
+
const s = port.toString();
|
|
50
|
+
|
|
51
|
+
expect(typeof s).toBe('string');
|
|
52
|
+
expect(s.trim().length).toBeGreaterThan(0); // non-empty
|
|
44
53
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { assert } from "../../../assert.js";
|
|
2
2
|
import { BitSet } from "../../../binary/BitSet.js";
|
|
3
3
|
import { HashMap } from "../../../collection/HashMap.js";
|
|
4
|
-
import { returnZero } from "../../../function/Functions.js";
|
|
5
4
|
import { max2 } from "../../../math/max2.js";
|
|
6
5
|
import DataType from "../../../parser/simple/DataType.js";
|
|
7
6
|
|
|
@@ -95,15 +94,13 @@ class ExpressionNode {
|
|
|
95
94
|
|
|
96
95
|
/**
|
|
97
96
|
*
|
|
98
|
-
* @param {
|
|
99
|
-
* @
|
|
97
|
+
* @param {ReactiveExpression} exp
|
|
98
|
+
* @returns {number}
|
|
100
99
|
*/
|
|
101
|
-
function
|
|
102
|
-
return
|
|
100
|
+
function scoreByTreeSize(exp){
|
|
101
|
+
return exp.computeTreeSize();
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
const temp_array = [];
|
|
106
|
-
|
|
107
104
|
/**
|
|
108
105
|
* Evaluate given state against multiple predicates, order of evaluation is controlled by scoring function, highest score nodes are evaluated first
|
|
109
106
|
*/
|
|
@@ -112,7 +109,7 @@ export class MultiPredicateEvaluator {
|
|
|
112
109
|
*
|
|
113
110
|
* @param {function(ReactiveExpression):number} scoringFunction
|
|
114
111
|
*/
|
|
115
|
-
constructor(scoringFunction =
|
|
112
|
+
constructor(scoringFunction = scoreByTreeSize) {
|
|
116
113
|
/**
|
|
117
114
|
*
|
|
118
115
|
* @type {function(ReactiveExpression): number}
|
|
@@ -9,6 +9,47 @@ import { MultiPredicateEvaluator } from "./MultiPredicateEvaluator.js";
|
|
|
9
9
|
import { randomFromArray } from "../../../math/random/randomFromArray.js";
|
|
10
10
|
import { randomFloatBetween } from "../../../math/random/randomFloatBetween.js";
|
|
11
11
|
import { randomIntegerBetween } from "../../../math/random/randomIntegerBetween.js";
|
|
12
|
+
import { ReactiveEquals } from "../model/comparative/ReactiveEquals.js";
|
|
13
|
+
|
|
14
|
+
test('constructor does not throw', () => {
|
|
15
|
+
expect(() => new MultiPredicateEvaluator()).not.toThrow();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('build empty', () => {
|
|
19
|
+
const processor = new MultiPredicateEvaluator();
|
|
20
|
+
|
|
21
|
+
expect(() => processor.build([])).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('match against empty list of predicates', () => {
|
|
25
|
+
|
|
26
|
+
const processor = new MultiPredicateEvaluator();
|
|
27
|
+
processor.build([]);
|
|
28
|
+
|
|
29
|
+
processor.initialize({});
|
|
30
|
+
|
|
31
|
+
expect(processor.next()).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('match against one predicate', () => {
|
|
35
|
+
|
|
36
|
+
const processor = new MultiPredicateEvaluator();
|
|
37
|
+
|
|
38
|
+
const predicate = ReactiveEquals.from(new ReactiveReference('a'), ReactiveLiteralNumber.from(7));
|
|
39
|
+
|
|
40
|
+
processor.build([predicate]);
|
|
41
|
+
|
|
42
|
+
processor.initialize({ a: 1 });
|
|
43
|
+
|
|
44
|
+
expect(processor.next()).toBeUndefined();
|
|
45
|
+
|
|
46
|
+
processor.finalize();
|
|
47
|
+
processor.initialize({ a: 7 });
|
|
48
|
+
|
|
49
|
+
expect(processor.next()).toBe(predicate);
|
|
50
|
+
expect(processor.next()).toBeUndefined();
|
|
51
|
+
|
|
52
|
+
});
|
|
12
53
|
|
|
13
54
|
test.skip('performance', () => {
|
|
14
55
|
const random = seededRandom(12319);
|
|
@@ -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
|
+
});
|