yukigo-haskell-parser 0.1.0 → 0.1.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 (64) hide show
  1. package/.mocharc.json +3 -3
  2. package/README.md +10 -10
  3. package/dist/index.d.ts +13 -2
  4. package/dist/index.js +112 -32
  5. package/dist/index.js.map +1 -1
  6. package/dist/parser/grammar.js +220 -231
  7. package/dist/parser/grammar.js.map +1 -1
  8. package/dist/parser/lexer.d.ts +70 -15
  9. package/dist/parser/lexer.js +259 -50
  10. package/dist/parser/lexer.js.map +1 -1
  11. package/dist/prelude.d.ts +1 -1
  12. package/dist/prelude.js +280 -204
  13. package/dist/prelude.js.map +1 -1
  14. package/dist/typechecker/DeclarationCollector.d.ts +6 -1
  15. package/dist/typechecker/DeclarationCollector.js +39 -0
  16. package/dist/typechecker/DeclarationCollector.js.map +1 -1
  17. package/dist/typechecker/TypeBuilder.d.ts +1 -1
  18. package/dist/typechecker/TypeBuilder.js +6 -2
  19. package/dist/typechecker/TypeBuilder.js.map +1 -1
  20. package/dist/typechecker/checker.d.ts +15 -4
  21. package/dist/typechecker/checker.js +108 -25
  22. package/dist/typechecker/checker.js.map +1 -1
  23. package/dist/typechecker/core.d.ts +4 -0
  24. package/dist/typechecker/core.js +45 -19
  25. package/dist/typechecker/core.js.map +1 -1
  26. package/dist/typechecker/inference.d.ts +5 -1
  27. package/dist/typechecker/inference.js +95 -21
  28. package/dist/typechecker/inference.js.map +1 -1
  29. package/dist/utils/helpers.d.ts +1 -1
  30. package/dist/utils/helpers.js +1 -1
  31. package/dist/utils/helpers.js.map +1 -1
  32. package/dist/utils/types.d.ts +1 -1
  33. package/dist/utils/types.js +7 -0
  34. package/dist/utils/types.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/index.ts +192 -55
  37. package/src/parser/grammar.ne +519 -463
  38. package/src/parser/grammar.ts +230 -239
  39. package/src/parser/lexer.ts +353 -77
  40. package/src/prelude.ts +282 -206
  41. package/src/typechecker/DeclarationCollector.ts +157 -104
  42. package/src/typechecker/TypeBuilder.ts +150 -148
  43. package/src/typechecker/checker.ts +501 -395
  44. package/src/typechecker/core.ts +192 -162
  45. package/src/typechecker/inference.ts +1421 -1327
  46. package/src/utils/helpers.ts +29 -29
  47. package/src/utils/types.ts +63 -56
  48. package/tests/hspec.spec.ts +92 -0
  49. package/tests/lexer.spec.ts +175 -0
  50. package/tests/parser.spec.ts +829 -823
  51. package/tests/prelude.spec.ts +17 -22
  52. package/tests/typechecker.spec.ts +327 -310
  53. package/tsconfig.json +26 -17
  54. package/tsconfig.tsbuildinfo +1 -0
  55. package/dist/parser/layoutPreprocessor.d.ts +0 -1
  56. package/dist/parser/layoutPreprocessor.js +0 -22
  57. package/dist/parser/layoutPreprocessor.js.map +0 -1
  58. package/dist/parser/preprocessor.d.ts +0 -28
  59. package/dist/parser/preprocessor.js +0 -453
  60. package/dist/parser/preprocessor.js.map +0 -1
  61. package/src/parser/layoutPreprocessor.ts +0 -21
  62. package/src/parser/preprocessor.ne +0 -375
  63. package/src/parser/preprocessor.ts +0 -502
  64. package/tests/preprocessor.spec.ts +0 -69
@@ -1,77 +1,353 @@
1
- import moo, { Lexer } from "moo";
2
- import { keywords } from "../utils/types.js";
3
- import { makeLexer } from "moo-ignore";
4
- import IndentationLexer from "moo-indentation-lexer";
5
-
6
- export const HaskellLexerConfig = {
7
- NL: { match: /\r?\n/, lineBreaks: true },
8
- anonymousVariable: "_",
9
- WS: / |\t/,
10
- comment: /--.*?$|{-[\s\S]*?-}/,
11
- number:
12
- /0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?/,
13
- char: /'(?:\\['\\bfnrtv0]|\\u[0-9a-fA-F]{4}|[^'\\\n\r])?'/,
14
- string: /"(?:\\["\\bfnrtv0]|\\u[0-9a-fA-F]{4}|[^"\\\n\r])*"/,
15
- //template: /`(?:\\[\s\S]|[^\\`])*`/,
16
- backtick: "`",
17
- lparen: "(",
18
- rparen: ")",
19
- lbracket: "{",
20
- rbracket: "}",
21
- lsquare: "[",
22
- rsquare: "]",
23
- rangeOperator: "..",
24
- semicolon: ";",
25
- typeArrow: "->",
26
- leftArrow: "<-",
27
- typeEquals: "::",
28
- question: "?",
29
- arrow: "=>",
30
- bool: {
31
- match: ["True", "False"],
32
- },
33
- op: /,|>>=|>>|\\\\|\\|\.|\+\+|\+|\-|\*\*|\*|===|!==|==|\/=|<=|>=|<|>|&&|\/|\|\||\.\.|\$|\^\^|\^|#|@|~|!!|!|%|\?|:|&|\||`/,
34
- assign: "=",
35
- constructor: {
36
- match: /[A-Z][a-zA-Z0-9']*/,
37
- type: moo.keywords({
38
- typeClass: [
39
- "Foldable",
40
- "Bounded",
41
- "Enum",
42
- "Eq",
43
- "Floating",
44
- "Fractional",
45
- "Functor",
46
- "Integral",
47
- "Ix",
48
- "Monad",
49
- "MonadPlus",
50
- "Num",
51
- "Ord",
52
- "Random",
53
- "RandomGen",
54
- "Read",
55
- "Real",
56
- "RealFloat",
57
- "RealFrac",
58
- "Show",
59
- ],
60
- }),
61
- },
62
- variable: {
63
- match: /[a-z_][a-zA-Z0-9_']*/,
64
- type: moo.keywords({
65
- otherwise: "otherwise",
66
- error: "error",
67
- keyword: keywords,
68
- }),
69
- },
70
- };
71
-
72
- export const HSLexer: Lexer = makeLexer(HaskellLexerConfig, []);
73
- export const PreprocessorLexer: Lexer = new IndentationLexer({
74
- lexer: moo.compile(HaskellLexerConfig),
75
- indentationType: "WS",
76
- newlineType: 'NL',
77
- });
1
+ import moo, { Lexer, LexerState, Token } from "moo";
2
+ import { keywords } from "../utils/types.js";
3
+ declare module "moo" {
4
+ interface Lexer {
5
+ index: number; // Add the missing property to the type definition
6
+ }
7
+ }
8
+
9
+ interface LayoutState {
10
+ stack: number[]; // indentation stack (follows moo naming)
11
+ tokenBuffer: Token[];
12
+ expectingBlock: boolean;
13
+ firstTokenProcessed: boolean;
14
+ }
15
+ type FullState = {
16
+ layoutState: LayoutState;
17
+ mooIndex?: number;
18
+ } & LexerState;
19
+
20
+ export const HaskellLexerConfig = {
21
+ comment: { match: /--.*?$|{-[\s\S]*?-}/, lineBreaks: true },
22
+
23
+ number:
24
+ /0[xX][0-9a-fA-F]+|0[bB][01]+|0[oO][0-7]+|(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?/,
25
+ char: /'(?:\\['\\bfnrtv0]|\\u[0-9a-fA-F]{4}|[^'\\\n\r])?'/,
26
+ string: { match: /"(?:\\[\s\S]|[^"\\])*"/, lineBreaks: true },
27
+
28
+ lparen: "(",
29
+ rparen: ")",
30
+ lbracket: "{",
31
+ rbracket: "}",
32
+ lsquare: "[",
33
+ rsquare: "]",
34
+ semicolon: ";",
35
+
36
+ typeArrow: "->",
37
+ leftArrow: "<-",
38
+ typeEquals: "::",
39
+ fatArrow: "=>",
40
+ op: {
41
+ match:
42
+ /,|>>=|>>|\\\\|\\|\.\.|\.|\+\+|\+|\-|\*\*|\*|===|!==|==|\/=|<=|>=|<|>|&&|\/|\|\||\$!|\$|\^\^|\^|#|@|~|!!|!|%|\?|:|&|`/,
43
+ lineBreaks: false,
44
+ },
45
+ assign: "=",
46
+ backslash: "\\",
47
+ pipe: "|",
48
+ at: "@",
49
+ tilde: "~",
50
+
51
+ anonymousVariable: "_",
52
+
53
+ constructor: {
54
+ match: /[A-Z][a-zA-Z0-9']*/,
55
+ type: moo.keywords({ bool: ["True", "False"] }),
56
+ },
57
+ variable: {
58
+ match: /[a-z_][a-zA-Z0-9_']*/,
59
+ type: moo.keywords({ otherwise: "otherwise", keyword: keywords }),
60
+ },
61
+
62
+ NL: { match: /\r?\n/, lineBreaks: true },
63
+ WS: { match: /[ \t]+/, lineBreaks: false },
64
+ };
65
+
66
+ export class HaskellLayoutLexer implements Lexer {
67
+ public index: number;
68
+ private mooLexer: Lexer;
69
+ private state: LayoutState;
70
+
71
+ private readonly layoutTriggers = new Set(["where", "let", "do", "of"]);
72
+ private readonly noSemicolonKeywords = new Set(["in", "then", "else", "of"]);
73
+ private readonly alwaysNewDeclarationKeywords = new Set([
74
+ "type",
75
+ "data",
76
+ "class",
77
+ "instance",
78
+ "describe",
79
+ "it",
80
+ "module",
81
+ "import",
82
+ ]);
83
+
84
+ constructor() {
85
+ this.mooLexer = moo.compile(HaskellLexerConfig);
86
+ this.state = {
87
+ stack: [1],
88
+ tokenBuffer: [],
89
+ expectingBlock: false,
90
+ firstTokenProcessed: false,
91
+ };
92
+ }
93
+
94
+ [Symbol.iterator](): Iterator<Token> {
95
+ return {
96
+ next: () => {
97
+ const value = this.next();
98
+ return value
99
+ ? { value, done: false }
100
+ : { value: undefined, done: true };
101
+ },
102
+ };
103
+ }
104
+
105
+ reset(chunk?: string, state?: FullState): this {
106
+ if (state && state.layoutState) {
107
+ const fullState = state;
108
+ this.state = {
109
+ stack: [...fullState.layoutState.stack],
110
+ tokenBuffer: [...fullState.layoutState.tokenBuffer],
111
+ expectingBlock: fullState.layoutState.expectingBlock,
112
+ firstTokenProcessed: fullState.layoutState.firstTokenProcessed,
113
+ };
114
+ this.mooLexer.reset(chunk, fullState);
115
+ if (typeof fullState.mooIndex === "number") {
116
+ this.mooLexer.index = fullState.mooIndex;
117
+ }
118
+ } else {
119
+ this.state = {
120
+ stack: [1],
121
+ tokenBuffer: [],
122
+ expectingBlock: false,
123
+ firstTokenProcessed: false,
124
+ };
125
+ this.mooLexer.reset(chunk);
126
+ }
127
+ return this;
128
+ }
129
+
130
+ save(): FullState {
131
+ return {
132
+ ...this.mooLexer.save(),
133
+ mooIndex: this.mooLexer.index,
134
+ layoutState: {
135
+ stack: [...this.state.stack],
136
+ tokenBuffer: [...this.state.tokenBuffer],
137
+ expectingBlock: this.state.expectingBlock,
138
+ firstTokenProcessed: this.state.firstTokenProcessed,
139
+ },
140
+ };
141
+ }
142
+
143
+ pushState(state: string) {
144
+ this.mooLexer.pushState(state);
145
+ }
146
+ popState() {
147
+ return this.mooLexer.popState();
148
+ }
149
+ setState(state: string) {
150
+ this.mooLexer.setState(state);
151
+ }
152
+ formatError(token: Token, message?: string) {
153
+ return this.mooLexer.formatError(token, message);
154
+ }
155
+ has(tokenType: string) {
156
+ return true; // deprecated in moo but Lexer interface requires it.
157
+ }
158
+
159
+ next(): Token | undefined {
160
+ let token: Token | undefined;
161
+
162
+ if (this.state.tokenBuffer.length > 0) {
163
+ token = this.state.tokenBuffer.shift();
164
+ } else {
165
+ token = this.fetchAndProcessNextToken();
166
+ }
167
+
168
+ if (token) {
169
+ this.checkLayoutTrigger(token);
170
+ if (!this.state.firstTokenProcessed && token.col > 1) {
171
+ this.state.stack[0] = token.col;
172
+ }
173
+ this.state.firstTokenProcessed = true;
174
+ }
175
+
176
+ return token;
177
+ }
178
+
179
+ private fetchAndProcessNextToken(): Token | undefined {
180
+ let { token: rawToken, crossedNewline } =
181
+ this.advanceSkippingWhitespace();
182
+
183
+ if (!rawToken) return this.emitEOF();
184
+
185
+
186
+ if (this.state.expectingBlock) return this.handleBlockStart(rawToken);
187
+
188
+ if (this.isInToken(rawToken)) return this.handleInKeyword(rawToken);
189
+
190
+ if (crossedNewline) return this.handleIndentationChange(rawToken);
191
+
192
+ return rawToken;
193
+ }
194
+
195
+ private advanceSkippingWhitespace(): {
196
+ token: Token | undefined;
197
+ crossedNewline: boolean;
198
+ } {
199
+ let token = this.mooLexer.next();
200
+ let crossedNewline = false;
201
+
202
+ while (token && this.isSkippableToken(token)) {
203
+ if (this.isNewlineToken(token)) crossedNewline = true;
204
+ token = this.mooLexer.next();
205
+ }
206
+ return { token, crossedNewline };
207
+ }
208
+
209
+ private closeBlocksUntil(targetIndent: number, sourceToken: Token): Token {
210
+ const ops: Token[] = [];
211
+
212
+ while (
213
+ this.state.stack.length > 1 &&
214
+ this.state.stack[this.state.stack.length - 1] > targetIndent
215
+ ) {
216
+ this.state.stack.pop();
217
+ ops.push(this.createVirtualToken("rbracket", "}", sourceToken));
218
+ }
219
+
220
+ const isAlwaysNewDeclaration =
221
+ this.state.stack.length === 1 &&
222
+ sourceToken.type === "keyword" &&
223
+ this.alwaysNewDeclarationKeywords.has(sourceToken.value);
224
+
225
+ const shouldInjectSemicolon =
226
+ !sourceToken.value || !this.noSemicolonKeywords.has(sourceToken.value);
227
+
228
+ if (
229
+ this.state.stack.length > 1 &&
230
+ this.state.stack[this.state.stack.length - 1] === targetIndent &&
231
+ this.isInToken(sourceToken)
232
+ ) {
233
+ this.state.stack.pop();
234
+ ops.push(this.createVirtualToken("rbracket", "}", sourceToken));
235
+ } else if (
236
+ (this.state.stack.length > 0 &&
237
+ this.state.stack[this.state.stack.length - 1] === targetIndent &&
238
+ shouldInjectSemicolon) ||
239
+ isAlwaysNewDeclaration
240
+ ) {
241
+ ops.push(this.createVirtualToken("semicolon", ";", sourceToken));
242
+ }
243
+
244
+ this.state.tokenBuffer.unshift(...ops);
245
+
246
+ return this.state.tokenBuffer.shift();
247
+ }
248
+
249
+ private emitEOF(): Token | undefined {
250
+ if (this.state.stack.length > 1) {
251
+ const dummy = {
252
+ line: 0,
253
+ col: 0,
254
+ text: "",
255
+ value: "",
256
+ offset: 0,
257
+ lineBreaks: 0,
258
+ };
259
+ const ops: Token[] = [];
260
+ while (this.state.stack.length > 1) {
261
+ this.state.stack.pop();
262
+ ops.push(this.createVirtualToken("rbracket", "}", dummy));
263
+ }
264
+ this.state.tokenBuffer.push(...ops);
265
+ return this.state.tokenBuffer.shift();
266
+ }
267
+ return undefined;
268
+ }
269
+
270
+ private enqueue(token: Token) {
271
+ this.state.tokenBuffer.push(token);
272
+ }
273
+
274
+ private handleBlockStart(token: Token) {
275
+ this.state.expectingBlock = false;
276
+ if (token.type === "lbracket") return token;
277
+
278
+ this.state.stack.push(token.col);
279
+ this.enqueue(token);
280
+ return this.createVirtualToken("lbracket", "{", token);
281
+ }
282
+ private handleInKeyword(token: Token) {
283
+ const stackTop = this.state.stack[this.state.stack.length - 1];
284
+ if (token.col < stackTop) {
285
+ this.enqueue(token);
286
+ return this.closeBlocksUntil(token.col, token);
287
+ }
288
+ if (this.state.stack.length > 1) {
289
+ this.state.stack.pop();
290
+ this.enqueue(token);
291
+ return this.createVirtualToken("rbracket", "}", token);
292
+ }
293
+ return token;
294
+ }
295
+ private handleIndentationChange(token: Token) {
296
+ const currentIndent = token.col;
297
+ const stackTop = this.state.stack[this.state.stack.length - 1];
298
+
299
+ const isAlwaysNewDeclaration =
300
+ this.state.stack.length === 1 &&
301
+ token.type === "keyword" &&
302
+ this.alwaysNewDeclarationKeywords.has(token.value);
303
+
304
+ if (currentIndent === stackTop || isAlwaysNewDeclaration) {
305
+ const isContinuationKeyword =
306
+ token.value && this.noSemicolonKeywords.has(token.value);
307
+ if (this.state.firstTokenProcessed && !isContinuationKeyword) {
308
+ this.enqueue(token);
309
+ return this.createVirtualToken("semicolon", ";", token);
310
+ }
311
+ return token;
312
+ } else if (currentIndent < stackTop) {
313
+ this.enqueue(token);
314
+ return this.closeBlocksUntil(currentIndent, token);
315
+ }
316
+ return token;
317
+ }
318
+
319
+ private checkLayoutTrigger(token: Token) {
320
+ if (token && token.value && this.layoutTriggers.has(token.value))
321
+ this.state.expectingBlock = true;
322
+ }
323
+ private isSkippableToken(token: Token) {
324
+ return (
325
+ token.type === "WS" ||
326
+ this.isNewlineToken(token) ||
327
+ token.type === "comment"
328
+ );
329
+ }
330
+ private isNewlineToken(token: Token) {
331
+ return token.type === "NL";
332
+ }
333
+ private isInToken(token: Token) {
334
+ return token.type === "keyword" && token.value === "in";
335
+ }
336
+
337
+ private createVirtualToken(
338
+ type: string,
339
+ value: string,
340
+ source: Token
341
+ ): Token {
342
+ return {
343
+ type,
344
+ value,
345
+ text: value,
346
+ line: source.line,
347
+ col: source.col,
348
+ offset: source.offset,
349
+ lineBreaks: 0,
350
+ toString: () => value,
351
+ };
352
+ }
353
+ }