Skip to content

Commit

Permalink
Merge pull request #15 from quephird/implement_break_and_continue
Browse files Browse the repository at this point in the history
Implement `break` and `continue`
  • Loading branch information
quephird committed Mar 17, 2024
2 parents 1cc1447 + 3d7fd4c commit 801e5ed
Show file tree
Hide file tree
Showing 16 changed files with 425 additions and 64 deletions.
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)
}

0 comments on commit 801e5ed

Please sign in to comment.