Skip to content

Commit

Permalink
Support for statements and states
Browse files Browse the repository at this point in the history
  • Loading branch information
theSalted committed Jun 29, 2024
1 parent d6fbd73 commit e7bea96
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 30 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ This project follows the book [Crafting Interpreters](http://www.craftinginterpr
- [ ] Comprehensive Tests
- [x] Interpreter
- [x] Basic expression
- [x] Statements
- [x] States
- [x] Scoping
- [ ] Control Flows
- [ ] Functions
- [ ] Classes
- [ ] Interpreter
- [ ] Expression support for REPL mode

## Reference
- [Original Implementation](https://github.com/munificent/craftinginterpreters)
Expand Down
8 changes: 8 additions & 0 deletions Sources/Lox/AbstractSyntaxTreePrinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ public struct AbstractSyntaxTreePrinter: StatementVisitor, ExpressionVisitor {
parenthesize(name: ";", expressions: stmt.expression)
}

public func visit(_ stmt: Block) -> String {
var output = ""
for statement in stmt.statements {
output.append(statement.accept(visitor: self))
}
return "(block \(output))"
}

public func visit(_ stmt: Var) -> String {
guard let initializer = stmt.initializer else {
return parenthesize(name: "var", parts: stmt.name)
Expand Down
13 changes: 13 additions & 0 deletions Sources/Lox/Definitions/Statement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public protocol StatementVisitor {
associatedtype StatementVisitorReturn

func visit(_ stmt: Expr) -> StatementVisitorReturn
func visit(_ stmt: Block) -> StatementVisitorReturn
func visit(_ stmt: Var) -> StatementVisitorReturn
func visit(_ stmt: Print) -> StatementVisitorReturn
}
Expand All @@ -30,6 +31,18 @@ public struct Expr: Statement {
}
}

public struct Block: Statement {
let statements: Array<Statement>

init(statements: Array<Statement>) {
self.statements = statements
}

public func accept<V: StatementVisitor, R>(visitor: V) -> R where R == V.StatementVisitorReturn {
return visitor.visit(self)
}
}

public struct Var: Statement {
let name: Token
let initializer: Expression?
Expand Down
15 changes: 14 additions & 1 deletion Sources/Lox/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@
let NilAny: Any = Optional<Any>.none as Any

public final class Environment {
var enclosing: Environment?
private var values = Dictionary<String, Any>()

init() {
self.enclosing = nil
}

init(enclosing: Environment) {
self.enclosing = enclosing
}

func define(name: String, value: Any) {
assert(!(value is Result<Any, InterpreterError>), "You should not store a result, please store its value instead")
values[name] = value
Expand All @@ -28,7 +37,11 @@ public final class Environment {
if let unwrapped = values[name.lexeme] {
return unwrapped
}
return NilAny
throw InterpreterError.runtime(message: "Variable \(name.lexeme) is undefined.", onLine: name.line, locationDescription: nil)
}

if let enclosing {
return try enclosing.get(name)
}

throw InterpreterError.runtime(message: "Undefined variable \(name.lexeme).", onLine: name.line, locationDescription: nil)
Expand Down
60 changes: 37 additions & 23 deletions Sources/Lox/Interpreter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
import OSLog

public final class Interpreter: StatementVisitor, ExpressionVisitor {
private let environment = Environment()
private var environment = Environment()

func intercept(_ statements: Array<Statement>) {
func interpret(_ statements: Array<Statement>) {
for statement in statements {
switch execute(stmt: statement) {

case .success(_), .none:
break
case .failure(let error):
return Lox.reportError(error)
let result = execute(stmt: statement)
if case let .failure(error) = result {
Lox.reportError(error)
}
}
}
Expand All @@ -44,6 +41,11 @@ public final class Interpreter: StatementVisitor, ExpressionVisitor {
}
}

public func visit(_ stmt: Block) -> Value? {
let result = executeBlock(statements: stmt.statements, environment: Environment(enclosing: environment))
return result
}

public func visit(_ stmt: Var) -> Value? {
var value: Value? = nil

Expand All @@ -59,15 +61,13 @@ public final class Interpreter: StatementVisitor, ExpressionVisitor {
case .failure(let error):
return .failure(error)
case .none:
return nil
/* Uncomment this to throw an error for un-init value
return .failure(
InterpreterError.runtime(
message: "variable must be initialized",
onLine: stmt.name.line,
locationDescription: nil
)
)*/
)
}
}

Expand Down Expand Up @@ -97,14 +97,11 @@ public final class Interpreter: StatementVisitor, ExpressionVisitor {
case .failure(let error):
return .failure(error)
case .none:
try? environment.assign(name: name, value: NilAny)
value = NilAny
/* Uncomment to throw an error instead
return .failure(
InterpreterError.runtime(
message: "Assignment can't be resolved",
onLine: name.line,
locationDescription: nil))*/
locationDescription: nil))
}
do { try environment.assign(name: expr.name, value: value) }
catch {
Expand Down Expand Up @@ -230,10 +227,14 @@ public final class Interpreter: StatementVisitor, ExpressionVisitor {
}

public func visit(_ expr: Variable) -> Value? {
if let value = try? environment.get(expr.name) {
let name = expr.name
if let value = try? environment.get(name) {
return .success(value)
} else {
return nil
return .failure(InterpreterError.runtime(
message: "Undefined variable '\(name.lexeme)'.",
onLine: name.line,
locationDescription: nil))
}
}

Expand All @@ -244,13 +245,26 @@ public final class Interpreter: StatementVisitor, ExpressionVisitor {

extension Interpreter {
private func execute(stmt: Statement) -> Value? {
switch stmt.accept(visitor: self) {

case .none, .success(_):
return nil
case .failure(let error):
return .failure(error)
let value = stmt.accept(visitor: self)
return value
}

private func executeBlock(statements: Array<Statement>, environment: Environment) -> Value? {
let previous = self.environment
self.environment = environment

defer {
self.environment = previous
}

for statement in statements {
let result = execute(stmt: statement)
if case let .failure(error) = result {
return .failure(error)
}
}

return nil
}

private func evaluate(_ expr: Expression) -> Value? {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Lox/Lox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct Lox {

if (hadError) { return }

interpreter.intercept(statements)
interpreter.interpret(statements)
}

/// Runs the interpreter on the given code string.
Expand Down Expand Up @@ -92,7 +92,7 @@ public extension Lox {
static func reportError(_ interpreterError: InterpreterError) {
switch interpreterError {
case .runtime(message: let message, onLine: let onLine, locationDescription: let locationDescription):
reportError(message, at: locationDescription, on: onLine)
reportError(message, at: locationDescription, on: onLine) // TODO: Better way
hadRuntimeError = true
}
}
Expand Down
28 changes: 25 additions & 3 deletions Sources/Lox/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ public class Parser {
}

private func statement() throws -> Statement {
if (match(.print)) { return try printStatement() }
if match(.print) { return try printStatement() }
if match(.leftBrace) {
return Block(statements: try block())
}

return try expressionStatement()
}

Expand Down Expand Up @@ -89,6 +93,24 @@ public class Parser {
return Expr(expression: expression)
}

private func block() throws -> Array<Statement> {
var statements = Array<Statement>()

while !matchLatest(.rightBrace) && !reachedEOF() {
if let declaration = declaration() {
statements.append(declaration)
}
}

do {
try consume(.rightBrace)
} catch {
throw reportError("Expect '}' after block.", token: latestToken)
}

return statements
}

private func printStatement() throws -> Statement {
let value = try expression()
do {
Expand Down Expand Up @@ -173,7 +195,7 @@ public class Parser {
try consume(.rightParenthesis)
} catch {
// error("Expected ')' after expression")
throw reportError("unexpected ')' after expression", token: latestToken)
throw reportError("Unexpected ')' after expression", token: latestToken)
}

return Grouping(expression: expression)
Expand Down Expand Up @@ -218,7 +240,7 @@ public extension Parser {

/// Check if latest character matches given token
private func matchLatest(_ type: TokenType) -> Bool {
if (reachedEOF()) { return false }
if reachedEOF() { return false }
return latestToken.type == type
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Lox/Scanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public class Scanner {
scanNumber()
case _ where targetCharacter.isLetter || targetCharacter == T.underScore.character:
scanKeywordAndIdentifier()
case " ", "\r", "\t": break // Ignore whitespace characters
case " ", "\r", "\t", "\n": break // Ignore whitespace characters
default: Lox.reportError("Unexpected character: '\(targetCharacter)'", on: line)
}
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/SyntaxDefinitionGenerator/Resources/statement.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"name": "Expr",
"parameterField": "expression: Expression"
},
{
"name": "Block",
"parameterField": "statements: Array<Statement>"
},
{
"name": "Var",
"parameterField": "name: Token, initializer: Expression?"
Expand Down

0 comments on commit e7bea96

Please sign in to comment.