yukigo-haskell-parser 0.1.2 → 0.2.1

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 (57) hide show
  1. package/.mocharc.json +3 -3
  2. package/CHANGELOG.md +41 -7
  3. package/README.md +5 -10
  4. package/dist/index.js +21 -13
  5. package/dist/index.js.map +1 -1
  6. package/dist/parser/grammar.cjs +397 -0
  7. package/dist/parser/grammar.cjs.map +1 -0
  8. package/dist/parser/grammar.d.cts +38 -0
  9. package/dist/parser/lexer.d.ts +2 -1
  10. package/dist/parser/lexer.js +6 -1
  11. package/dist/parser/lexer.js.map +1 -1
  12. package/dist/prelude.js +279 -279
  13. package/dist/typechecker/DeclarationCollector.d.ts +2 -0
  14. package/dist/typechecker/DeclarationCollector.js +6 -5
  15. package/dist/typechecker/DeclarationCollector.js.map +1 -1
  16. package/dist/typechecker/TypeBuilder.js +9 -7
  17. package/dist/typechecker/TypeBuilder.js.map +1 -1
  18. package/dist/typechecker/checker.d.ts +9 -4
  19. package/dist/typechecker/checker.js +66 -94
  20. package/dist/typechecker/checker.js.map +1 -1
  21. package/dist/typechecker/core.js +8 -7
  22. package/dist/typechecker/core.js.map +1 -1
  23. package/dist/typechecker/inference.d.ts +4 -1
  24. package/dist/typechecker/inference.js +39 -19
  25. package/dist/typechecker/inference.js.map +1 -1
  26. package/dist/utils/helpers.d.ts +3 -0
  27. package/dist/utils/helpers.js +5 -0
  28. package/dist/utils/helpers.js.map +1 -1
  29. package/dist/utils/types.d.ts +12 -3
  30. package/dist/utils/types.js +34 -22
  31. package/dist/utils/types.js.map +1 -1
  32. package/package.json +9 -10
  33. package/src/index.ts +223 -192
  34. package/src/parser/{grammar.ts → grammar.cjs} +191 -223
  35. package/src/parser/grammar.d.ts +3 -0
  36. package/src/parser/grammar.ne +521 -518
  37. package/src/parser/lexer.ts +357 -353
  38. package/src/prelude.ts +281 -281
  39. package/src/typechecker/DeclarationCollector.ts +160 -157
  40. package/src/typechecker/TypeBuilder.ts +153 -150
  41. package/src/typechecker/checker.ts +487 -501
  42. package/src/typechecker/core.ts +195 -192
  43. package/src/typechecker/inference.ts +1442 -1421
  44. package/src/utils/helpers.ts +34 -29
  45. package/src/utils/types.ts +75 -63
  46. package/tests/hspec.spec.ts +95 -92
  47. package/tests/lexer.spec.ts +175 -175
  48. package/tests/parser.spec.ts +838 -829
  49. package/tests/prelude.spec.ts +17 -17
  50. package/tests/typechecker.spec.ts +326 -327
  51. package/tsconfig.build.json +16 -0
  52. package/tsconfig.build.tsbuildinfo +1 -0
  53. package/tsconfig.json +29 -26
  54. package/tsconfig.tsbuildinfo +1 -1
  55. package/dist/parser/grammar.d.ts +0 -28
  56. package/dist/parser/grammar.js +0 -393
  57. package/dist/parser/grammar.js.map +0 -1
@@ -1,175 +1,175 @@
1
- import { assert, expect } from "chai";
2
- import { HaskellLayoutLexer } from "../src/parser/lexer.js";
3
-
4
- describe("HaskellLayoutLexer", () => {
5
- let lexer: HaskellLayoutLexer;
6
-
7
- beforeEach(() => {
8
- lexer = new HaskellLayoutLexer();
9
- });
10
-
11
- // Helper: Returns array of objects { type, value } for better debugging
12
- const getTokens = (input: string) => {
13
- lexer.reset(input);
14
- const tokens = [];
15
- for (const token of lexer) {
16
- tokens.push({ type: token.type, value: token.value });
17
- }
18
- return tokens;
19
- };
20
-
21
- const getTypes = (input: string) => getTokens(input).map((t) => t.type);
22
-
23
- it("should ignore basic whitespace", () => {
24
- const input = "x = 5";
25
- const types = getTypes(input);
26
- expect(types).to.not.include("WS");
27
- expect(types).to.not.include("NL");
28
- expect(types).to.include("assign");
29
- });
30
-
31
- describe("Layout Rule: 'do' blocks", () => {
32
- it("should inject braces for indented 'do' block", () => {
33
- const input = `
34
- do
35
- x
36
- y
37
- `;
38
- const tokens = getTokens(input);
39
- // We check values for keywords since type is now generic 'keyword' or 'variable'
40
- expect(tokens[0].value).to.equal("do");
41
- expect(tokens[1].type).to.equal("lbracket"); // { Injected
42
- expect(tokens[2].value).to.equal("x");
43
- expect(tokens[3].type).to.equal("semicolon"); // ; Injected
44
- expect(tokens[4].value).to.equal("y");
45
- expect(tokens[5].type).to.equal("rbracket"); // } Injected
46
- });
47
-
48
- it("should handle explicit braces (no layout injection)", () => {
49
- const input = "do { x; y }";
50
- const types = getTypes(input);
51
- expect(types).to.include("lbracket");
52
- expect(types).to.include("semicolon");
53
- expect(types).to.include("rbracket");
54
- // Ensure we didn't double up
55
- expect(types.filter((t) => t === "lbracket").length).to.equal(1);
56
- });
57
- });
58
-
59
- describe("Layout Rule: 'let' blocks", () => {
60
- it("should inject braces and handle 'in' dedent", () => {
61
- const input = `
62
- let
63
- x = 1
64
- in x
65
- `;
66
- const tokens = getTokens(input);
67
-
68
- const letIndex = tokens.findIndex((t) => t.value === "let");
69
- expect(tokens[letIndex + 1].type).to.equal("lbracket");
70
-
71
- const inIndex = tokens.findIndex((t) => t.value === "in");
72
- expect(tokens[inIndex - 1].type).to.equal("rbracket");
73
- });
74
- });
75
-
76
- describe("Layout Rule: 'where' clauses", () => {
77
- it("should handle nested blocks correctly", () => {
78
- const input = `
79
- f = do
80
- let x = 1
81
- return x
82
- `;
83
- const types = getTypes(input);
84
-
85
- const lbrackets = types.filter((t) => t === "lbracket").length;
86
- const rbrackets = types.filter((t) => t === "rbracket").length;
87
- const semicolons = types.filter((t) => t === "semicolon").length;
88
-
89
- expect(lbrackets).to.equal(2);
90
- expect(rbrackets).to.equal(2);
91
- expect(semicolons).to.equal(1);
92
- });
93
- });
94
-
95
- describe("Layout Rule: Separators", () => {
96
- it("should inject semicolons for aligned declarations", () => {
97
- // NOTE: We removed the leading newline here.
98
- // If we keep the newline, the lexer will insert a semicolon before 'x' too
99
- // (because x is at col 1, which matches stack 1).
100
- const input = "x = 1\ny = 2";
101
-
102
- const tokens = getTokens(input);
103
-
104
- // Expected: x, =, 1, SEMICOLON, y, =, 2
105
- const semiToken = tokens.find((t) => t.type === "semicolon");
106
- expect(semiToken).to.not.be.undefined;
107
-
108
- const semiIndex = tokens.findIndex((t) => t.type === "semicolon");
109
- // Should be after '1' (index 2) and before 'y' (index 4)
110
- expect(semiIndex).to.equal(3);
111
- });
112
- });
113
-
114
- describe("Nearley Integration Requirements", () => {
115
- it("should save and restore state correctly (Backtracking)", () => {
116
- const input = `
117
- do
118
- x
119
- y
120
- `;
121
- lexer.reset(input);
122
-
123
- lexer.next();
124
- lexer.next();
125
-
126
- const state = lexer.save();
127
-
128
- const t1 = lexer.next();
129
- const t2 = lexer.next();
130
-
131
- lexer.reset(input, state);
132
-
133
- const t1_restored = lexer.next();
134
- const t2_restored = lexer.next();
135
-
136
- expect(t1_restored?.value).to.equal(t1?.value);
137
- expect(t2_restored?.type).to.equal(t2?.type);
138
- });
139
- });
140
- describe("Layout Rule: Separators", () => {
141
- it("should not inject semicolons or blocks for if-then-else", () => {
142
- const input = "f x = if x < 4 then 10 else 20";
143
- const input2 = "f x = \n if x < 4\nthen 10\nelse 20";
144
- const inlinetokes = getTokens(input);
145
- const multiTokens = getTokens(input2);
146
- expect(multiTokens.length).to.eq(inlinetokes.length);
147
- multiTokens.forEach((tok, i) => {
148
- const inlineTok = inlinetokes[i];
149
- expect(tok.type).to.eq(inlineTok.type);
150
- expect(tok.value).to.eq(inlineTok.value);
151
- });
152
- });
153
- });
154
- describe("Layout Rule: 'where' clauses (Multi-line)", () => {
155
- it("should handle multi-line where clause with alignment", () => {
156
- const input = `
157
- areaOfCircle radius = pi * radius_squared
158
- where pi = 3.14159
159
- radius_squared = radius * radius`;
160
- const tokens = getTokens(input);
161
-
162
- const whereIndex = tokens.findIndex((t) => t.value === "where");
163
- expect(whereIndex).to.not.equal(-1);
164
- expect(tokens[whereIndex + 1].type).to.equal("lbracket");
165
-
166
- const semiIndex = tokens.findIndex(
167
- (t, i) => i > whereIndex && t.type === "semicolon"
168
- );
169
- expect(semiIndex).to.not.equal(-1);
170
-
171
- const lastToken = tokens[tokens.length - 1];
172
- expect(lastToken.type).to.equal("rbracket");
173
- });
174
- });
175
- });
1
+ import { assert, expect } from "chai";
2
+ import { HaskellLayoutLexer } from "../src/parser/lexer.js";
3
+
4
+ describe("HaskellLayoutLexer", () => {
5
+ let lexer: HaskellLayoutLexer;
6
+
7
+ beforeEach(() => {
8
+ lexer = new HaskellLayoutLexer();
9
+ });
10
+
11
+ // Helper: Returns array of objects { type, value } for better debugging
12
+ const getTokens = (input: string) => {
13
+ lexer.reset(input);
14
+ const tokens = [];
15
+ for (const token of lexer) {
16
+ tokens.push({ type: token.type, value: token.value });
17
+ }
18
+ return tokens;
19
+ };
20
+
21
+ const getTypes = (input: string) => getTokens(input).map((t) => t.type);
22
+
23
+ it("should ignore basic whitespace", () => {
24
+ const input = "x = 5";
25
+ const types = getTypes(input);
26
+ expect(types).to.not.include("WS");
27
+ expect(types).to.not.include("NL");
28
+ expect(types).to.include("assign");
29
+ });
30
+
31
+ describe("Layout Rule: 'do' blocks", () => {
32
+ it("should inject braces for indented 'do' block", () => {
33
+ const input = `
34
+ do
35
+ x
36
+ y
37
+ `;
38
+ const tokens = getTokens(input);
39
+ // We check values for keywords since type is now generic 'keyword' or 'variable'
40
+ expect(tokens[0].value).to.equal("do");
41
+ expect(tokens[1].type).to.equal("lbracket"); // { Injected
42
+ expect(tokens[2].value).to.equal("x");
43
+ expect(tokens[3].type).to.equal("semicolon"); // ; Injected
44
+ expect(tokens[4].value).to.equal("y");
45
+ expect(tokens[5].type).to.equal("rbracket"); // } Injected
46
+ });
47
+
48
+ it("should handle explicit braces (no layout injection)", () => {
49
+ const input = "do { x; y }";
50
+ const types = getTypes(input);
51
+ expect(types).to.include("lbracket");
52
+ expect(types).to.include("semicolon");
53
+ expect(types).to.include("rbracket");
54
+ // Ensure we didn't double up
55
+ expect(types.filter((t) => t === "lbracket").length).to.equal(1);
56
+ });
57
+ });
58
+
59
+ describe("Layout Rule: 'let' blocks", () => {
60
+ it("should inject braces and handle 'in' dedent", () => {
61
+ const input = `
62
+ let
63
+ x = 1
64
+ in x
65
+ `;
66
+ const tokens = getTokens(input);
67
+
68
+ const letIndex = tokens.findIndex((t) => t.value === "let");
69
+ expect(tokens[letIndex + 1].type).to.equal("lbracket");
70
+
71
+ const inIndex = tokens.findIndex((t) => t.value === "in");
72
+ expect(tokens[inIndex - 1].type).to.equal("rbracket");
73
+ });
74
+ });
75
+
76
+ describe("Layout Rule: 'where' clauses", () => {
77
+ it("should handle nested blocks correctly", () => {
78
+ const input = `
79
+ f = do
80
+ let x = 1
81
+ return x
82
+ `;
83
+ const types = getTypes(input);
84
+
85
+ const lbrackets = types.filter((t) => t === "lbracket").length;
86
+ const rbrackets = types.filter((t) => t === "rbracket").length;
87
+ const semicolons = types.filter((t) => t === "semicolon").length;
88
+
89
+ expect(lbrackets).to.equal(2);
90
+ expect(rbrackets).to.equal(2);
91
+ expect(semicolons).to.equal(1);
92
+ });
93
+ });
94
+
95
+ describe("Layout Rule: Separators", () => {
96
+ it("should inject semicolons for aligned declarations", () => {
97
+ // NOTE: We removed the leading newline here.
98
+ // If we keep the newline, the lexer will insert a semicolon before 'x' too
99
+ // (because x is at col 1, which matches stack 1).
100
+ const input = "x = 1\ny = 2";
101
+
102
+ const tokens = getTokens(input);
103
+
104
+ // Expected: x, =, 1, SEMICOLON, y, =, 2
105
+ const semiToken = tokens.find((t) => t.type === "semicolon");
106
+ expect(semiToken).to.not.be.undefined;
107
+
108
+ const semiIndex = tokens.findIndex((t) => t.type === "semicolon");
109
+ // Should be after '1' (index 2) and before 'y' (index 4)
110
+ expect(semiIndex).to.equal(3);
111
+ });
112
+ });
113
+
114
+ describe("Nearley Integration Requirements", () => {
115
+ it("should save and restore state correctly (Backtracking)", () => {
116
+ const input = `
117
+ do
118
+ x
119
+ y
120
+ `;
121
+ lexer.reset(input);
122
+
123
+ lexer.next();
124
+ lexer.next();
125
+
126
+ const state = lexer.save();
127
+
128
+ const t1 = lexer.next();
129
+ const t2 = lexer.next();
130
+
131
+ lexer.reset(input, state);
132
+
133
+ const t1_restored = lexer.next();
134
+ const t2_restored = lexer.next();
135
+
136
+ expect(t1_restored?.value).to.equal(t1?.value);
137
+ expect(t2_restored?.type).to.equal(t2?.type);
138
+ });
139
+ });
140
+ describe("Layout Rule: Separators", () => {
141
+ it("should not inject semicolons or blocks for if-then-else", () => {
142
+ const input = "f x = if x < 4 then 10 else 20";
143
+ const input2 = "f x = \n if x < 4\nthen 10\nelse 20";
144
+ const inlinetokes = getTokens(input);
145
+ const multiTokens = getTokens(input2);
146
+ expect(multiTokens.length).to.eq(inlinetokes.length);
147
+ multiTokens.forEach((tok, i) => {
148
+ const inlineTok = inlinetokes[i];
149
+ expect(tok.type).to.eq(inlineTok.type);
150
+ expect(tok.value).to.eq(inlineTok.value);
151
+ });
152
+ });
153
+ });
154
+ describe("Layout Rule: 'where' clauses (Multi-line)", () => {
155
+ it("should handle multi-line where clause with alignment", () => {
156
+ const input = `
157
+ areaOfCircle radius = pi * radius_squared
158
+ where pi = 3.14159
159
+ radius_squared = radius * radius`;
160
+ const tokens = getTokens(input);
161
+
162
+ const whereIndex = tokens.findIndex((t) => t.value === "where");
163
+ expect(whereIndex).to.not.equal(-1);
164
+ expect(tokens[whereIndex + 1].type).to.equal("lbracket");
165
+
166
+ const semiIndex = tokens.findIndex(
167
+ (t, i) => i > whereIndex && t.type === "semicolon"
168
+ );
169
+ expect(semiIndex).to.not.equal(-1);
170
+
171
+ const lastToken = tokens[tokens.length - 1];
172
+ expect(lastToken.type).to.equal("rbracket");
173
+ });
174
+ });
175
+ });