@graffiticode/parser 0.3.0 → 1.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiticode/parser",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/index.js CHANGED
@@ -1,2 +1 @@
1
1
  export { parser } from "./parser.js";
2
- export { unparse } from "./unparse.js";
package/src/parser.js CHANGED
@@ -1,5 +1,3 @@
1
- import vm from "vm";
2
- import { getLangAsset } from "../../api/src/lang/index.js";
3
1
  import { parse } from "./parse.js";
4
2
  import { unparse } from "./unparse.js";
5
3
 
@@ -40,58 +38,30 @@ const main = {
40
38
  }
41
39
  };
42
40
 
43
- export const buildParser = ({
44
- log,
45
- cache,
46
- getLangAsset,
47
- main,
48
- vm
49
- }) => {
41
+ export const buildParser = ({ main }) => {
50
42
  return {
51
- async parse(lang, src) {
52
- if (!cache.has(lang)) {
53
- let data = await getLangAsset(lang, "/lexicon.js");
54
- // TODO Make lexicon JSON.
55
- if (data instanceof Buffer) {
56
- data = data.toString();
57
- }
58
- if (typeof (data) !== "string") {
59
- log(`Failed to get usable lexicon for ${lang}`, typeof (data), data);
60
- throw new Error("unable to use lexicon");
61
- }
62
- const lstr = data.substring(data.indexOf("{"));
63
- let lexicon;
64
- try {
65
- lexicon = JSON.parse(lstr);
66
- } catch (err) {
67
- if (err instanceof SyntaxError) {
68
- log(`failed to parse ${lang} lexicon: ${err.message}`);
69
- const context = { window: { gcexports: {} } };
70
- vm.createContext(context);
71
- vm.runInContext(data, context);
72
- if (typeof (context.window.gcexports.globalLexicon) === "object") {
73
- lexicon = context.window.gcexports.globalLexicon;
74
- }
75
- }
76
- if (!lexicon) {
77
- throw new Error("Malformed lexicon");
78
- }
79
- }
80
- cache.set(lang, lexicon);
81
- };
82
- const lexicon = cache.get(lang);
43
+ async parse(lang, src, lexicon) {
44
+ // Lexicon is now required
45
+ if (!lexicon) {
46
+ throw new Error("Lexicon is required for parsing");
47
+ }
83
48
  return await main.parse(src, lexicon);
84
49
  }
85
50
  };
86
51
  };
87
52
 
88
53
  export const parser = buildParser({
89
- log: console.log,
90
- cache: new Map(),
91
- getLangAsset,
92
- main,
93
- vm
54
+ main
94
55
  });
95
56
 
96
57
  // Add unparse as a property of parser
97
58
  parser.unparse = unparse;
59
+
60
+ // Add reformat function that parses and unparses code
61
+ parser.reformat = async function(lang, src, lexicon, options = {}) {
62
+ if (!lexicon) {
63
+ throw new Error("Lexicon is required for reformatting");
64
+ }
65
+ const ast = await this.parse(lang, src, lexicon);
66
+ return unparse(ast, lexicon, options);
67
+ };
@@ -1,153 +1,96 @@
1
1
  import { jest } from "@jest/globals";
2
2
  import { buildParser, parser } from "./parser.js";
3
3
  import { mockPromiseValue, mockPromiseError } from "./testing/index.js";
4
- import vm from "vm";
4
+ import { lexicon as basisLexicon } from "@graffiticode/basis";
5
5
 
6
6
  describe("lang/parser", () => {
7
7
  const log = jest.fn();
8
- it("should call main parser language lexicon", async () => {
8
+ it("should use provided lexicon", async () => {
9
9
  // Arrange
10
- const cache = new Map();
11
- const getLangAsset = mockPromiseValue("{}");
12
10
  const main = {
13
11
  parse: mockPromiseValue({ root: "0" })
14
12
  };
15
- const parser = buildParser({ log, cache, getLangAsset, main });
13
+ const parser = buildParser({ main });
16
14
  const lang = "0";
17
15
  const src = "'foo'..";
16
+ const providedLexicon = { test: "lexicon" };
18
17
 
19
18
  // Act
20
- await expect(parser.parse(lang, src)).resolves.toStrictEqual({ root: "0" });
19
+ await expect(parser.parse(lang, src, providedLexicon)).resolves.toStrictEqual({ root: "0" });
21
20
 
22
21
  // Assert
23
- expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
24
- expect(main.parse).toHaveBeenCalledWith(src, {});
25
- expect(cache.has(lang)).toBe(true);
26
- expect(cache.get(lang)).toStrictEqual({});
22
+ expect(main.parse).toHaveBeenCalledWith(src, providedLexicon);
27
23
  });
28
- it("should call main parser cached lexicon", async () => {
24
+
25
+ it("should throw error when lexicon is missing", async () => {
29
26
  // Arrange
30
- const cache = new Map();
31
27
  const main = {
32
28
  parse: mockPromiseValue({ root: "0" })
33
29
  };
34
- const parser = buildParser({
35
- cache,
36
- main
37
- });
30
+ const parser = buildParser({ main });
38
31
  const lang = "0";
39
32
  const src = "'foo'..";
40
- cache.set(lang, {});
41
-
42
- // Act
43
- await expect(parser.parse(lang, src)).resolves.toStrictEqual({ root: "0" });
44
-
45
- // Assert
46
- expect(main.parse).toHaveBeenCalledWith(src, {});
47
- });
48
- it("should return error if get language asset fails", async () => {
49
- // Arrange
50
- const cache = new Map();
51
- const err = new Error("failed to get lexicon");
52
- const getLangAsset = mockPromiseError(err);
53
- const parser = buildParser({
54
- cache,
55
- getLangAsset
56
- });
57
- const lang = "00";
58
- const src = "'foo'..";
59
33
 
60
- // Act
61
- await expect(parser.parse(lang, src)).rejects.toBe(err);
62
-
63
- // Assert
64
- expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
34
+ // Act & Assert
35
+ await expect(parser.parse(lang, src)).rejects.toThrow("Lexicon is required for parsing");
65
36
  });
66
- it("should return error if main parser fails", async () => {
37
+ it("should pass lexicon to main parser", async () => {
67
38
  // Arrange
68
- const log = jest.fn();
69
- const cache = new Map();
70
- const getLangAsset = mockPromiseValue("{}");
71
- const err = new Error("main parser failed");
72
- const main = { parse: mockPromiseError(err) };
73
- const parser = buildParser({ log, cache, getLangAsset, main });
39
+ const main = {
40
+ parse: mockPromiseValue({ root: "0" })
41
+ };
42
+ const parser = buildParser({ main });
74
43
  const lang = "0";
75
44
  const src = "'foo'..";
45
+ const lexicon = { someFunc: { name: "SOMEFUNC" } };
76
46
 
77
47
  // Act
78
- await expect(parser.parse(lang, src)).rejects.toBe(err);
48
+ await expect(parser.parse(lang, src, lexicon)).resolves.toStrictEqual({ root: "0" });
79
49
 
80
50
  // Assert
81
- expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
82
- expect(main.parse).toHaveBeenCalledWith(src, {});
83
- expect(cache.has(lang)).toBe(true);
84
- expect(cache.get(lang)).toStrictEqual({});
51
+ expect(main.parse).toHaveBeenCalledWith(src, lexicon);
85
52
  });
86
- it("should return succeed if lexicon is a buffer", async () => {
53
+ it("should return error if main parser fails with lexicon", async () => {
87
54
  // Arrange
88
- const log = jest.fn();
89
- const cache = new Map();
90
- const getLangAsset = mockPromiseValue(Buffer.from("{}"));
91
- const ast = { root: "0" };
92
- const main = { parse: mockPromiseValue(ast) };
93
- const parser = buildParser({ log, cache, getLangAsset, main });
94
- const lang = "0";
55
+ const err = new Error("parser failed");
56
+ const main = { parse: mockPromiseError(err) };
57
+ const parser = buildParser({ main });
58
+ const lang = "00";
95
59
  const src = "'foo'..";
60
+ const lexicon = {};
96
61
 
97
62
  // Act
98
- await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
63
+ await expect(parser.parse(lang, src, lexicon)).rejects.toBe(err);
99
64
 
100
65
  // Assert
101
- expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
102
- expect(main.parse).toHaveBeenCalledWith(src, {});
103
- expect(cache.has(lang)).toBe(true);
104
- expect(cache.get(lang)).toStrictEqual({});
66
+ expect(main.parse).toHaveBeenCalledWith(src, lexicon);
105
67
  });
106
- it("should try vm if lexicon cannot parse JSON", async () => {
68
+ it("should return error if main parser fails", async () => {
107
69
  // Arrange
108
- const log = jest.fn();
109
- const cache = new Map();
110
- const rawLexicon = `
111
- (() => {
112
- window.gcexports.globalLexicon = {};
113
- })();
114
- `;
115
- const getLangAsset = mockPromiseValue(rawLexicon);
116
- const ast = { root: "0" };
117
- const main = { parse: mockPromiseValue(ast) };
118
- const vm = {
119
- createContext: jest.fn(),
120
- runInContext: jest.fn().mockImplementation((data, context) => {
121
- context.window.gcexports.globalLexicon = {};
122
- })
123
- };
124
- const parser = buildParser({ log, cache, getLangAsset, main, vm });
70
+ const err = new Error("main parser failed");
71
+ const main = { parse: mockPromiseError(err) };
72
+ const parser = buildParser({ main });
125
73
  const lang = "0";
126
74
  const src = "'foo'..";
75
+ const lexicon = {};
127
76
 
128
77
  // Act
129
- await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
78
+ await expect(parser.parse(lang, src, lexicon)).rejects.toBe(err);
130
79
 
131
80
  // Assert
132
- expect(getLangAsset).toHaveBeenCalledWith(lang, "/lexicon.js");
133
- expect(main.parse).toHaveBeenCalledWith(src, {});
134
- expect(cache.has(lang)).toBe(true);
135
- expect(cache.get(lang)).toStrictEqual({});
136
- expect(vm.createContext).toHaveBeenCalled();
137
- expect(vm.runInContext).toHaveBeenCalledWith(rawLexicon, expect.anything());
81
+ expect(main.parse).toHaveBeenCalledWith(src, lexicon);
138
82
  });
139
83
  it("should parse error", async () => {
140
84
  // Arrange
141
- const cache = new Map();
142
- const getLangAsset = mockPromiseValue("{}");
143
85
  const err = new Error("End of program reached.");
144
86
  const main = { parse: mockPromiseError(err) };
145
- const parser = buildParser({ log, cache, getLangAsset, main });
87
+ const parser = buildParser({ main });
146
88
  const lang = "0";
147
89
  const src = "'hello, world'";
90
+ const lexicon = {};
148
91
 
149
92
  // Act & Assert
150
- await expect(parser.parse(lang, src)).rejects.toBe(err);
93
+ await expect(parser.parse(lang, src, lexicon)).rejects.toBe(err);
151
94
  });
152
95
  });
153
96
 
@@ -155,7 +98,7 @@ describe("parser integration tests", () => {
155
98
  // Tests using the actual parser
156
99
  it("should parse string literals", async () => {
157
100
  // Arrange & Act
158
- const result = await parser.parse(0, "'hello, world'..");
101
+ const result = await parser.parse(0, "'hello, world'..", basisLexicon);
159
102
 
160
103
  // Assert
161
104
  expect(result).toHaveProperty("root");
@@ -184,7 +127,7 @@ describe("parser integration tests", () => {
184
127
 
185
128
  it("should parse numeric literals", async () => {
186
129
  // Arrange & Act
187
- const result = await parser.parse(0, "42..");
130
+ const result = await parser.parse(0, "42..", basisLexicon);
188
131
 
189
132
  // Assert
190
133
  expect(result).toHaveProperty("root");
@@ -208,7 +151,7 @@ describe("parser integration tests", () => {
208
151
 
209
152
  it("should have a PROG node at the root", async () => {
210
153
  // Let's test the most basic structure that should always work
211
- const result = await parser.parse(0, "123..");
154
+ const result = await parser.parse(0, "123..", basisLexicon);
212
155
 
213
156
  // Assert
214
157
  expect(result).toHaveProperty("root");
@@ -236,9 +179,8 @@ describe("parser integration tests", () => {
236
179
  });
237
180
 
238
181
  it("should parse complex program: apply (<a b: add a b>) [10 20]..", async () => {
239
- // Create parser with custom lexicon
240
- const customLexiconCache = new Map();
241
- customLexiconCache.set(0, {
182
+ // Create custom lexicon
183
+ const customLexicon = {
242
184
  add: {
243
185
  tk: 2,
244
186
  name: "add",
@@ -251,23 +193,10 @@ describe("parser integration tests", () => {
251
193
  cls: "function",
252
194
  length: 2
253
195
  }
254
- });
255
-
256
- // Use the parser with our custom cache
257
- const customParser = buildParser({
258
- log: console.log,
259
- cache: customLexiconCache,
260
- getLangAsset: async () => ({}),
261
- main: {
262
- parse: (src, lexicon) => {
263
- return Promise.resolve(parser.parse(0, src));
264
- }
265
- },
266
- vm
267
- });
196
+ };
268
197
 
269
198
  // Act
270
- const result = await customParser.parse(0, "apply (<a b: add a b>) [10 20]..");
199
+ const result = await parser.parse(0, "apply (<a b: add a b>) [10 20]..", customLexicon);
271
200
 
272
201
  // Assert
273
202
  expect(result).toHaveProperty("root");
@@ -327,7 +256,7 @@ describe("parser integration tests", () => {
327
256
 
328
257
  try {
329
258
  // Unclosed string - missing closing quote
330
- result = await parser.parse(0, "'unclosed string..");
259
+ result = await parser.parse(0, "'unclosed string..", basisLexicon);
331
260
  } catch (e) {
332
261
  // Check for expected error (we should now have a robust parser that doesn't throw)
333
262
  console.error("Unexpected error:", e);
@@ -367,7 +296,7 @@ describe("parser integration tests", () => {
367
296
 
368
297
  try {
369
298
  // Missing closing bracket
370
- result = await parser.parse(0, "[1, 2, 3..");
299
+ result = await parser.parse(0, "[1, 2, 3..", basisLexicon);
371
300
  } catch (e) {
372
301
  console.error("Unexpected error:", e);
373
302
  throw e;
@@ -406,7 +335,7 @@ describe("parser integration tests", () => {
406
335
 
407
336
  try {
408
337
  // Invalid sequence of tokens
409
- result = await parser.parse(0, "if then else..");
338
+ result = await parser.parse(0, "if then else..", basisLexicon);
410
339
  } catch (e) {
411
340
  console.error("Unexpected error:", e);
412
341
  throw e;
@@ -441,32 +370,9 @@ describe("parser integration tests", () => {
441
370
  });
442
371
 
443
372
  it("should perform parse-time evaluation for adding two numbers", async () => {
444
- // Create parser with custom lexicon that defines 'add' function
445
- const customLexiconCache = new Map();
446
- customLexiconCache.set(0, {
447
- add: {
448
- tk: 2,
449
- name: "add",
450
- cls: "function",
451
- length: 2
452
- }
453
- });
454
-
455
- // Use the parser with our custom cache
456
- const customParser = buildParser({
457
- log: console.log,
458
- cache: customLexiconCache,
459
- getLangAsset: async () => ({}),
460
- main: {
461
- parse: (src, lexicon) => {
462
- return Promise.resolve(parser.parse(0, src));
463
- }
464
- },
465
- vm
466
- });
467
-
373
+ // Use basis lexicon which includes add function
468
374
  // Act - parse a simple addition expression
469
- const result = await customParser.parse(0, "add 123 456..");
375
+ const result = await parser.parse(0, "add 123 456..", basisLexicon);
470
376
  console.log(
471
377
  "TEST",
472
378
  "result=" + JSON.stringify(result, null, 2),