Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement break and continue #15

Merged
merged 13 commits into from Mar 17, 2024
12 changes: 6 additions & 6 deletions slox.xcodeproj/project.pbxproj
Expand Up @@ -51,12 +51,12 @@
876A31652B8C04990085A350 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876A31642B8C04990085A350 /* Expression.swift */; };
876A31672B8C11810085A350 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876A31662B8C11810085A350 /* Parser.swift */; };
876A31692B8C3AAB0085A350 /* ParseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876A31682B8C3AAB0085A350 /* ParseError.swift */; };
877168C22B91A9BD00723543 /* Return.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* Return.swift */; };
877168C22B91A9BD00723543 /* JumpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* JumpType.swift */; };
87777E3C2BA0FF70002E38F2 /* LoxList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87777E3B2BA0FF70002E38F2 /* LoxList.swift */; };
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87777E3B2BA0FF70002E38F2 /* LoxList.swift */; };
87BAFC492B9179CB0013E5FE /* LoxCallable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC482B9179CB0013E5FE /* LoxCallable.swift */; };
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */; };
87C2F3742B91C2BA00126707 /* Return.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* Return.swift */; };
87C2F3742B91C2BA00126707 /* JumpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* JumpType.swift */; };
87EEDFBE2B96F52D00C7FE6D /* LoxInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */; };
87EEDFBF2B9920F900C7FE6D /* LoxClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8702307F2B96E9580056FE57 /* LoxClass.swift */; };
87EEDFC02B9920FD00C7FE6D /* LoxInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */; };
Expand Down Expand Up @@ -102,7 +102,7 @@
876A31642B8C04990085A350 /* Expression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = "<group>"; };
876A31662B8C11810085A350 /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
876A31682B8C3AAB0085A350 /* ParseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseError.swift; sourceTree = "<group>"; };
877168C12B91A9BD00723543 /* Return.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Return.swift; sourceTree = "<group>"; };
877168C12B91A9BD00723543 /* JumpType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpType.swift; sourceTree = "<group>"; };
87777E3B2BA0FF70002E38F2 /* LoxList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxList.swift; sourceTree = "<group>"; };
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxCallable.swift; sourceTree = "<group>"; };
87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefinedFunction.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -152,6 +152,7 @@
873CCB322B8ED8B900FC249A /* Environment.swift */,
876A31642B8C04990085A350 /* Expression.swift */,
873CCB202B8D5FAE00FC249A /* Interpreter.swift */,
877168C12B91A9BD00723543 /* JumpType.swift */,
873CCB242B8D765D00FC249A /* Lox.swift */,
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */,
8702307F2B96E9580056FE57 /* LoxClass.swift */,
Expand All @@ -166,7 +167,6 @@
873283932B95122800E49035 /* ResolvedStatement.swift */,
873283952B95127100E49035 /* Resolver.swift */,
873283972B9523AD00E49035 /* ResolverError.swift */,
877168C12B91A9BD00723543 /* Return.swift */,
873CCB222B8D617C00FC249A /* RuntimeError.swift */,
876A31612B8986630085A350 /* ScanError.swift */,
876560062B8827F9002BDE42 /* Scanner.swift */,
Expand Down Expand Up @@ -288,7 +288,7 @@
873CCB232B8D617C00FC249A /* RuntimeError.swift in Sources */,
87BAFC492B9179CB0013E5FE /* LoxCallable.swift in Sources */,
873CCB212B8D5FAE00FC249A /* Interpreter.swift in Sources */,
877168C22B91A9BD00723543 /* Return.swift in Sources */,
877168C22B91A9BD00723543 /* JumpType.swift in Sources */,
870230802B96E9580056FE57 /* LoxClass.swift in Sources */,
876A31672B8C11810085A350 /* Parser.swift in Sources */,
876560072B8827F9002BDE42 /* Scanner.swift in Sources */,
Expand Down Expand Up @@ -328,7 +328,7 @@
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */,
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */,
8702307B2B95755E0056FE57 /* ResolvedExpression.swift in Sources */,
87C2F3742B91C2BA00126707 /* Return.swift in Sources */,
87C2F3742B91C2BA00126707 /* JumpType.swift in Sources */,
873CCB282B8E7B6300FC249A /* Parser.swift in Sources */,
873CCB272B8E7AD100FC249A /* ParserTests.swift in Sources */,
876A315D2B897C020085A350 /* Scanner.swift in Sources */,
Expand Down
53 changes: 51 additions & 2 deletions slox/Interpreter.swift
Expand Up @@ -83,6 +83,11 @@ class Interpreter {
environment: Environment(enclosingEnvironment: environment))
case .while(let expr, let stmt):
try handleWhileStatement(expr: expr, stmt: stmt)
case .for(let initializerStmt, let testExpr, let incrementExpr, let bodyStmt):
try handleForStatement(initializerStmt: initializerStmt,
testExpr: testExpr,
incrementExpr: incrementExpr,
bodyStmt: bodyStmt)
case .class(let nameToken, let superclassExpr, let methods, let staticMethods):
try handleClassDeclaration(nameToken: nameToken,
superclassExpr: superclassExpr,
Expand All @@ -92,6 +97,10 @@ class Interpreter {
try handleFunctionDeclaration(name: name, lambda: lambda)
case .return(let returnToken, let expr):
try handleReturnStatement(returnToken: returnToken, expr: expr)
case .break(let breakToken):
try handleBreakStatement(breakToken: breakToken)
case .continue(let continueToken):
try handleContinueStatement(continueToken: continueToken)
}
}

Expand Down Expand Up @@ -207,7 +216,15 @@ class Interpreter {
value = try evaluate(expr: expr)
}

throw Return.return(value)
throw JumpType.return(value)
}

private func handleBreakStatement(breakToken: Token) throws {
throw JumpType.break
}

private func handleContinueStatement(continueToken: Token) throws {
throw JumpType.continue
}

private func handleVariableDeclaration(name: Token, expr: ResolvedExpression?) throws {
Expand Down Expand Up @@ -237,7 +254,39 @@ class Interpreter {

private func handleWhileStatement(expr: ResolvedExpression, stmt: ResolvedStatement) throws {
while try evaluate(expr: expr).isTruthy {
try execute(statement: stmt)
do {
try execute(statement: stmt)
} catch JumpType.break {
break
} catch JumpType.continue {
continue
}
}
}

private func handleForStatement(initializerStmt: ResolvedStatement?,
testExpr: ResolvedExpression,
incrementExpr: ResolvedExpression?,
bodyStmt: ResolvedStatement) throws {
if let initializerStmt {
try execute(statement: initializerStmt)
}

while try evaluate(expr: testExpr).isTruthy {
do {
try execute(statement: bodyStmt)
} catch JumpType.break {
break
} catch JumpType.continue {
if let incrementExpr {
_ = try evaluate(expr: incrementExpr)
}
continue
}

if let incrementExpr {
_ = try evaluate(expr: incrementExpr)
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion slox/Return.swift → slox/JumpType.swift
Expand Up @@ -7,6 +7,8 @@

import Foundation

enum Return: LocalizedError {
enum JumpType: LocalizedError {
case `return`(LoxValue)
case `break`
case `continue`
}
3 changes: 3 additions & 0 deletions slox/ParseError.swift
Expand Up @@ -34,6 +34,7 @@ enum ParseError: CustomStringConvertible, Equatable, LocalizedError {
case missingDotAfterSuper(Token)
case expectedSuperclassMethodName(Token)
case missingCloseBracketForSubscriptAccess(Token)
case unsupportedJumpStatement(Token)

var description: String {
switch self {
Expand Down Expand Up @@ -89,6 +90,8 @@ enum ParseError: CustomStringConvertible, Equatable, LocalizedError {
return "[Line \(token.line)] Error: expected superclass method name"
case .missingCloseBracketForSubscriptAccess(let token):
return "[Line \(token.line)] Error: expected closing bracket after subscript index"
case .unsupportedJumpStatement(let token):
return "[Line \(token.line)] Error: unsupported jump statement"
}
}
}
74 changes: 42 additions & 32 deletions slox/Parser.swift
Expand Up @@ -45,7 +45,7 @@ struct Parser {
// | forStmt
// | ifStmt
// | printStmt
// | returnStmt
// | jumpStmt
// | whileStmt
// | block ;
// exprStmt → expression ";" ;
Expand All @@ -55,7 +55,9 @@ struct Parser {
// ifStmt → "if" "(" expression ")" statement
// ( "else" statement )? ;
// printStmt → "print" expression ";" ;
// returnStmt → "return" expression? ";" ;
// jumpStmt → ( "return" expression? ";"
// | "break" ";"
// | "continue" ";" ) ;
// whileStmt → "while" "(" expression ")" statement ;
// block → "{" declaration* "}" ;
mutating private func parseDeclaration() throws -> Statement {
Expand Down Expand Up @@ -177,8 +179,8 @@ struct Parser {
return try parsePrintStatement()
}

if currentTokenMatchesAny(types: [.return]) {
return try parseReturnStatement()
if [.return, .break, .continue].contains(currentToken.type) {
return try parseJumpStatement()
}

if currentTokenMatchesAny(types: [.while]) {
Expand Down Expand Up @@ -207,9 +209,9 @@ struct Parser {
initializerStmt = try parseExpressionStatement()
}

var conditionExpr: Expression? = nil
var testExpr: Expression = .literal(.boolean(true))
if !currentTokenMatches(type: .semicolon) {
conditionExpr = try parseExpression()
testExpr = try parseExpression()
}
if !currentTokenMatchesAny(types: [.semicolon]) {
throw ParseError.missingSemicolonAfterForLoopCondition(currentToken)
Expand All @@ -223,25 +225,9 @@ struct Parser {
throw ParseError.missingCloseParenForForStatement(currentToken)
}

var forStmt = try parseStatement()

// Here is where we do desugaring, rewriting a for statement
// in terms of a while statement.
if let incrementExpr {
forStmt = .block([forStmt, .expression(incrementExpr)])
}

if let conditionExpr {
forStmt = .while(conditionExpr, forStmt)
} else {
forStmt = .while(.literal(.boolean(true)), forStmt)
}
let bodyStmt = try parseStatement()

if let initializerStmt {
forStmt = .block([initializerStmt, forStmt])
}

return forStmt
return .for(initializerStmt, testExpr, incrementExpr, bodyStmt)
}

mutating private func parseIfStatement() throws -> Statement {
Expand Down Expand Up @@ -273,19 +259,43 @@ struct Parser {
throw ParseError.missingSemicolon(currentToken)
}

mutating private func parseReturnStatement() throws -> Statement {
let returnToken = previousToken
mutating private func parseJumpStatement() throws -> Statement {
if currentTokenMatchesAny(types: [.return]) {
let returnToken = previousToken

var expr: Expression? = nil
if currentToken.type != .semicolon {
expr = try parseExpression()
}

if !currentTokenMatchesAny(types: [.semicolon]) {
throw ParseError.missingSemicolon(currentToken)
}

var expr: Expression? = nil
if currentToken.type != .semicolon {
expr = try parseExpression()
return .return(returnToken, expr)
}

if !currentTokenMatchesAny(types: [.semicolon]) {
throw ParseError.missingSemicolon(currentToken)
if currentTokenMatchesAny(types: [.break]) {
let breakToken = previousToken

if !currentTokenMatchesAny(types: [.semicolon]) {
throw ParseError.missingSemicolon(currentToken)
}

return .break(breakToken)
}

if currentTokenMatchesAny(types: [.continue]) {
let continueToken = previousToken

if !currentTokenMatchesAny(types: [.semicolon]) {
throw ParseError.missingSemicolon(currentToken)
}

return .continue(continueToken)
}

return .return(returnToken, expr)
throw ParseError.unsupportedJumpStatement(currentToken)
}

mutating private func parseWhileStatement() throws -> Statement {
Expand Down
3 changes: 3 additions & 0 deletions slox/ResolvedStatement.swift
Expand Up @@ -12,7 +12,10 @@ indirect enum ResolvedStatement: Equatable {
case variableDeclaration(Token, ResolvedExpression?)
case block([ResolvedStatement])
case `while`(ResolvedExpression, ResolvedStatement)
case `for`(ResolvedStatement?, ResolvedExpression, ResolvedExpression?, ResolvedStatement)
case function(Token, ResolvedExpression)
case `return`(Token, ResolvedExpression?)
case `class`(Token, ResolvedExpression?, [ResolvedStatement], [ResolvedStatement])
case `break`(Token)
case `continue`(Token)
}