@graffiticode/parser 0.1.1 → 0.1.2
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 +4 -6
- package/src/ast.js +24 -18
- package/src/env.js +8 -4
- package/src/folder.js +8 -3
- package/src/folder.spec.js +42 -0
- package/src/parse.js +2 -2
- package/src/parse.spec.js +118 -0
- package/src/parse.spec.js~ +119 -0
- package/src/parser.spec.js +11 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiticode/parser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"eslint": "^8.38.0",
|
|
11
|
-
"jest": "^29.
|
|
11
|
+
"jest": "^29.7.0",
|
|
12
12
|
"supertest": "^6.3.3"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
@@ -18,8 +18,6 @@
|
|
|
18
18
|
},
|
|
19
19
|
"keywords": [],
|
|
20
20
|
"author": "",
|
|
21
|
-
"license": "
|
|
22
|
-
"description": ""
|
|
23
|
-
"dependencies": {
|
|
24
|
-
}
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"description": ""
|
|
25
23
|
}
|
package/src/ast.js
CHANGED
|
@@ -20,23 +20,23 @@ function nodeToJSON(n) {
|
|
|
20
20
|
let obj;
|
|
21
21
|
if (typeof n === "object") {
|
|
22
22
|
switch (n.tag) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
case "num":
|
|
24
|
+
obj = n.elts[0];
|
|
25
|
+
break;
|
|
26
|
+
case "str":
|
|
27
|
+
obj = n.elts[0];
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
obj = {};
|
|
31
|
+
obj.tag = n.tag;
|
|
32
|
+
obj.elts = [];
|
|
33
|
+
if (n.coord) {
|
|
34
|
+
obj.coord = n.coord;
|
|
35
|
+
}
|
|
36
|
+
for (let i = 0; i < n.elts.length; i++) {
|
|
37
|
+
obj.elts[i] = nodeToJSON(n.elts[i]);
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
40
|
}
|
|
41
41
|
} else if (typeof n === "string") {
|
|
42
42
|
obj = n;
|
|
@@ -667,6 +667,10 @@ export class Ast {
|
|
|
667
667
|
nids.push(word.nid || 0);
|
|
668
668
|
}
|
|
669
669
|
const pattern = env.pattern;
|
|
670
|
+
console.log(
|
|
671
|
+
"lambda()",
|
|
672
|
+
"pattern=" + JSON.stringify(pattern, null, 2),
|
|
673
|
+
);
|
|
670
674
|
Ast.push(ctx, {
|
|
671
675
|
tag: "LAMBDA",
|
|
672
676
|
elts: [{
|
|
@@ -674,7 +678,9 @@ export class Ast {
|
|
|
674
678
|
elts: names
|
|
675
679
|
}, nid, {
|
|
676
680
|
tag: "LIST",
|
|
677
|
-
elts:
|
|
681
|
+
elts: [
|
|
682
|
+
// pattern // FIXME
|
|
683
|
+
],
|
|
678
684
|
}, {
|
|
679
685
|
tag: "LIST",
|
|
680
686
|
elts: nids
|
package/src/env.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// env
|
|
2
2
|
|
|
3
|
+
function topEnv(ctx) {
|
|
4
|
+
return ctx.state.env[ctx.state.env.length - 1];
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export class Env {
|
|
4
8
|
static findWord(ctx, lexeme) {
|
|
5
9
|
const env = ctx.state.env;
|
|
@@ -13,12 +17,12 @@ export class Env {
|
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
static addWord(ctx, lexeme, entry) {
|
|
16
|
-
|
|
20
|
+
topEnv(ctx).lexicon[lexeme] = entry;
|
|
17
21
|
return null;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
static addPattern(ctx, pattern) {
|
|
21
|
-
|
|
25
|
+
topEnv(ctx).pattern.push(pattern);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
static enterEnv(ctx, name) {
|
|
@@ -31,7 +35,7 @@ export class Env {
|
|
|
31
35
|
// return; // just stop recursing
|
|
32
36
|
throw new Error("runaway recursion");
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
topEnv(ctx).paramc = ctx.state.paramc;
|
|
35
39
|
ctx.state.env.push({
|
|
36
40
|
name,
|
|
37
41
|
lexicon: {},
|
|
@@ -41,6 +45,6 @@ export class Env {
|
|
|
41
45
|
|
|
42
46
|
static exitEnv(ctx) {
|
|
43
47
|
ctx.state.env.pop();
|
|
44
|
-
ctx.state.paramc =
|
|
48
|
+
ctx.state.paramc = topEnv(ctx).paramc;
|
|
45
49
|
}
|
|
46
50
|
}
|
package/src/folder.js
CHANGED
|
@@ -203,11 +203,16 @@ export class Folder {
|
|
|
203
203
|
const elts = [];
|
|
204
204
|
for (let i = 0; i < word.length; i++) {
|
|
205
205
|
const elt = Ast.pop(ctx);
|
|
206
|
-
assertErr(
|
|
206
|
+
assertErr(
|
|
207
|
+
ctx,
|
|
208
|
+
elt,
|
|
209
|
+
`Too few arguments for ${word.name}. Expected ${word.length}.`,
|
|
210
|
+
node.coord
|
|
211
|
+
);
|
|
207
212
|
elts.push(elt);
|
|
208
213
|
}
|
|
209
214
|
if (word.nid) {
|
|
210
|
-
Ast.
|
|
215
|
+
Ast.foldApply(ctx, word, elts);
|
|
211
216
|
} else {
|
|
212
217
|
Ast.push(ctx, {
|
|
213
218
|
tag: word.name,
|
|
@@ -220,7 +225,7 @@ export class Folder {
|
|
|
220
225
|
assert(false);
|
|
221
226
|
}
|
|
222
227
|
} else {
|
|
223
|
-
//
|
|
228
|
+
// assertErr(ctx, false, "unresolved ident " + name, node.coord);
|
|
224
229
|
Ast.push(ctx, node);
|
|
225
230
|
}
|
|
226
231
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Folder } from "./folder.js";
|
|
2
|
+
import { Ast } from "./ast.js";
|
|
3
|
+
|
|
4
|
+
describe("folder", () => {
|
|
5
|
+
it("should fold 'add 2 3' to 5", () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
const ctx = {
|
|
8
|
+
state: {
|
|
9
|
+
nodePool: ["unused"],
|
|
10
|
+
nodeStack: [],
|
|
11
|
+
nodeStackStack: [],
|
|
12
|
+
nodeMap: {},
|
|
13
|
+
env: [{ name: "global", lexicon: {} }]
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Create nodes for "add 2 3"
|
|
18
|
+
const n1Id = Ast.intern(ctx, {
|
|
19
|
+
tag: "NUM",
|
|
20
|
+
elts: ["2"]
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const n2Id = Ast.intern(ctx, {
|
|
24
|
+
tag: "NUM",
|
|
25
|
+
elts: ["3"]
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const addNodeId = Ast.intern(ctx, {
|
|
29
|
+
tag: "ADD",
|
|
30
|
+
elts: [n1Id, n2Id]
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Act
|
|
34
|
+
Folder.fold(ctx, addNodeId);
|
|
35
|
+
const resultId = ctx.state.nodeStack.pop();
|
|
36
|
+
const resultNode = ctx.state.nodePool[resultId];
|
|
37
|
+
|
|
38
|
+
// Assert
|
|
39
|
+
expect(resultNode.tag).toBe("NUM");
|
|
40
|
+
expect(resultNode.elts[0]).toBe("5");
|
|
41
|
+
});
|
|
42
|
+
});
|
package/src/parse.js
CHANGED
|
@@ -239,7 +239,7 @@ export const parse = (function () {
|
|
|
239
239
|
scanTime += (t1 - t0);
|
|
240
240
|
ctx.state.nextToken = tk;
|
|
241
241
|
const to = getPos(ctx);
|
|
242
|
-
ctx.state.nextTokenCoord = {from, to};
|
|
242
|
+
ctx.state.nextTokenCoord = { from, to };
|
|
243
243
|
} else {
|
|
244
244
|
tk = nextToken;
|
|
245
245
|
}
|
|
@@ -1166,7 +1166,7 @@ export const parse = (function () {
|
|
|
1166
1166
|
c = nextCC();
|
|
1167
1167
|
}
|
|
1168
1168
|
}
|
|
1169
|
-
const coord = {from: getPos(ctx) - lexeme.length, to: getPos(ctx)};
|
|
1169
|
+
const coord = { from: getPos(ctx) - lexeme.length, to: getPos(ctx) };
|
|
1170
1170
|
assertErr(ctx, c !== 0, `Unterminated string: ${lexeme}`, coord);
|
|
1171
1171
|
if (quoteChar === CC_BACKTICK && c === CC_DOLLAR &&
|
|
1172
1172
|
peekCC() === CC_LEFTBRACE) {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { buildParser } from "./parser.js";
|
|
2
|
+
import { mockPromiseValue } from "./testing/index.js";
|
|
3
|
+
|
|
4
|
+
describe("parse operations", () => {
|
|
5
|
+
const log = jest.fn();
|
|
6
|
+
|
|
7
|
+
it("should parse 'apply (<a b: add a b>) [10 20]'", async () => {
|
|
8
|
+
// Arrange
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
11
|
+
const main = {
|
|
12
|
+
parse: mockPromiseValue({
|
|
13
|
+
1: {
|
|
14
|
+
elts: ["a"],
|
|
15
|
+
tag: "IDENT"
|
|
16
|
+
},
|
|
17
|
+
2: {
|
|
18
|
+
elts: ["b"],
|
|
19
|
+
tag: "IDENT"
|
|
20
|
+
},
|
|
21
|
+
3: {
|
|
22
|
+
elts: [1, 2],
|
|
23
|
+
tag: "ADD"
|
|
24
|
+
},
|
|
25
|
+
4: {
|
|
26
|
+
elts: [3],
|
|
27
|
+
tag: "EXPRS"
|
|
28
|
+
},
|
|
29
|
+
5: {
|
|
30
|
+
elts: [1, 2],
|
|
31
|
+
tag: "LIST"
|
|
32
|
+
},
|
|
33
|
+
6: {
|
|
34
|
+
elts: [5, 4],
|
|
35
|
+
tag: "LAMBDA"
|
|
36
|
+
},
|
|
37
|
+
7: {
|
|
38
|
+
elts: ["10"],
|
|
39
|
+
tag: "NUM"
|
|
40
|
+
},
|
|
41
|
+
8: {
|
|
42
|
+
elts: ["20"],
|
|
43
|
+
tag: "NUM"
|
|
44
|
+
},
|
|
45
|
+
9: {
|
|
46
|
+
elts: [7, 8],
|
|
47
|
+
tag: "LIST"
|
|
48
|
+
},
|
|
49
|
+
10: {
|
|
50
|
+
elts: [6, 9],
|
|
51
|
+
tag: "APPLY"
|
|
52
|
+
},
|
|
53
|
+
11: {
|
|
54
|
+
elts: [10],
|
|
55
|
+
tag: "EXPRS"
|
|
56
|
+
},
|
|
57
|
+
12: {
|
|
58
|
+
elts: [11],
|
|
59
|
+
tag: "PROG"
|
|
60
|
+
},
|
|
61
|
+
root: 12
|
|
62
|
+
})
|
|
63
|
+
};
|
|
64
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
65
|
+
const lang = "0002";
|
|
66
|
+
const src = "apply (<a b: add a b>) [10 20]..";
|
|
67
|
+
|
|
68
|
+
// Act
|
|
69
|
+
const result = await parser.parse(lang, src);
|
|
70
|
+
|
|
71
|
+
// Assert
|
|
72
|
+
expect(result).toHaveProperty("root", 12);
|
|
73
|
+
expect(result[10].tag).toBe("APPLY");
|
|
74
|
+
expect(result[10].elts).toEqual([6, 9]);
|
|
75
|
+
expect(result[6].tag).toBe("LAMBDA");
|
|
76
|
+
expect(result[9].tag).toBe("LIST");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should parse 'hello, world!'", async () => {
|
|
80
|
+
// Arrange
|
|
81
|
+
const cache = new Map();
|
|
82
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
83
|
+
const main = {
|
|
84
|
+
parse: mockPromiseValue({
|
|
85
|
+
1: {
|
|
86
|
+
elts: [
|
|
87
|
+
"hello, world"
|
|
88
|
+
],
|
|
89
|
+
tag: "STR"
|
|
90
|
+
},
|
|
91
|
+
2: {
|
|
92
|
+
elts: [
|
|
93
|
+
1
|
|
94
|
+
],
|
|
95
|
+
tag: "EXPRS"
|
|
96
|
+
},
|
|
97
|
+
3: {
|
|
98
|
+
elts: [
|
|
99
|
+
2
|
|
100
|
+
],
|
|
101
|
+
tag: "PROG"
|
|
102
|
+
},
|
|
103
|
+
root: 3
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
107
|
+
const lang = "0002";
|
|
108
|
+
const src = "'hello, world'..";
|
|
109
|
+
|
|
110
|
+
// Act
|
|
111
|
+
const result = await parser.parse(lang, src);
|
|
112
|
+
|
|
113
|
+
// Assert
|
|
114
|
+
expect(result).toHaveProperty("root", 3);
|
|
115
|
+
expect(result[1].tag).toBe("STR");
|
|
116
|
+
expect(result[1].elts).toEqual(["hello, world"]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { parser, buildParser } from "./parser.js";
|
|
3
|
+
import { mockPromiseValue, mockPromiseError } from "./testing/index.js";
|
|
4
|
+
|
|
5
|
+
describe("parse operations", () => {
|
|
6
|
+
const log = jest.fn();
|
|
7
|
+
|
|
8
|
+
it("should parse 'apply (<a b: add a b>) [10 20]'", async () => {
|
|
9
|
+
// Arrange
|
|
10
|
+
const cache = new Map();
|
|
11
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
12
|
+
const main = {
|
|
13
|
+
parse: mockPromiseValue({
|
|
14
|
+
1: {
|
|
15
|
+
elts: ["a"],
|
|
16
|
+
tag: "IDENT"
|
|
17
|
+
},
|
|
18
|
+
2: {
|
|
19
|
+
elts: ["b"],
|
|
20
|
+
tag: "IDENT"
|
|
21
|
+
},
|
|
22
|
+
3: {
|
|
23
|
+
elts: [1, 2],
|
|
24
|
+
tag: "ADD"
|
|
25
|
+
},
|
|
26
|
+
4: {
|
|
27
|
+
elts: [3],
|
|
28
|
+
tag: "EXPRS"
|
|
29
|
+
},
|
|
30
|
+
5: {
|
|
31
|
+
elts: [1, 2],
|
|
32
|
+
tag: "LIST"
|
|
33
|
+
},
|
|
34
|
+
6: {
|
|
35
|
+
elts: [5, 4],
|
|
36
|
+
tag: "LAMBDA"
|
|
37
|
+
},
|
|
38
|
+
7: {
|
|
39
|
+
elts: ["10"],
|
|
40
|
+
tag: "NUM"
|
|
41
|
+
},
|
|
42
|
+
8: {
|
|
43
|
+
elts: ["20"],
|
|
44
|
+
tag: "NUM"
|
|
45
|
+
},
|
|
46
|
+
9: {
|
|
47
|
+
elts: [7, 8],
|
|
48
|
+
tag: "LIST"
|
|
49
|
+
},
|
|
50
|
+
10: {
|
|
51
|
+
elts: [6, 9],
|
|
52
|
+
tag: "APPLY"
|
|
53
|
+
},
|
|
54
|
+
11: {
|
|
55
|
+
elts: [10],
|
|
56
|
+
tag: "EXPRS"
|
|
57
|
+
},
|
|
58
|
+
12: {
|
|
59
|
+
elts: [11],
|
|
60
|
+
tag: "PROG"
|
|
61
|
+
},
|
|
62
|
+
root: 12
|
|
63
|
+
})
|
|
64
|
+
};
|
|
65
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
66
|
+
const lang = "0";
|
|
67
|
+
const src = "apply (<a b: add a b>) [10 20]..";
|
|
68
|
+
|
|
69
|
+
// Act
|
|
70
|
+
const result = await parser.parse(lang, src);
|
|
71
|
+
|
|
72
|
+
// Assert
|
|
73
|
+
expect(result).toHaveProperty("root", 12);
|
|
74
|
+
expect(result[10].tag).toBe("APPLY");
|
|
75
|
+
expect(result[10].elts).toEqual([6, 9]);
|
|
76
|
+
expect(result[6].tag).toBe("LAMBDA");
|
|
77
|
+
expect(result[9].tag).toBe("LIST");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should parse 'hello, world!'", async () => {
|
|
81
|
+
// Arrange
|
|
82
|
+
const cache = new Map();
|
|
83
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
84
|
+
const main = {
|
|
85
|
+
parse: mockPromiseValue({
|
|
86
|
+
1: {
|
|
87
|
+
elts: [
|
|
88
|
+
"hello, world"
|
|
89
|
+
],
|
|
90
|
+
tag: "STR"
|
|
91
|
+
},
|
|
92
|
+
2: {
|
|
93
|
+
elts: [
|
|
94
|
+
1
|
|
95
|
+
],
|
|
96
|
+
tag: "EXPRS"
|
|
97
|
+
},
|
|
98
|
+
3: {
|
|
99
|
+
elts: [
|
|
100
|
+
2
|
|
101
|
+
],
|
|
102
|
+
tag: "PROG"
|
|
103
|
+
},
|
|
104
|
+
root: 3
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
108
|
+
const lang = "0";
|
|
109
|
+
const src = "'hello, world'..";
|
|
110
|
+
|
|
111
|
+
// Act
|
|
112
|
+
const result = await parser.parse(lang, src);
|
|
113
|
+
|
|
114
|
+
// Assert
|
|
115
|
+
expect(result).toHaveProperty("root", 3);
|
|
116
|
+
expect(result[1].tag).toBe("STR");
|
|
117
|
+
expect(result[1].elts).toEqual(["hello, world"]);
|
|
118
|
+
});
|
|
119
|
+
});
|
package/src/parser.spec.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jest } from "@jest/globals";
|
|
2
|
-
import {
|
|
2
|
+
import { buildParser } from "./parser.js";
|
|
3
3
|
import { mockPromiseValue, mockPromiseError } from "./testing/index.js";
|
|
4
4
|
|
|
5
5
|
describe("lang/parser", () => {
|
|
@@ -135,41 +135,17 @@ describe("lang/parser", () => {
|
|
|
135
135
|
expect(vm.createContext).toHaveBeenCalled();
|
|
136
136
|
expect(vm.runInContext).toHaveBeenCalledWith(rawLexicon, expect.anything());
|
|
137
137
|
});
|
|
138
|
-
it
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 () => {
|
|
138
|
+
it("should parse error", async () => {
|
|
139
|
+
// Arrange
|
|
140
|
+
const cache = new Map();
|
|
141
|
+
const getLangAsset = mockPromiseValue("{}");
|
|
142
|
+
const err = new Error("End of program reached.");
|
|
143
|
+
const main = { parse: mockPromiseError(err) };
|
|
144
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
165
145
|
const lang = "0";
|
|
166
146
|
const src = "'hello, world'";
|
|
167
|
-
|
|
147
|
+
|
|
148
|
+
// Act & Assert
|
|
149
|
+
await expect(parser.parse(lang, src)).rejects.toBe(err);
|
|
168
150
|
});
|
|
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
151
|
});
|