yukigo-haskell-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.
Files changed (63) hide show
  1. package/.mocharc.json +4 -0
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +10 -0
  4. package/dist/index.d.ts +7 -0
  5. package/dist/index.js +54 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/parser/grammar.d.ts +28 -0
  8. package/dist/parser/grammar.js +404 -0
  9. package/dist/parser/grammar.js.map +1 -0
  10. package/dist/parser/layoutPreprocessor.d.ts +1 -0
  11. package/dist/parser/layoutPreprocessor.js +22 -0
  12. package/dist/parser/layoutPreprocessor.js.map +1 -0
  13. package/dist/parser/lexer.d.ts +42 -0
  14. package/dist/parser/lexer.js +75 -0
  15. package/dist/parser/lexer.js.map +1 -0
  16. package/dist/parser/preprocessor.d.ts +28 -0
  17. package/dist/parser/preprocessor.js +453 -0
  18. package/dist/parser/preprocessor.js.map +1 -0
  19. package/dist/prelude.d.ts +1 -0
  20. package/dist/prelude.js +205 -0
  21. package/dist/prelude.js.map +1 -0
  22. package/dist/typechecker/DeclarationCollector.d.ts +15 -0
  23. package/dist/typechecker/DeclarationCollector.js +80 -0
  24. package/dist/typechecker/DeclarationCollector.js.map +1 -0
  25. package/dist/typechecker/TypeBuilder.d.ts +12 -0
  26. package/dist/typechecker/TypeBuilder.js +113 -0
  27. package/dist/typechecker/TypeBuilder.js.map +1 -0
  28. package/dist/typechecker/checker.d.ts +80 -0
  29. package/dist/typechecker/checker.js +269 -0
  30. package/dist/typechecker/checker.js.map +1 -0
  31. package/dist/typechecker/core.d.ts +14 -0
  32. package/dist/typechecker/core.js +142 -0
  33. package/dist/typechecker/core.js.map +1 -0
  34. package/dist/typechecker/inference.d.ts +69 -0
  35. package/dist/typechecker/inference.js +984 -0
  36. package/dist/typechecker/inference.js.map +1 -0
  37. package/dist/utils/helpers.d.ts +2 -0
  38. package/dist/utils/helpers.js +25 -0
  39. package/dist/utils/helpers.js.map +1 -0
  40. package/dist/utils/types.d.ts +6 -0
  41. package/dist/utils/types.js +53 -0
  42. package/dist/utils/types.js.map +1 -0
  43. package/package.json +36 -0
  44. package/src/index.ts +55 -0
  45. package/src/parser/grammar.ne +463 -0
  46. package/src/parser/grammar.ts +512 -0
  47. package/src/parser/layoutPreprocessor.ts +21 -0
  48. package/src/parser/lexer.ts +77 -0
  49. package/src/parser/preprocessor.ne +375 -0
  50. package/src/parser/preprocessor.ts +502 -0
  51. package/src/prelude.ts +206 -0
  52. package/src/typechecker/DeclarationCollector.ts +104 -0
  53. package/src/typechecker/TypeBuilder.ts +148 -0
  54. package/src/typechecker/checker.ts +395 -0
  55. package/src/typechecker/core.ts +162 -0
  56. package/src/typechecker/inference.ts +1327 -0
  57. package/src/utils/helpers.ts +29 -0
  58. package/src/utils/types.ts +56 -0
  59. package/tests/parser.spec.ts +823 -0
  60. package/tests/prelude.spec.ts +22 -0
  61. package/tests/preprocessor.spec.ts +69 -0
  62. package/tests/typechecker.spec.ts +310 -0
  63. package/tsconfig.json +17 -0
@@ -0,0 +1,22 @@
1
+ import { YukigoHaskellParser } from "../src/index.js";
2
+ import { assert } from "chai";
3
+ import { readFileSync } from "fs";
4
+ import { preprocessor } from "../src/parser/layoutPreprocessor.js";
5
+ import { preludeCode } from "../src/prelude.js";
6
+
7
+ describe("PreludePdP Tests", () => {
8
+ let parser: YukigoHaskellParser;
9
+ beforeEach(() => {
10
+ parser = new YukigoHaskellParser("");
11
+ });
12
+ it("preprocesses PreludePdP", () => {
13
+ assert.doesNotThrow(() => preprocessor(preludeCode));
14
+ });
15
+ it("parses PreludePdP", () => {
16
+ assert.doesNotThrow(() => parser.parse(preludeCode));
17
+ });
18
+ it("validates PreludePdP", () => {
19
+ parser.parse(preludeCode);
20
+ assert.isEmpty(parser.errors);
21
+ });
22
+ });
@@ -0,0 +1,69 @@
1
+ import { assert } from "chai";
2
+ import { preprocessor } from "../src/parser/layoutPreprocessor.js";
3
+ import { PreprocessorLexer } from "../src/parser/lexer.js";
4
+
5
+ describe("Preprocessor Tests", () => {
6
+ it("processes where clauses correctly", () => {
7
+ const expectedCode =
8
+ "f x = x * pi where { pi = 3.14; anotherBinding = 200 }";
9
+ assert.equal(
10
+ preprocessor(`f x = x * pi
11
+ where
12
+ pi = 3.14
13
+ anotherBinding = 200`),
14
+ expectedCode
15
+ );
16
+ assert.equal(
17
+ preprocessor(`f x = x * pi where
18
+ pi = 3.14
19
+ anotherBinding = 200`),
20
+ expectedCode
21
+ );
22
+ assert.equal(
23
+ preprocessor(`f x = x * pi where pi = 3.14; anotherBinding = 200`),
24
+ expectedCode
25
+ );
26
+ assert.equal(
27
+ preprocessor(`f x = x where x = 1`),
28
+ `f x = x where { x = 1 }`
29
+ );
30
+ assert.equal(
31
+ preprocessor(`f x = x where { x = 1 }`),
32
+ `f x = x where { x = 1 }`
33
+ );
34
+ assert.equal(
35
+ preprocessor(`g y = y * z
36
+ where
37
+ z = 10
38
+ h w = w where w = 2`),
39
+ `g y = y * z where { z = 10 }
40
+ h w = w where { w = 2 }`
41
+ );
42
+ });
43
+ it("processes let...in expressions correctly", () => {
44
+ const expectedCode = "f = let { x = 1; y = 2 } in x + y";
45
+ assert.equal(preprocessor(`f = let x = 1; y = 2 in x + y`), expectedCode);
46
+ assert.equal(preprocessor(`f = let {x=1;y=2} in x + y`), expectedCode);
47
+ assert.equal(
48
+ preprocessor(`f = let
49
+ x = 1
50
+ y = 2
51
+ in x + y`),
52
+ expectedCode
53
+ );
54
+ assert.equal(
55
+ preprocessor(`
56
+ f =
57
+ let x = 1
58
+ y = 2
59
+ in x + y`),
60
+ expectedCode
61
+ );
62
+ assert.equal(
63
+ preprocessor(
64
+ `hypotenuse a b =\n let\n a_sq = a * a\n b_sq = b * b\n sum_sq = a_sq + b_sq\n in\n sqrt sum_sq`
65
+ ),
66
+ `hypotenuse a b = let { a_sq = a * a; b_sq = b * b; sum_sq = a_sq + b_sq } in sqrt sum_sq`
67
+ );
68
+ });
69
+ });
@@ -0,0 +1,310 @@
1
+ import { inspect } from "util";
2
+ import { YukigoHaskellParser } from "../src/index.js";
3
+ import { assert } from "chai";
4
+
5
+ describe("TypeChecker Tests", () => {
6
+ let parser: YukigoHaskellParser;
7
+ beforeEach(() => {
8
+ parser = new YukigoHaskellParser();
9
+ });
10
+ it("accepts function with correct type signature", () => {
11
+ const code = `length' :: [a] -> Int\r\nlength' [] = 0\r\nlength' (_:xs) = 1 + length' xs`;
12
+ parser.parse(code);
13
+ assert.isEmpty(parser.errors);
14
+ });
15
+ it("validates correct usage of logical operation", () => {
16
+ const code = `f :: Bool -> Bool -> Bool\r\nf x y = x && y`;
17
+ parser.parse(code);
18
+ assert.isEmpty(parser.errors);
19
+ });
20
+ it("detects incorrect usage of logical operation", () => {
21
+ const code = `f :: String -> Bool -> Bool\r\nf x y = x && y`;
22
+ parser.parse(code);
23
+ assert.include(
24
+ parser.errors,
25
+ "Type error in 'f': Left side of And must be a boolean"
26
+ );
27
+ });
28
+ it("validates logical operation without signature", () => {
29
+ const code = `f x y = x && y`;
30
+ parser.parse(code);
31
+ assert.isEmpty(parser.errors);
32
+ });
33
+ it("detects return type mismatch", () => {
34
+ const code = `toInt :: String -> Int\r\ntoInt s = s`;
35
+ parser.parse(code);
36
+
37
+ assert.include(
38
+ parser.errors,
39
+ "Type error in 'toInt': Cannot unify YuString with YuNumber"
40
+ );
41
+ });
42
+
43
+ it("detects parameter type mismatch", () => {
44
+ const code = `head' :: [Int] -> Int\r\nhead' (x:_) = x\r\nhead' [] = 0\r\nfirst :: [Char] -> Char\r\nfirst list = head' list`;
45
+ parser.parse(code);
46
+ assert.include(
47
+ parser.errors,
48
+ "Type error in 'first': Cannot apply [YuChar] to type [YuNumber] -> YuNumber"
49
+ );
50
+ });
51
+ it("detects bad application of parameters in tuple", () => {
52
+ const code = `f :: (Int, Int, Int) -> Int\r\nf (x,y,z)= maximum (x y z) - maximum (x y z)`;
53
+ parser.parse(code);
54
+ assert.include(
55
+ parser.errors,
56
+ "Type error in 'f': Cannot apply YuNumber to type YuNumber"
57
+ );
58
+ });
59
+ it("validates correct concat operation of lists", () => {
60
+ const code = `f = [2] ++ [4]`;
61
+ parser.parse(code);
62
+ assert.isEmpty(parser.errors);
63
+ });
64
+ it("detects incorrect concat operation of lists", () => {
65
+ const code = `f = [2] ++ ["4"]`;
66
+ parser.parse(code);
67
+ assert.include(
68
+ parser.errors,
69
+ "Type error in 'f': Concat operation requires both operands to be lists of the same type."
70
+ );
71
+ });
72
+
73
+ it("validates polymorphic function signatures", () => {
74
+ const code = `id :: a -> a\nid x = x`;
75
+ parser.parse(code);
76
+ assert.isEmpty(
77
+ parser.errors,
78
+ "Polymorphic identity function should be valid"
79
+ );
80
+ });
81
+ it("validates cons pattern correct usage", () => {
82
+ const code = `f (x:xs) = x + f xs`;
83
+ parser.parse(code);
84
+ assert.isEmpty(parser.errors);
85
+ });
86
+ it("validates empty list pattern", () => {
87
+ const code = `f [] = []`;
88
+ parser.parse(code);
89
+ assert.isEmpty(parser.errors);
90
+ });
91
+ it("validates empty list pattern and cons pattern in function with multiple equations", () => {
92
+ const code = `f [] = []\r\nf (x:xs)= x : f xs`;
93
+ parser.parse(code);
94
+ assert.isEmpty(parser.errors);
95
+ });
96
+ it("validates list pattern with elements", () => {
97
+ const code = `f [y] = y`;
98
+ parser.parse(code);
99
+ assert.isEmpty(parser.errors);
100
+ });
101
+ it("detects duplicate variable with list pattern", () => {
102
+ const code = `f [y] y = y`;
103
+ parser.parse(code);
104
+ assert.include(
105
+ parser.errors,
106
+ "Type error in 'f': Duplicate variable name 'y'"
107
+ );
108
+ });
109
+ it("validates tuple pattern", () => {
110
+ const code = `f (x, y) = y > 21`;
111
+ parser.parse(code);
112
+ assert.isEmpty(parser.errors);
113
+ });
114
+ it("detects incorrect polymorphic usage", () => {
115
+ const code = `id :: a -> a\nid x = x + "not polymorphic"`;
116
+ parser.parse(code);
117
+
118
+ assert.include(
119
+ parser.errors,
120
+ "Type error in 'id': Right operand of Plus must be a number"
121
+ );
122
+ });
123
+
124
+ it("validates typeclass constraints", () => {
125
+ const code = `f :: Num a => a -> a\r\nf x = x + 2`;
126
+ const ast = parser.parse(code);
127
+ assert.isEmpty(parser.errors, "Should accept valid Show constraint");
128
+ });
129
+ it("detects incorrect typeclass constraints", () => {
130
+ const code = `f :: Num a => a -> a\r\nf x = x ++ "2"`;
131
+ parser.parse(code);
132
+ assert.include(
133
+ parser.errors,
134
+ "Type error in 'f': Type 'YuString' is not an instance of 'Num'"
135
+ );
136
+ });
137
+ it("detects arithmetic type errors", () => {
138
+ const code = `invalidAdd = "text" + 42`;
139
+ parser.parse(code);
140
+
141
+ assert.include(
142
+ parser.errors,
143
+ "Type error in 'invalidAdd': Left operand of Plus must be a number"
144
+ );
145
+ });
146
+
147
+ it("validates higher-order function types", () => {
148
+ const code = `apply :: (a -> b) -> a -> b\napply f x = f x`;
149
+ parser.parse(code);
150
+ assert.isEmpty(parser.errors, "Should accept valid higher-order function");
151
+ });
152
+
153
+ it("detects higher-order function misuse", () => {
154
+ const code = `apply :: (Int -> String) -> Int -> String\napply f x = f (x + "error")`;
155
+ parser.parse(code);
156
+
157
+ assert.include(
158
+ parser.errors,
159
+ "Type error in 'apply': Right operand of Plus must be a number"
160
+ );
161
+ });
162
+
163
+ it("validates recursive function types", () => {
164
+ const code = `factorial :: Int -> Int\nfactorial 0 = 1\nfactorial n = n * factorial (n - 1)`;
165
+ parser.parse(code);
166
+ assert.isEmpty(parser.errors, "Should accept valid recursive function");
167
+ });
168
+
169
+ it("detects recursive type errors", () => {
170
+ const code = `badFactorial :: Int -> String\nbadFactorial 0 = "1"\nbadFactorial n = n * badFactorial (n - 1)`;
171
+ parser.parse(code);
172
+ assert.include(
173
+ parser.errors,
174
+ "Type error in 'badFactorial': Right operand of Multiply must be a number"
175
+ );
176
+ });
177
+
178
+ it("validates data constructor types", () => {
179
+ const code = `fromMaybe :: a -> Maybe a -> a\nfromMaybe d Nothing = d\nfromMaybe _ (Just x) = x`;
180
+ parser.parse(code);
181
+ assert.isEmpty(parser.errors, "Should accept valid Maybe implementation");
182
+ });
183
+
184
+ it("detects data constructor type errors", () => {
185
+ const code = `data Foo = Bar String | Baz\r\nf :: Int -> Foo\r\nf x = Bar x`;
186
+ parser.parse(code);
187
+ assert.include(
188
+ parser.errors,
189
+ "Type error in 'f': Cannot apply YuNumber to type YuString -> Foo t657"
190
+ );
191
+ });
192
+ it("validates correct usage of map", () => {
193
+ const code = `f :: [Int] -> [Int]\nf xs = map (\\x -> x * 2) xs`;
194
+ parser.parse(code);
195
+ assert.isEmpty(parser.errors);
196
+ });
197
+ it("detects incorrect usage of map", () => {
198
+ const code = `f :: [Int] -> [Int]\nf xs = map (\\x -> x ++ "2") xs`;
199
+ parser.parse(code);
200
+ assert.include(
201
+ parser.errors,
202
+ "Type error in 'f': Cannot unify YuString with YuNumber"
203
+ );
204
+ });
205
+ it("detects incorrect arity in map", () => {
206
+ const code = `f :: [Int] -> [Int]\nf xs = map (\\x y -> x + 2) xs`;
207
+ parser.parse(code);
208
+ assert.include(
209
+ parser.errors,
210
+ "Type error in 'f': Cannot unify t659 -> YuNumber with YuNumber"
211
+ );
212
+ });
213
+ it("validates correct usage of filter", () => {
214
+ const code = `f :: [Int] -> [Int]\nf xs = filter (\\x -> x == 2) xs`;
215
+ parser.parse(code);
216
+ assert.isEmpty(parser.errors);
217
+ });
218
+ it("detects incorrect usage of filter", () => {
219
+ const code = `f :: [Int] -> [Int]\nf xs = filter (\\x -> x ++ "2") xs`;
220
+ parser.parse(code);
221
+ assert.include(
222
+ parser.errors,
223
+ "Type error in 'f': Cannot apply t657 -> YuString to type (t656 -> YuBoolean) -> [t656] -> [t656]"
224
+ );
225
+ });
226
+ it("handles multiple type errors", () => {
227
+ const code = `f :: Int -> String\nf x = x + "error"\ng = 42`;
228
+ parser.parse(code);
229
+ assert.equal(parser.errors.length, 1);
230
+ assert.include(
231
+ parser.errors,
232
+ "Type error in 'f': Right operand of Plus must be a number"
233
+ );
234
+ });
235
+ it("detects incorrect use of maximum with not ordenable type", () => {
236
+ const code = `f :: [Boolean] -> Boolean\nf xs = maximum xs`;
237
+ parser.parse(code);
238
+ assert.include(
239
+ parser.errors,
240
+ "Type error in 'f': Type 'YuBoolean' is not an instance of 'Ord'"
241
+ );
242
+ });
243
+ it("validates correct use of arithmetic operator round", () => {
244
+ const code = `f :: Int -> Int\nf x = round x`;
245
+ parser.parse(code);
246
+ assert.isEmpty(parser.errors);
247
+ });
248
+ it("detects incorrect use of arithmetic operator round", () => {
249
+ const code = `f :: String -> Int\nf x = round x`;
250
+ parser.parse(code);
251
+ assert.include(
252
+ parser.errors,
253
+ "Type error in 'f': Operand of Round must be a number"
254
+ );
255
+ });
256
+ it("validates correct usage of function without signature defined after application", () => {
257
+ const code = `f :: String -> String\nf x = g x\n\ng y = y + 2`;
258
+ parser.parse(code);
259
+ assert.isEmpty(parser.errors);
260
+ });
261
+ it("validates correct usage of where clause", () => {
262
+ const code = `areaOfCircle radius = pi * radius_squared\n where\n pi = 3.14159\n radius_squared = radius * radius`;
263
+ parser.parse(code);
264
+ assert.isEmpty(parser.errors);
265
+ });
266
+ it("detects invalid type with where clause", () => {
267
+ const code = `areaOfCircle :: Int -> Int\r\nareaOfCircle radius = pi * radius_squared\n where\n pi = "3.14159"\n radius_squared = radius * radius`;
268
+ parser.parse(code);
269
+ assert.include(
270
+ parser.errors,
271
+ "Type error in 'areaOfCircle': Left operand of Multiply must be a number"
272
+ );
273
+ });
274
+ it("validates correct usage of let...in expression", () => {
275
+ const code = `hypotenuse a b = let a_sq = a * a; b_sq = b * b; sum_sq = a_sq + b_sq in sqrt sum_sq`;
276
+ parser.parse(code);
277
+ assert.isEmpty(parser.errors);
278
+ });
279
+ it("detects incorrect usage of let...in expression", () => {
280
+ const code = `hypotenuse a b = let a_sq = a * a; b_sq = b * b; sum_sq = a_sq + b_sq in sum_sq ++ "a string"`;
281
+ parser.parse(code);
282
+ assert.include(
283
+ parser.errors,
284
+ "Type error in 'hypotenuse': String operation requires string operands"
285
+ );
286
+ });
287
+ it("detects incorrect usage of let...in expression", () => {
288
+ const code = `hypotenuse a b = let a_sq = a * a; b_sq = b * b; sum_sq = a_sq ++ b_sq in sqrt sum_sq`;
289
+ parser.parse(code);
290
+ assert.include(
291
+ parser.errors,
292
+ "Type error in 'hypotenuse': Type error in 'sum_sq': Left operand of concat must be a list, got YuNumber"
293
+ );
294
+ });
295
+ it("validates infix partial application", () => {
296
+ const code = `doble numero = (*) numero 2`;
297
+ parser.parse(code);
298
+ assert.isEmpty(parser.errors);
299
+ });
300
+ it("validates infix partial application", () => {
301
+ const code = `doble numero = (* 2) numero`;
302
+ parser.parse(code);
303
+ assert.isEmpty(parser.errors);
304
+ });
305
+ it("validates infix partial application", () => {
306
+ const code = `doble numero = (2 *) numero`;
307
+ parser.parse(code);
308
+ assert.isEmpty(parser.errors);
309
+ });
310
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2024",
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
+ "lib": ["ES2024"],
7
+ "outDir": "./dist",
8
+ "declaration": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "sourceMap": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["src"],
16
+ "exclude": ["dist/**/*"]
17
+ }