yukigo-haskell-parser 0.1.0 → 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/.mocharc.json +3 -3
- package/CHANGELOG.md +6 -0
- package/README.md +10 -10
- package/dist/index.d.ts +13 -2
- package/dist/index.js +112 -32
- package/dist/index.js.map +1 -1
- package/dist/parser/grammar.js +220 -231
- package/dist/parser/grammar.js.map +1 -1
- package/dist/parser/lexer.d.ts +70 -15
- package/dist/parser/lexer.js +259 -50
- package/dist/parser/lexer.js.map +1 -1
- package/dist/prelude.d.ts +1 -1
- package/dist/prelude.js +280 -204
- package/dist/prelude.js.map +1 -1
- package/dist/typechecker/DeclarationCollector.d.ts +6 -1
- package/dist/typechecker/DeclarationCollector.js +39 -0
- package/dist/typechecker/DeclarationCollector.js.map +1 -1
- package/dist/typechecker/TypeBuilder.d.ts +1 -1
- package/dist/typechecker/TypeBuilder.js +6 -2
- package/dist/typechecker/TypeBuilder.js.map +1 -1
- package/dist/typechecker/checker.d.ts +15 -4
- package/dist/typechecker/checker.js +108 -25
- package/dist/typechecker/checker.js.map +1 -1
- package/dist/typechecker/core.d.ts +4 -0
- package/dist/typechecker/core.js +45 -19
- package/dist/typechecker/core.js.map +1 -1
- package/dist/typechecker/inference.d.ts +5 -1
- package/dist/typechecker/inference.js +95 -21
- package/dist/typechecker/inference.js.map +1 -1
- package/dist/utils/helpers.d.ts +1 -1
- package/dist/utils/helpers.js +1 -1
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/types.d.ts +1 -1
- package/dist/utils/types.js +7 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +192 -55
- package/src/parser/grammar.ne +519 -463
- package/src/parser/grammar.ts +230 -239
- package/src/parser/lexer.ts +353 -77
- package/src/prelude.ts +282 -206
- package/src/typechecker/DeclarationCollector.ts +157 -104
- package/src/typechecker/TypeBuilder.ts +150 -148
- package/src/typechecker/checker.ts +501 -395
- package/src/typechecker/core.ts +192 -162
- package/src/typechecker/inference.ts +1421 -1327
- package/src/utils/helpers.ts +29 -29
- package/src/utils/types.ts +63 -56
- package/tests/hspec.spec.ts +92 -0
- package/tests/lexer.spec.ts +175 -0
- package/tests/parser.spec.ts +829 -823
- package/tests/prelude.spec.ts +17 -22
- package/tests/typechecker.spec.ts +327 -310
- package/tsconfig.json +26 -17
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/parser/layoutPreprocessor.d.ts +0 -1
- package/dist/parser/layoutPreprocessor.js +0 -22
- package/dist/parser/layoutPreprocessor.js.map +0 -1
- package/dist/parser/preprocessor.d.ts +0 -28
- package/dist/parser/preprocessor.js +0 -453
- package/dist/parser/preprocessor.js.map +0 -1
- package/src/parser/layoutPreprocessor.ts +0 -21
- package/src/parser/preprocessor.ne +0 -375
- package/src/parser/preprocessor.ts +0 -502
- package/tests/preprocessor.spec.ts +0 -69
package/src/utils/helpers.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { inspect } from "util";
|
|
2
|
-
import { AST } from "yukigo-ast";
|
|
3
|
-
import { Function } from "yukigo-ast";
|
|
4
|
-
|
|
5
|
-
export function groupFunctionDeclarations(ast: AST): AST {
|
|
6
|
-
const groups: Record<string, Function[]> = {};
|
|
7
|
-
const others: AST = [];
|
|
8
|
-
|
|
9
|
-
for (const node of ast) {
|
|
10
|
-
if (node instanceof Function) {
|
|
11
|
-
const name = node.identifier.value;
|
|
12
|
-
if (!groups[name]) {
|
|
13
|
-
groups[name] = [];
|
|
14
|
-
}
|
|
15
|
-
groups[name].push(node);
|
|
16
|
-
} else {
|
|
17
|
-
others.push(node);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Merge equations for each function name
|
|
22
|
-
const functionGroups: Function[] = Object.values(groups).map((functions) => {
|
|
23
|
-
const identifier = functions[0].identifier;
|
|
24
|
-
const allEquations = functions.flatMap((f) => f.equations);
|
|
25
|
-
return new Function(identifier, allEquations, identifier.loc);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return [...others, ...functionGroups];
|
|
29
|
-
}
|
|
1
|
+
import { inspect } from "util";
|
|
2
|
+
import { AST } from "yukigo-ast";
|
|
3
|
+
import { Function } from "yukigo-ast";
|
|
4
|
+
|
|
5
|
+
export function groupFunctionDeclarations(ast: AST): AST {
|
|
6
|
+
const groups: Record<string, Function[]> = {};
|
|
7
|
+
const others: AST = [];
|
|
8
|
+
|
|
9
|
+
for (const node of ast) {
|
|
10
|
+
if (node instanceof Function) {
|
|
11
|
+
const name = node.identifier.value;
|
|
12
|
+
if (!groups[name]) {
|
|
13
|
+
groups[name] = [];
|
|
14
|
+
}
|
|
15
|
+
groups[name].push(node);
|
|
16
|
+
} else {
|
|
17
|
+
others.push(node);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Merge equations for each function name
|
|
22
|
+
const functionGroups: Function[] = Object.values(groups).map((functions) => {
|
|
23
|
+
const identifier = functions[0].identifier;
|
|
24
|
+
const allEquations = functions.flatMap((f) => f.equations);
|
|
25
|
+
return new Function(identifier, allEquations, identifier.loc);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return [...others, ...functionGroups];
|
|
29
|
+
}
|
package/src/utils/types.ts
CHANGED
|
@@ -1,56 +1,63 @@
|
|
|
1
|
-
import { YukigoPrimitive } from "yukigo-ast";
|
|
2
|
-
|
|
3
|
-
export const keywords = [
|
|
4
|
-
"type",
|
|
5
|
-
"where",
|
|
6
|
-
"in",
|
|
7
|
-
"if",
|
|
8
|
-
"else",
|
|
9
|
-
"then",
|
|
10
|
-
"data",
|
|
11
|
-
"case",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
["
|
|
50
|
-
["
|
|
51
|
-
["
|
|
52
|
-
["
|
|
53
|
-
["
|
|
54
|
-
["
|
|
55
|
-
["
|
|
56
|
-
]
|
|
1
|
+
import { YukigoPrimitive } from "yukigo-ast";
|
|
2
|
+
|
|
3
|
+
export const keywords = [
|
|
4
|
+
"type",
|
|
5
|
+
"where",
|
|
6
|
+
"in",
|
|
7
|
+
"if",
|
|
8
|
+
"else",
|
|
9
|
+
"then",
|
|
10
|
+
"data",
|
|
11
|
+
"case",
|
|
12
|
+
"error",
|
|
13
|
+
"class",
|
|
14
|
+
"do",
|
|
15
|
+
"default",
|
|
16
|
+
"deriving",
|
|
17
|
+
"import",
|
|
18
|
+
"infix",
|
|
19
|
+
"infixl",
|
|
20
|
+
"infixr",
|
|
21
|
+
"instance",
|
|
22
|
+
"let",
|
|
23
|
+
"module",
|
|
24
|
+
"newtype",
|
|
25
|
+
"of",
|
|
26
|
+
"qualified",
|
|
27
|
+
"hiding",
|
|
28
|
+
"foreign",
|
|
29
|
+
"describe",
|
|
30
|
+
"it",
|
|
31
|
+
"shouldBe",
|
|
32
|
+
"shouldNotBe",
|
|
33
|
+
"shouldSatisfy",
|
|
34
|
+
"shouldThrow"
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export const typeMappings: { [key: string]: YukigoPrimitive } = {
|
|
38
|
+
Float: "YuNumber",
|
|
39
|
+
Double: "YuNumber",
|
|
40
|
+
Int: "YuNumber",
|
|
41
|
+
Integer: "YuNumber",
|
|
42
|
+
String: "YuString",
|
|
43
|
+
Char: "YuChar",
|
|
44
|
+
Boolean: "YuBoolean",
|
|
45
|
+
Bool: "YuBoolean",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const typeClasses: Map<string, string[]> = new Map([
|
|
49
|
+
["Bounded", ["YuChar", "YuNumber"]],
|
|
50
|
+
["Enum", ["YuChar", "YuNumber"]],
|
|
51
|
+
["Ord", ["YuNumber", "YuString", "YuChar"]],
|
|
52
|
+
["Eq", ["YuNumber", "YuString", "YuChar", "YuBoolean"]],
|
|
53
|
+
["Floating", ["YuNumber"]],
|
|
54
|
+
["Fractional", ["YuNumber"]],
|
|
55
|
+
["Integral", ["YuNumber"]],
|
|
56
|
+
["Num", ["YuNumber"]],
|
|
57
|
+
["Random", ["YuBoolean", "YuChar", "YuNumber"]],
|
|
58
|
+
["Read", ["YuList", "YuChar", "YuNumber"]],
|
|
59
|
+
["Real", ["YuNumber"]],
|
|
60
|
+
["RealFloat", ["YuNumber"]],
|
|
61
|
+
["RealFrac", ["YuNumber"]],
|
|
62
|
+
["Show", ["YuNumber", "YuString", "YuChar", "YuList"]],
|
|
63
|
+
]);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { YukigoHaskellParser } from "../src/index.js";
|
|
3
|
+
import {
|
|
4
|
+
TestGroup,
|
|
5
|
+
Test,
|
|
6
|
+
Assert,
|
|
7
|
+
Equality,
|
|
8
|
+
Truth,
|
|
9
|
+
Failure,
|
|
10
|
+
Raise,
|
|
11
|
+
NumberPrimitive,
|
|
12
|
+
SymbolPrimitive,
|
|
13
|
+
Application,
|
|
14
|
+
StringPrimitive,
|
|
15
|
+
BooleanPrimitive
|
|
16
|
+
} from "yukigo-ast";
|
|
17
|
+
|
|
18
|
+
describe("HSpec Parsing", () => {
|
|
19
|
+
const parser = new YukigoHaskellParser("", { typecheck: true, includePrims: false });
|
|
20
|
+
|
|
21
|
+
it("should parse describe and it blocks", () => {
|
|
22
|
+
const code = `
|
|
23
|
+
describe "Math" do
|
|
24
|
+
it "adds 1 and 1" do
|
|
25
|
+
(1 + 1) shouldBe 2
|
|
26
|
+
`;
|
|
27
|
+
const ast = parser.parse(code);
|
|
28
|
+
expect(ast).to.have.lengthOf(1);
|
|
29
|
+
expect(ast[0]).to.be.instanceOf(TestGroup);
|
|
30
|
+
|
|
31
|
+
const group = ast[0] as TestGroup;
|
|
32
|
+
expect(group.name).to.be.instanceOf(StringPrimitive);
|
|
33
|
+
expect((group.name as StringPrimitive).value).to.equal("Math");
|
|
34
|
+
|
|
35
|
+
expect(group.group.statements).to.have.lengthOf(1);
|
|
36
|
+
expect(group.group.statements[0]).to.be.instanceOf(Test);
|
|
37
|
+
|
|
38
|
+
const test = group.group.statements[0] as Test;
|
|
39
|
+
expect(test.name).to.be.instanceOf(StringPrimitive);
|
|
40
|
+
expect((test.name as StringPrimitive).value).to.equal("adds 1 and 1");
|
|
41
|
+
|
|
42
|
+
expect(test.body.statements).to.have.lengthOf(1);
|
|
43
|
+
expect(test.body.statements[0]).to.be.instanceOf(Assert);
|
|
44
|
+
|
|
45
|
+
const assert = test.body.statements[0] as Assert;
|
|
46
|
+
expect(assert.negated).to.be.instanceOf(BooleanPrimitive);
|
|
47
|
+
expect((assert.negated as BooleanPrimitive).value).to.be.false;
|
|
48
|
+
expect(assert.body).to.be.instanceOf(Equality);
|
|
49
|
+
|
|
50
|
+
const equality = assert.body as Equality;
|
|
51
|
+
expect(equality.expected).to.be.instanceOf(NumberPrimitive);
|
|
52
|
+
expect((equality.expected as NumberPrimitive).value).to.equal(2);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should parse shouldNotBe", () => {
|
|
56
|
+
const code = `it "negation" do 1 shouldNotBe 2`;
|
|
57
|
+
const ast = parser.parse(code);
|
|
58
|
+
const test = ast[0] as Test;
|
|
59
|
+
const assert = test.body.statements[0] as Assert;
|
|
60
|
+
expect((assert.negated as BooleanPrimitive).value).to.be.true;
|
|
61
|
+
expect(assert.body).to.be.instanceOf(Equality);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should parse shouldSatisfy", () => {
|
|
65
|
+
const code = `it "satisfy" do 5 shouldSatisfy odd`;
|
|
66
|
+
const ast = parser.parse(code);
|
|
67
|
+
const test = ast[0] as Test;
|
|
68
|
+
const assert = test.body.statements[0] as Assert;
|
|
69
|
+
expect(assert.body).to.be.instanceOf(Truth);
|
|
70
|
+
|
|
71
|
+
const truth = assert.body as Truth;
|
|
72
|
+
expect(truth.body).to.be.instanceOf(Application);
|
|
73
|
+
const app = truth.body as Application;
|
|
74
|
+
expect(app.functionExpr).to.be.instanceOf(SymbolPrimitive);
|
|
75
|
+
expect((app.functionExpr as SymbolPrimitive).value).to.equal("odd");
|
|
76
|
+
expect(app.parameter).to.be.instanceOf(NumberPrimitive);
|
|
77
|
+
expect((app.parameter as NumberPrimitive).value).to.equal(5);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should parse shouldThrow", () => {
|
|
81
|
+
const code = `it "throws" do error "fail" shouldThrow anyException`;
|
|
82
|
+
const ast = parser.parse(code);
|
|
83
|
+
const test = ast[0] as Test;
|
|
84
|
+
const assert = test.body.statements[0] as Assert;
|
|
85
|
+
expect(assert.body).to.be.instanceOf(Failure);
|
|
86
|
+
|
|
87
|
+
const failure = assert.body as Failure;
|
|
88
|
+
expect(failure.func).to.be.instanceOf(Raise);
|
|
89
|
+
expect(failure.message).to.be.instanceOf(SymbolPrimitive);
|
|
90
|
+
expect((failure.message as SymbolPrimitive).value).to.equal("anyException");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +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
|
+
});
|