@graffiticode/parser 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/package.json +27 -0
- package/src/fold.js +235 -0
- package/src/index.js +1 -0
- package/src/parse.js +2177 -0
- package/src/parser.js +97 -0
- package/src/parser.js~ +97 -0
- package/src/parser.spec.js +175 -0
- package/src/parser.spec.js~ +175 -0
- package/src/testing/index.js +16 -0
package/src/parser.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import vm from "vm";
|
|
2
|
+
import { getLangAsset } from "../../api/src/lang/index.js";
|
|
3
|
+
import { parse } from "./parse.js";
|
|
4
|
+
|
|
5
|
+
// commonjs export
|
|
6
|
+
const main = {
|
|
7
|
+
parse(src, lexicon) {
|
|
8
|
+
const stream = new parse.StringStream(src);
|
|
9
|
+
const state = {
|
|
10
|
+
cc: parse.program, // top level parsing function
|
|
11
|
+
argc: 0,
|
|
12
|
+
argcStack: [0],
|
|
13
|
+
paramc: 0,
|
|
14
|
+
paramcStack: [0],
|
|
15
|
+
env: [{ name: "global", lexicon }],
|
|
16
|
+
exprc: 0,
|
|
17
|
+
exprcStack: [0],
|
|
18
|
+
nodeStack: [],
|
|
19
|
+
nodeStackStack: [],
|
|
20
|
+
nodePool: ["unused"],
|
|
21
|
+
nodeMap: {},
|
|
22
|
+
nextToken: -1,
|
|
23
|
+
errors: [],
|
|
24
|
+
coords: [],
|
|
25
|
+
inStr: 0,
|
|
26
|
+
quoteCharStack: []
|
|
27
|
+
};
|
|
28
|
+
const next = function () {
|
|
29
|
+
return parse.parse(stream, state);
|
|
30
|
+
};
|
|
31
|
+
let ast;
|
|
32
|
+
while (state.cc !== null && stream.peek()) {
|
|
33
|
+
ast = next();
|
|
34
|
+
}
|
|
35
|
+
if (state.cc) {
|
|
36
|
+
throw new Error("End of program reached.");
|
|
37
|
+
}
|
|
38
|
+
console.log(
|
|
39
|
+
"parse()",
|
|
40
|
+
"ast=" + JSON.stringify(ast, null, 2),
|
|
41
|
+
);
|
|
42
|
+
return ast;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const buildParser = ({
|
|
47
|
+
log,
|
|
48
|
+
cache,
|
|
49
|
+
getLangAsset,
|
|
50
|
+
main,
|
|
51
|
+
vm
|
|
52
|
+
}) => {
|
|
53
|
+
return {
|
|
54
|
+
async parse(lang, src) {
|
|
55
|
+
if (!cache.has(lang)) {
|
|
56
|
+
let data = await getLangAsset(lang, "/lexicon.js");
|
|
57
|
+
// TODO Make lexicon JSON.
|
|
58
|
+
if (data instanceof Buffer) {
|
|
59
|
+
data = data.toString();
|
|
60
|
+
}
|
|
61
|
+
if (typeof (data) !== "string") {
|
|
62
|
+
log(`Failed to get usable lexicon for ${lang}`, typeof (data), data);
|
|
63
|
+
throw new Error("unable to use lexicon");
|
|
64
|
+
}
|
|
65
|
+
const lstr = data.substring(data.indexOf("{"));
|
|
66
|
+
let lexicon;
|
|
67
|
+
try {
|
|
68
|
+
lexicon = JSON.parse(lstr);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof SyntaxError) {
|
|
71
|
+
log(`failed to parse ${lang} lexicon: ${err.message}`);
|
|
72
|
+
const context = { window: { gcexports: {} } };
|
|
73
|
+
vm.createContext(context);
|
|
74
|
+
vm.runInContext(data, context);
|
|
75
|
+
if (typeof (context.window.gcexports.globalLexicon) === "object") {
|
|
76
|
+
lexicon = context.window.gcexports.globalLexicon;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!lexicon) {
|
|
80
|
+
throw new Error("Malformed lexicon");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
cache.set(lang, lexicon);
|
|
84
|
+
};
|
|
85
|
+
const lexicon = cache.get(lang);
|
|
86
|
+
return await main.parse(src, lexicon);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const parser = buildParser({
|
|
92
|
+
log: console.log,
|
|
93
|
+
cache: new Map(),
|
|
94
|
+
getLangAsset,
|
|
95
|
+
main,
|
|
96
|
+
vm
|
|
97
|
+
});
|
package/src/parser.js~
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import vm from "vm";
|
|
2
|
+
import { getLangAsset } from "../../../api/src/lang/index.js";
|
|
3
|
+
import { parse } from "./parse.js";
|
|
4
|
+
|
|
5
|
+
// commonjs export
|
|
6
|
+
const main = {
|
|
7
|
+
parse(src, lexicon) {
|
|
8
|
+
const stream = new parse.StringStream(src);
|
|
9
|
+
const state = {
|
|
10
|
+
cc: parse.program, // top level parsing function
|
|
11
|
+
argc: 0,
|
|
12
|
+
argcStack: [0],
|
|
13
|
+
paramc: 0,
|
|
14
|
+
paramcStack: [0],
|
|
15
|
+
env: [{ name: "global", lexicon }],
|
|
16
|
+
exprc: 0,
|
|
17
|
+
exprcStack: [0],
|
|
18
|
+
nodeStack: [],
|
|
19
|
+
nodeStackStack: [],
|
|
20
|
+
nodePool: ["unused"],
|
|
21
|
+
nodeMap: {},
|
|
22
|
+
nextToken: -1,
|
|
23
|
+
errors: [],
|
|
24
|
+
coords: [],
|
|
25
|
+
inStr: 0,
|
|
26
|
+
quoteCharStack: []
|
|
27
|
+
};
|
|
28
|
+
const next = function () {
|
|
29
|
+
return parse.parse(stream, state);
|
|
30
|
+
};
|
|
31
|
+
let ast;
|
|
32
|
+
while (state.cc !== null && stream.peek()) {
|
|
33
|
+
ast = next();
|
|
34
|
+
}
|
|
35
|
+
if (state.cc) {
|
|
36
|
+
throw new Error("End of program reached.");
|
|
37
|
+
}
|
|
38
|
+
console.log(
|
|
39
|
+
"parse()",
|
|
40
|
+
"ast=" + JSON.stringify(ast, null, 2),
|
|
41
|
+
);
|
|
42
|
+
return ast;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const buildParser = ({
|
|
47
|
+
log,
|
|
48
|
+
cache,
|
|
49
|
+
getLangAsset,
|
|
50
|
+
main,
|
|
51
|
+
vm
|
|
52
|
+
}) => {
|
|
53
|
+
return {
|
|
54
|
+
async parse(lang, src) {
|
|
55
|
+
if (!cache.has(lang)) {
|
|
56
|
+
let data = await getLangAsset(lang, "/lexicon.js");
|
|
57
|
+
// TODO Make lexicon JSON.
|
|
58
|
+
if (data instanceof Buffer) {
|
|
59
|
+
data = data.toString();
|
|
60
|
+
}
|
|
61
|
+
if (typeof (data) !== "string") {
|
|
62
|
+
log(`Failed to get usable lexicon for ${lang}`, typeof (data), data);
|
|
63
|
+
throw new Error("unable to use lexicon");
|
|
64
|
+
}
|
|
65
|
+
const lstr = data.substring(data.indexOf("{"));
|
|
66
|
+
let lexicon;
|
|
67
|
+
try {
|
|
68
|
+
lexicon = JSON.parse(lstr);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof SyntaxError) {
|
|
71
|
+
log(`failed to parse ${lang} lexicon: ${err.message}`);
|
|
72
|
+
const context = { window: { gcexports: {} } };
|
|
73
|
+
vm.createContext(context);
|
|
74
|
+
vm.runInContext(data, context);
|
|
75
|
+
if (typeof (context.window.gcexports.globalLexicon) === "object") {
|
|
76
|
+
lexicon = context.window.gcexports.globalLexicon;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!lexicon) {
|
|
80
|
+
throw new Error("Malformed lexicon");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
cache.set(lang, lexicon);
|
|
84
|
+
};
|
|
85
|
+
const lexicon = cache.get(lang);
|
|
86
|
+
return await main.parse(src, lexicon);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const parser = buildParser({
|
|
92
|
+
log: console.log,
|
|
93
|
+
cache: new Map(),
|
|
94
|
+
getLangAsset,
|
|
95
|
+
main,
|
|
96
|
+
vm
|
|
97
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { parser, buildParser } from "./parser.js";
|
|
3
|
+
import { mockPromiseValue, mockPromiseError } from "./testing/index.js";
|
|
4
|
+
|
|
5
|
+
describe("lang/parser", () => {
|
|
6
|
+
const log = jest.fn();
|
|
7
|
+
it("should call main parser language lexicon", async () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
11
|
+
const main = {
|
|
12
|
+
parse: mockPromiseValue({ root: "0" })
|
|
13
|
+
};
|
|
14
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
15
|
+
const lang = "0";
|
|
16
|
+
const src = "'foo'..";
|
|
17
|
+
|
|
18
|
+
// Act
|
|
19
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual({ root: "0" });
|
|
20
|
+
|
|
21
|
+
// Assert
|
|
22
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
23
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
24
|
+
expect(cache.has(lang)).toBe(true);
|
|
25
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
26
|
+
});
|
|
27
|
+
it("should call main parser cached lexicon", async () => {
|
|
28
|
+
// Arrange
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
const main = {
|
|
31
|
+
parse: mockPromiseValue({ root: "0" })
|
|
32
|
+
};
|
|
33
|
+
const parser = buildParser({
|
|
34
|
+
cache,
|
|
35
|
+
main
|
|
36
|
+
});
|
|
37
|
+
const lang = "0";
|
|
38
|
+
const src = "'foo'..";
|
|
39
|
+
cache.set(lang, {});
|
|
40
|
+
|
|
41
|
+
// Act
|
|
42
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual({ root: "0" });
|
|
43
|
+
|
|
44
|
+
// Assert
|
|
45
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
46
|
+
});
|
|
47
|
+
it("should return error if get language asset fails", async () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
const cache = new Map();
|
|
50
|
+
const err = new Error("failed to get lexicon");
|
|
51
|
+
const getLangAsset = mockPromiseError(err);
|
|
52
|
+
const parser = buildParser({
|
|
53
|
+
cache,
|
|
54
|
+
getLangAsset
|
|
55
|
+
});
|
|
56
|
+
const lang = "00";
|
|
57
|
+
const src = "'foo'..";
|
|
58
|
+
|
|
59
|
+
// Act
|
|
60
|
+
await expect(parser.parse(lang, src)).rejects.toBe(err);
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
64
|
+
});
|
|
65
|
+
it("should return error if main parser fails", async () => {
|
|
66
|
+
// Arrange
|
|
67
|
+
const log = jest.fn();
|
|
68
|
+
const cache = new Map();
|
|
69
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
70
|
+
const err = new Error("main parser failed");
|
|
71
|
+
const main = { parse: mockPromiseError(err) };
|
|
72
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
73
|
+
const lang = "0";
|
|
74
|
+
const src = "'foo'..";
|
|
75
|
+
|
|
76
|
+
// Act
|
|
77
|
+
await expect(parser.parse(lang, src)).rejects.toBe(err);
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
81
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
82
|
+
expect(cache.has(lang)).toBe(true);
|
|
83
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
84
|
+
});
|
|
85
|
+
it("should return succeed if lexicon is a buffer", async () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const log = jest.fn();
|
|
88
|
+
const cache = new Map();
|
|
89
|
+
const getLangAsset = mockPromiseValue(Buffer.from("{}"));
|
|
90
|
+
const ast = { root: "0" };
|
|
91
|
+
const main = { parse: mockPromiseValue(ast) };
|
|
92
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
93
|
+
const lang = "0";
|
|
94
|
+
const src = "'foo'..";
|
|
95
|
+
|
|
96
|
+
// Act
|
|
97
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
|
|
98
|
+
|
|
99
|
+
// Assert
|
|
100
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
101
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
102
|
+
expect(cache.has(lang)).toBe(true);
|
|
103
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
104
|
+
});
|
|
105
|
+
it("should try vm if lexicon cannot parse JSON", async () => {
|
|
106
|
+
// Arrange
|
|
107
|
+
const log = jest.fn();
|
|
108
|
+
const cache = new Map();
|
|
109
|
+
const rawLexicon = `
|
|
110
|
+
(() => {
|
|
111
|
+
window.gcexports.globalLexicon = {};
|
|
112
|
+
})();
|
|
113
|
+
`;
|
|
114
|
+
const getLangAsset = mockPromiseValue(rawLexicon);
|
|
115
|
+
const ast = { root: "0" };
|
|
116
|
+
const main = { parse: mockPromiseValue(ast) };
|
|
117
|
+
const vm = {
|
|
118
|
+
createContext: jest.fn(),
|
|
119
|
+
runInContext: jest.fn().mockImplementation((data, context) => {
|
|
120
|
+
context.window.gcexports.globalLexicon = {};
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
const parser = buildParser({ log, cache, getLangAsset, main, vm });
|
|
124
|
+
const lang = "0";
|
|
125
|
+
const src = "'foo'..";
|
|
126
|
+
|
|
127
|
+
// Act
|
|
128
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
|
|
129
|
+
|
|
130
|
+
// Assert
|
|
131
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
132
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
133
|
+
expect(cache.has(lang)).toBe(true);
|
|
134
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
135
|
+
expect(vm.createContext).toHaveBeenCalled();
|
|
136
|
+
expect(vm.runInContext).toHaveBeenCalledWith(rawLexicon, expect.anything());
|
|
137
|
+
});
|
|
138
|
+
it.skip("should parse 'hello, world!'", async () => {
|
|
139
|
+
const lang = "0";
|
|
140
|
+
const src = "'hello, world'..";
|
|
141
|
+
const ast = {
|
|
142
|
+
1: {
|
|
143
|
+
elts: [
|
|
144
|
+
"hello, world"
|
|
145
|
+
],
|
|
146
|
+
tag: "STR"
|
|
147
|
+
},
|
|
148
|
+
2: {
|
|
149
|
+
elts: [
|
|
150
|
+
1
|
|
151
|
+
],
|
|
152
|
+
tag: "EXPRS"
|
|
153
|
+
},
|
|
154
|
+
3: {
|
|
155
|
+
elts: [
|
|
156
|
+
2
|
|
157
|
+
],
|
|
158
|
+
tag: "PROG"
|
|
159
|
+
},
|
|
160
|
+
root: 3
|
|
161
|
+
};
|
|
162
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
|
|
163
|
+
});
|
|
164
|
+
it.skip("should parse error", async () => {
|
|
165
|
+
const lang = "0";
|
|
166
|
+
const src = "'hello, world'";
|
|
167
|
+
await expect(parser.parse(lang, src)).rejects.toThrow("End of program reached");
|
|
168
|
+
});
|
|
169
|
+
// it("should parse error", async () => {
|
|
170
|
+
// const lang = "0";
|
|
171
|
+
// const src = "'hello, world..";
|
|
172
|
+
// const ast = {};
|
|
173
|
+
// await expect(parser.parse(lang, src)).rejects.toThrow("End of program reached");
|
|
174
|
+
// });
|
|
175
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { parser, buildParser } from "./parser.js";
|
|
3
|
+
import { mockPromiseValue, mockPromiseError } from "../testing/index.js";
|
|
4
|
+
|
|
5
|
+
describe("lang/parser", () => {
|
|
6
|
+
const log = jest.fn();
|
|
7
|
+
it("should call main parser language lexicon", async () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
11
|
+
const main = {
|
|
12
|
+
parse: mockPromiseValue({ root: "0" })
|
|
13
|
+
};
|
|
14
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
15
|
+
const lang = "0";
|
|
16
|
+
const src = "'foo'..";
|
|
17
|
+
|
|
18
|
+
// Act
|
|
19
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual({ root: "0" });
|
|
20
|
+
|
|
21
|
+
// Assert
|
|
22
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
23
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
24
|
+
expect(cache.has(lang)).toBe(true);
|
|
25
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
26
|
+
});
|
|
27
|
+
it("should call main parser cached lexicon", async () => {
|
|
28
|
+
// Arrange
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
const main = {
|
|
31
|
+
parse: mockPromiseValue({ root: "0" })
|
|
32
|
+
};
|
|
33
|
+
const parser = buildParser({
|
|
34
|
+
cache,
|
|
35
|
+
main
|
|
36
|
+
});
|
|
37
|
+
const lang = "0";
|
|
38
|
+
const src = "'foo'..";
|
|
39
|
+
cache.set(lang, {});
|
|
40
|
+
|
|
41
|
+
// Act
|
|
42
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual({ root: "0" });
|
|
43
|
+
|
|
44
|
+
// Assert
|
|
45
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
46
|
+
});
|
|
47
|
+
it("should return error if get language asset fails", async () => {
|
|
48
|
+
// Arrange
|
|
49
|
+
const cache = new Map();
|
|
50
|
+
const err = new Error("failed to get lexicon");
|
|
51
|
+
const getLangAsset = mockPromiseError(err);
|
|
52
|
+
const parser = buildParser({
|
|
53
|
+
cache,
|
|
54
|
+
getLangAsset
|
|
55
|
+
});
|
|
56
|
+
const lang = "00";
|
|
57
|
+
const src = "'foo'..";
|
|
58
|
+
|
|
59
|
+
// Act
|
|
60
|
+
await expect(parser.parse(lang, src)).rejects.toBe(err);
|
|
61
|
+
|
|
62
|
+
// Assert
|
|
63
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
64
|
+
});
|
|
65
|
+
it("should return error if main parser fails", async () => {
|
|
66
|
+
// Arrange
|
|
67
|
+
const log = jest.fn();
|
|
68
|
+
const cache = new Map();
|
|
69
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
70
|
+
const err = new Error("main parser failed");
|
|
71
|
+
const main = { parse: mockPromiseError(err) };
|
|
72
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
73
|
+
const lang = "0";
|
|
74
|
+
const src = "'foo'..";
|
|
75
|
+
|
|
76
|
+
// Act
|
|
77
|
+
await expect(parser.parse(lang, src)).rejects.toBe(err);
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
81
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
82
|
+
expect(cache.has(lang)).toBe(true);
|
|
83
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
84
|
+
});
|
|
85
|
+
it("should return succeed if lexicon is a buffer", async () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const log = jest.fn();
|
|
88
|
+
const cache = new Map();
|
|
89
|
+
const getLangAsset = mockPromiseValue(Buffer.from("{}"));
|
|
90
|
+
const ast = { root: "0" };
|
|
91
|
+
const main = { parse: mockPromiseValue(ast) };
|
|
92
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
93
|
+
const lang = "0";
|
|
94
|
+
const src = "'foo'..";
|
|
95
|
+
|
|
96
|
+
// Act
|
|
97
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
|
|
98
|
+
|
|
99
|
+
// Assert
|
|
100
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
101
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
102
|
+
expect(cache.has(lang)).toBe(true);
|
|
103
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
104
|
+
});
|
|
105
|
+
it("should try vm if lexicon cannot parse JSON", async () => {
|
|
106
|
+
// Arrange
|
|
107
|
+
const log = jest.fn();
|
|
108
|
+
const cache = new Map();
|
|
109
|
+
const rawLexicon = `
|
|
110
|
+
(() => {
|
|
111
|
+
window.gcexports.globalLexicon = {};
|
|
112
|
+
})();
|
|
113
|
+
`;
|
|
114
|
+
const getLangAsset = mockPromiseValue(rawLexicon);
|
|
115
|
+
const ast = { root: "0" };
|
|
116
|
+
const main = { parse: mockPromiseValue(ast) };
|
|
117
|
+
const vm = {
|
|
118
|
+
createContext: jest.fn(),
|
|
119
|
+
runInContext: jest.fn().mockImplementation((data, context) => {
|
|
120
|
+
context.window.gcexports.globalLexicon = {};
|
|
121
|
+
})
|
|
122
|
+
};
|
|
123
|
+
const parser = buildParser({ log, cache, getLangAsset, main, vm });
|
|
124
|
+
const lang = "0";
|
|
125
|
+
const src = "'foo'..";
|
|
126
|
+
|
|
127
|
+
// Act
|
|
128
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
|
|
129
|
+
|
|
130
|
+
// Assert
|
|
131
|
+
expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
|
|
132
|
+
expect(main.parse).toHaveBeenCalledWith(src, {});
|
|
133
|
+
expect(cache.has(lang)).toBe(true);
|
|
134
|
+
expect(cache.get(lang)).toStrictEqual({});
|
|
135
|
+
expect(vm.createContext).toHaveBeenCalled();
|
|
136
|
+
expect(vm.runInContext).toHaveBeenCalledWith(rawLexicon, expect.anything());
|
|
137
|
+
});
|
|
138
|
+
it.skip("should parse 'hello, world!'", async () => {
|
|
139
|
+
const lang = "0";
|
|
140
|
+
const src = "'hello, world'..";
|
|
141
|
+
const ast = {
|
|
142
|
+
1: {
|
|
143
|
+
elts: [
|
|
144
|
+
"hello, world"
|
|
145
|
+
],
|
|
146
|
+
tag: "STR"
|
|
147
|
+
},
|
|
148
|
+
2: {
|
|
149
|
+
elts: [
|
|
150
|
+
1
|
|
151
|
+
],
|
|
152
|
+
tag: "EXPRS"
|
|
153
|
+
},
|
|
154
|
+
3: {
|
|
155
|
+
elts: [
|
|
156
|
+
2
|
|
157
|
+
],
|
|
158
|
+
tag: "PROG"
|
|
159
|
+
},
|
|
160
|
+
root: 3
|
|
161
|
+
};
|
|
162
|
+
await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
|
|
163
|
+
});
|
|
164
|
+
it.skip("should parse error", async () => {
|
|
165
|
+
const lang = "0";
|
|
166
|
+
const src = "'hello, world'";
|
|
167
|
+
await expect(parser.parse(lang, src)).rejects.toThrow("End of program reached");
|
|
168
|
+
});
|
|
169
|
+
// it("should parse error", async () => {
|
|
170
|
+
// const lang = "0";
|
|
171
|
+
// const src = "'hello, world..";
|
|
172
|
+
// const ast = {};
|
|
173
|
+
// await expect(parser.parse(lang, src)).rejects.toThrow("End of program reached");
|
|
174
|
+
// });
|
|
175
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
export const mockPromiseError = (err) => {
|
|
3
|
+
return jest.fn().mockImplementation((...params) => {
|
|
4
|
+
if (params.length > 0) {
|
|
5
|
+
throw err;
|
|
6
|
+
} else {
|
|
7
|
+
throw new Error("no callback paramter given");
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const mockPromiseValue = (value) => {
|
|
13
|
+
return jest.fn().mockImplementation((...params) => {
|
|
14
|
+
return value;
|
|
15
|
+
});
|
|
16
|
+
};
|