Skip to content

Commit 7f8c490

Browse files
Add support for multi-line parentheses
1 parent 1d80a81 commit 7f8c490

File tree

4 files changed

+39
-1
lines changed

4 files changed

+39
-1
lines changed

src/errors.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ export namespace TokenizerErrors {
114114
this.name = "ForbiddenOperatorError";
115115
}
116116
}
117+
118+
export class NonMatchingParenthesesError extends BaseTokenizerError {
119+
constructor(line: number, col: number, source: string, current: number) {
120+
let msg = getFullLine(source, current-1) + "\n";
121+
let hint = `${col > 1 ? '~' : ''}^~ Non-matching closing parentheses.`;
122+
// The extra `~` character takes up some space.
123+
hint = hint.padStart(hint.length + col - MAGIC_OFFSET - (col > 1 ? 1 : 0), " ");
124+
super(msg + hint, line, col);
125+
this.name = "NonMatchingParenthesesError";
126+
}
127+
}
117128
}
118129

119130
export namespace ParserErrors {

src/tests/regression.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ def foo():
1515
print("hi")
1616
1717
print("world")
18+
`;
19+
toEstreeAST(text);
20+
})
21+
test('Issue #3', () => {
22+
const text = `
23+
def foo(
24+
a,
25+
b
26+
):
27+
pass
28+
29+
pass
1830
`;
1931
toEstreeAST(text);
2032
})

src/tokenizer.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const specialIdentifiers = new Map([
8888

8989
export const SPECIAL_IDENTIFIER_TOKENS = Array.from(specialIdentifiers.values());
9090

91+
9192
export class Tokenizer {
9293
private readonly source: string;
9394
private readonly tokens: Token[];
@@ -98,6 +99,7 @@ export class Tokenizer {
9899
private readonly indentStack: number[];
99100
private specialIdentifiers: Map<string, TokenType>;
100101
private forbiddenIdentifiers: Map<string, TokenType>;
102+
private parenthesesLevel: number;
101103

102104
// forbiddenOperators: Set<TokenType>;
103105
constructor(source: string) {
@@ -139,6 +141,7 @@ export class Tokenizer {
139141
// TokenType.DOUBLESTAREQUAL,
140142
// TokenType.DOUBLESLASHEQUAL,
141143
// ])
144+
this.parenthesesLevel = 0;
142145
}
143146

144147
private isAtEnd() {
@@ -273,6 +276,11 @@ export class Tokenizer {
273276
break;
274277
}
275278
case '\n':
279+
if (this.parenthesesLevel > 0) {
280+
this.line += 1;
281+
this.col = 0;
282+
break;
283+
}
276284
this.addToken(TokenType.NEWLINE);
277285
this.line += 1;
278286
this.col = 0;
@@ -362,9 +370,14 @@ export class Tokenizer {
362370
//// Everything else
363371
case '(':
364372
this.addToken(TokenType.LPAR);
373+
this.parenthesesLevel++;
365374
break;
366375
case ')':
367376
this.addToken(TokenType.RPAR);
377+
if (this.parenthesesLevel === 0) {
378+
throw new TokenizerErrors.NonMatchingParenthesesError(this.line, this.col, this.source, this.current);
379+
}
380+
this.parenthesesLevel--;
368381
break;
369382
case ',':
370383
this.addToken(TokenType.COMMA);

test.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
def foo():
1+
def foo(
2+
a, b
3+
):
24
pass
35

46
pass

0 commit comments

Comments
 (0)