Skip to content

Commit 71cf457

Browse files
committed
Add expression parser
1 parent eb57a91 commit 71cf457

4 files changed

Lines changed: 197 additions & 5 deletions

File tree

src/Expr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,6 @@ export class AstPrinter implements Visitor<string> {
8181
}
8282

8383
private parenthesize(name: string, ...exprs: Expr[]): string {
84-
return `(${name} ${exprs.map((expr) => expr.accept(this)).join('')})`
84+
return `(${name} ${exprs.map((expr) => expr.accept(this)).join(' ')})`
8585
}
8686
}

src/Lox.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
1+
import Token from './Token'
2+
import TokenType from './TokenType'
3+
14
export default class Lox {
25
public static hadError = false
36

4-
public static error(line: number, message: string): void {
5-
Lox.report(line, '', message)
7+
public static error(obj: number|Token, message: string): void {
8+
Lox.hadError = true
9+
if (typeof obj === 'number') {
10+
const line = obj as number
11+
Lox.report(line, '', message)
12+
} else {
13+
const token = obj as Token
14+
if (token.type === TokenType.EOF) {
15+
Lox.report(token.line, ' at end', message)
16+
} else {
17+
Lox.report(token.line, ` at '${token.lexeme}'`, message)
18+
}
19+
}
620
}
721

822
public static report(line: number, where: string, message: string): void {
9-
console.error(`[line ${line}] Error ${where}: ${message}`)
23+
console.error(`[line ${line}] Error${where}: ${message}`)
1024
}
1125
}

src/Parser.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Binary, Expr, Grouping, Literal, Unary } from './Expr'
2+
import Lox from './Lox'
3+
import Token from './Token'
4+
import TokenType from './TokenType'
5+
6+
export default class Parser {
7+
8+
private static error(token: Token, message: string): Error {
9+
Lox.error(token, message)
10+
return new Error()
11+
}
12+
13+
private current = 0
14+
15+
constructor(
16+
readonly tokens: Token[],
17+
) {}
18+
19+
public parse(): Expr {
20+
try {
21+
return this.expression()
22+
} catch (error) {
23+
return null
24+
}
25+
}
26+
27+
private match(...types: TokenType[]): boolean {
28+
const found = types.some((type) => this.check(type))
29+
if (found) this.advance()
30+
return found
31+
}
32+
33+
private advance(): Token {
34+
if (!this.isAtEnd()) this.current++
35+
return this.previous()
36+
}
37+
38+
private check(type: TokenType): boolean {
39+
if (this.isAtEnd()) return false
40+
return this.peek().type === type
41+
}
42+
43+
private previous(): Token {
44+
return this.tokens[this.current - 1]
45+
}
46+
47+
private isAtEnd(): boolean {
48+
return this.peek().type === TokenType.EOF
49+
}
50+
51+
private peek(): Token {
52+
return this.tokens[this.current]
53+
}
54+
55+
private expression(): Expr {
56+
return this.equality()
57+
}
58+
59+
private equality(): Expr {
60+
let expr = this.comparison()
61+
62+
while (this.match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL)) {
63+
const operator = this.previous()
64+
const right = this.comparison()
65+
expr = new Binary(expr, operator, right)
66+
}
67+
68+
return expr
69+
}
70+
71+
private comparison(): Expr {
72+
let expr = this.addition()
73+
74+
while (this.match(
75+
TokenType.GREATER,
76+
TokenType.GREATER_EQUAL,
77+
TokenType.LESS,
78+
TokenType.LESS_EQUAL,
79+
)) {
80+
const operator = this.previous()
81+
const right = this.addition()
82+
expr = new Binary(expr, operator, right)
83+
}
84+
85+
return expr
86+
}
87+
88+
private addition(): Expr {
89+
let expr = this.multiplication()
90+
91+
while (this.match(
92+
TokenType.MINUS,
93+
TokenType.PLUS,
94+
)) {
95+
const operator = this.previous()
96+
const right = this.multiplication()
97+
expr = new Binary(expr, operator, right)
98+
}
99+
100+
return expr
101+
}
102+
103+
private multiplication(): Expr {
104+
let expr = this.unary()
105+
106+
while (this.match(
107+
TokenType.SLASH,
108+
TokenType.STAR,
109+
)) {
110+
const operator = this.previous()
111+
const right = this.unary()
112+
expr = new Binary(expr, operator, right)
113+
}
114+
115+
return expr
116+
}
117+
118+
private unary(): Expr {
119+
if (this.match(TokenType.BANG, TokenType.MINUS)) {
120+
const operator = this.previous()
121+
const right = this.unary()
122+
return new Unary(operator, right)
123+
}
124+
125+
return this.primary()
126+
}
127+
128+
private primary(): Expr {
129+
if (this.match(TokenType.FALSE)) return new Literal(false)
130+
if (this.match(TokenType.TRUE)) return new Literal(true)
131+
if (this.match(TokenType.NIL)) return new Literal(null)
132+
133+
if (this.match(TokenType.NUMBER, TokenType.STRING)) {
134+
return new Literal(this.previous().literal)
135+
}
136+
137+
if (this.match(TokenType.LEFT_PAREN)) {
138+
const expr = this.expression()
139+
this.consume(TokenType.RIGHT_PAREN, 'Expect \')\' after expression.')
140+
return new Grouping(expr)
141+
}
142+
143+
throw Parser.error(this.peek(), 'Expect expression.')
144+
}
145+
146+
private consume(type: TokenType, message: string): Token {
147+
if (this.check(type)) return this.advance()
148+
throw Parser.error(this.peek(), message)
149+
}
150+
151+
private synchronize() {
152+
this.advance()
153+
154+
while (!this.isAtEnd()) {
155+
if (this.previous().type === TokenType.SEMICOLON) return
156+
157+
switch (this.peek().type) {
158+
case TokenType.CLASS:
159+
case TokenType.FUN:
160+
case TokenType.VAR:
161+
case TokenType.FOR:
162+
case TokenType.IF:
163+
case TokenType.WHILE:
164+
case TokenType.PRINT:
165+
case TokenType.RETURN:
166+
return
167+
}
168+
169+
this.advance()
170+
}
171+
}
172+
}

src/language.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as fs from 'fs'
22
import * as readlineSync from 'readline-sync'
33

4+
import { AstPrinter } from './Expr'
45
import Lox from './Lox'
6+
import Parser from './Parser'
57
import Scanner from './Scanner'
68
import Token from './Token'
79

@@ -33,5 +35,9 @@ function runPrompt(): void {
3335

3436
function run(input: string): void {
3537
const tokens: Token[] = new Scanner(input).tokens
36-
tokens.forEach((token) => console.log(token.toString()))
38+
const expression = new Parser(tokens).parse()
39+
40+
if (Lox.hadError) return
41+
42+
console.log(new AstPrinter().print(expression))
3743
}

0 commit comments

Comments
 (0)