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 lists #11

Merged
merged 17 commits into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions slox.xcodeproj/project.pbxproj
Expand Up @@ -52,6 +52,8 @@
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 */; };
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 */; };
Expand Down Expand Up @@ -101,6 +103,7 @@
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>"; };
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>"; };
87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxInstance.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -153,6 +156,7 @@
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */,
8702307F2B96E9580056FE57 /* LoxClass.swift */,
87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */,
87777E3B2BA0FF70002E38F2 /* LoxList.swift */,
876A315E2B897EEB0085A350 /* LoxValue.swift */,
870230742B9571490056FE57 /* MutableCollection+Extension.swift */,
8732838E2B93F89300E49035 /* NativeFunction.swift */,
Expand Down Expand Up @@ -275,6 +279,7 @@
buildActionMask = 2147483647;
files = (
876A31622B8986630085A350 /* ScanError.swift in Sources */,
87777E3C2BA0FF70002E38F2 /* LoxList.swift in Sources */,
876560052B8825AC002BDE42 /* Token.swift in Sources */,
873283942B95122800E49035 /* ResolvedStatement.swift in Sources */,
876A315F2B897EEB0085A350 /* LoxValue.swift in Sources */,
Expand Down Expand Up @@ -320,6 +325,7 @@
876A31602B89827B0085A350 /* LoxValue.swift in Sources */,
873CCB312B8EBB7800FC249A /* Statement.swift in Sources */,
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */,
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */,
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */,
8702307B2B95755E0056FE57 /* ResolvedExpression.swift in Sources */,
87C2F3742B91C2BA00126707 /* Return.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions slox/Environment.swift
Expand Up @@ -38,6 +38,18 @@ class Environment: Equatable {
throw RuntimeError.undefinedVariable(name)
}

func getValue(name: String) throws -> LoxValue {
if let value = values[name] {
return value
}

if let enclosingEnvironment {
return try enclosingEnvironment.getValue(name: name)
}

throw RuntimeError.undefinedVariable(name)
}

private func ancestor(depth: Int) throws -> Environment {
var i = 0
var ancestor: Environment = self
Expand Down
3 changes: 3 additions & 0 deletions slox/Expression.swift
Expand Up @@ -19,4 +19,7 @@ indirect enum Expression: Equatable {
case set(Expression, Token, Expression)
case this(Token)
case `super`(Token, Token)
case list([Expression])
case subscriptGet(Expression, Expression)
case subscriptSet(Expression, Expression, Expression)
}
117 changes: 85 additions & 32 deletions slox/Interpreter.swift
Expand Up @@ -8,26 +8,53 @@
import Foundation

class Interpreter {
static let standardLibrary = """
class List {
append(element) {
appendNative(this, element);
}

deleteAt(index) {
return deleteAtNative(this, index);
}
}
"""
var environment: Environment = Environment()

init() {
setUpGlobals()
}

private func prepareCode(source: String) throws -> [ResolvedStatement] {
var scanner = Scanner(source: source)
let tokens = try scanner.scanTokens()
var parser = Parser(tokens: tokens)
let statements = try parser.parse()
var resolver = Resolver()

return try resolver.resolve(statements: statements)
}

private func setUpGlobals() {
for nativeFunction in NativeFunction.allCases {
environment.define(name: String(describing: nativeFunction),
value: .nativeFunction(nativeFunction))
}

try! interpret(source: Self.standardLibrary)
}

func interpret(statements: [ResolvedStatement]) throws {
func interpret(source: String) throws {
let statements = try prepareCode(source: source)

for statement in statements {
try execute(statement: statement)
}
}

func interpretRepl(statements: [ResolvedStatement]) throws -> LoxValue? {
func interpretRepl(source: String) throws -> LoxValue? {
let statements = try prepareCode(source: source)

for (i, statement) in statements.enumerated() {
if i == statements.endIndex-1, case .expression(let expr) = statement {
return try evaluate(expr: expr)
Expand Down Expand Up @@ -71,7 +98,9 @@ class Interpreter {
private func handleIfStatement(testExpr: ResolvedExpression,
consequentStmt: ResolvedStatement,
alternativeStmt: ResolvedStatement?) throws {
if isTruthy(value: try evaluate(expr: testExpr)) {
let value = try evaluate(expr: testExpr)

if value.isTruthy {
try execute(statement: consequentStmt)
} else if let alternativeStmt {
try execute(statement: alternativeStmt)
Expand Down Expand Up @@ -207,7 +236,7 @@ class Interpreter {
}

private func handleWhileStatement(expr: ResolvedExpression, stmt: ResolvedStatement) throws {
while isTruthy(value: try evaluate(expr: expr)) {
while try evaluate(expr: expr).isTruthy {
try execute(statement: stmt)
}
}
Expand Down Expand Up @@ -242,6 +271,14 @@ class Interpreter {
return try handleLambdaExpression(params: params, statements: statements)
case .super(let superToken, let methodToken, let depth):
return try handleSuperExpression(superToken: superToken, methodToken: methodToken, depth: depth)
case .list(let elements):
return try handleListExpression(elements: elements)
case .subscriptGet(let listExpr, let indexExpr):
return try handleSubscriptGetExpression(listExpr: listExpr, indexExpr: indexExpr)
case .subscriptSet(let listExpr, let indexExpr, let valueExpr):
return try handleSubscriptSetExpression(listExpr: listExpr,
indexExpr: indexExpr,
valueExpr: valueExpr)
}
}

Expand All @@ -256,7 +293,7 @@ class Interpreter {

return .number(-number)
case .bang:
return .boolean(!isTruthy(value: value))
return .boolean(!value.isTruthy)
default:
throw RuntimeError.unsupportedUnaryOperator
}
Expand Down Expand Up @@ -300,9 +337,9 @@ class Interpreter {

switch oper.type {
case .bangEqual:
return .boolean(!isEqual(leftValue: leftValue, rightValue: rightValue))
return .boolean(!leftValue.isEqual(to: rightValue))
case .equalEqual:
return .boolean(isEqual(leftValue: leftValue, rightValue: rightValue))
return .boolean(leftValue.isEqual(to: rightValue))
case .plus:
throw RuntimeError.binaryOperandsMustBeNumbersOrStrings
case .minus, .star, .slash, .greater, .greaterEqual, .less, .lessEqual:
Expand Down Expand Up @@ -330,13 +367,13 @@ class Interpreter {
let leftValue = try evaluate(expr: leftExpr)

if case .and = oper.type {
if !isTruthy(value: leftValue) {
if !leftValue.isTruthy {
return leftValue
} else {
return try evaluate(expr: rightExpr)
}
} else {
if isTruthy(value: leftValue) {
if leftValue.isTruthy {
return leftValue
} else {
return try evaluate(expr: rightExpr)
Expand Down Expand Up @@ -391,7 +428,7 @@ class Interpreter {

let propertyValue = try evaluate(expr: valueExpr)

instance.set(propertyName: propertyNameToken.lexeme, propertyValue: propertyValue)
try instance.set(propertyName: propertyNameToken.lexeme, propertyValue: propertyValue)
return propertyValue
}

Expand Down Expand Up @@ -427,31 +464,47 @@ class Interpreter {
throw RuntimeError.undefinedProperty(methodToken.lexeme)
}

// Utility functions below
private func isEqual(leftValue: LoxValue, rightValue: LoxValue) -> Bool {
switch (leftValue, rightValue) {
case (.nil, .nil):
return true
case (.number(let leftNumber), .number(let rightNumber)):
return leftNumber == rightNumber
case (.string(let leftString), .string(let rightString)):
return leftString == rightString
case (.boolean(let leftBoolean), .boolean(let rightBoolean)):
return leftBoolean == rightBoolean
default:
return false
private func handleListExpression(elements: [ResolvedExpression]) throws -> LoxValue {
let elementValues = try elements.map { element in
return try evaluate(expr: element)
}

guard case .instance(let listClass as LoxClass) = try environment.getValue(name: "List") else {
// TODO: Do we need throw an exception here?
fatalError()
}

let list = LoxList(elements: elementValues, klass: listClass)
return .instance(list)
}

// In Lox, `false` and `nil` are false; everything else is true
private func isTruthy(value: LoxValue) -> Bool {
switch value {
case .nil:
return false
case .boolean(let boolean):
return boolean
default:
return true
private func handleSubscriptGetExpression(listExpr: ResolvedExpression,
indexExpr: ResolvedExpression) throws -> LoxValue {
guard case .instance(let list as LoxList) = try evaluate(expr: listExpr) else {
throw RuntimeError.notAList
}

guard case .number(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeANumber
}

return list[Int(index)]
}

private func handleSubscriptSetExpression(listExpr: ResolvedExpression,
indexExpr: ResolvedExpression,
valueExpr: ResolvedExpression) throws -> LoxValue {
guard case .instance(let list as LoxList) = try evaluate(expr: listExpr) else {
throw RuntimeError.notAList
}

guard case .number(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeANumber
}

let value = try evaluate(expr: valueExpr)

list[Int(index)] = value
return value
}
}
16 changes: 2 additions & 14 deletions slox/Lox.swift
Expand Up @@ -42,13 +42,7 @@ struct Lox {
print("slox>", terminator: " ")
while let input = readLine() {
do {
var scanner = Scanner(source: input)
let tokens = try scanner.scanTokens()
var parser = Parser(tokens: tokens)
let statements = try parser.parse()
var resolver = Resolver()
let resolvedStatements = try resolver.resolve(statements: statements)
if let result = try interpreter.interpretRepl(statements: resolvedStatements) {
if let result = try interpreter.interpretRepl(source: input) {
print(result)
}
} catch {
Expand All @@ -60,12 +54,6 @@ struct Lox {
}

private static func run(input: String) throws {
var scanner = Scanner(source: input)
let tokens = try scanner.scanTokens()
var parser = Parser(tokens: tokens)
let statements = try parser.parse()
var resolver = Resolver()
let resolvedStatements = try resolver.resolve(statements: statements)
try interpreter.interpret(statements: resolvedStatements)
try interpreter.interpret(source: input)
}
}
2 changes: 1 addition & 1 deletion slox/LoxInstance.swift
Expand Up @@ -45,7 +45,7 @@ class LoxInstance: Equatable {
throw RuntimeError.undefinedProperty(propertyName)
}

func set(propertyName: String, propertyValue: LoxValue) {
func set(propertyName: String, propertyValue: LoxValue) throws {
self.properties[propertyName] = propertyValue
}

Expand Down
45 changes: 45 additions & 0 deletions slox/LoxList.swift
@@ -0,0 +1,45 @@
//
// LoxList.swift
// slox
//
// Created by Danielle Kefford on 3/12/24.
//

class LoxList: LoxInstance {
var elements: [LoxValue]
var count: Int {
return elements.count
}

init(elements: [LoxValue], klass: LoxClass) {
self.elements = elements
super.init(klass: klass)
}

override func get(propertyName: String) throws -> LoxValue {
switch propertyName {
case "count":
return .number(Double(elements.count))
default:
return try super.get(propertyName: propertyName)
}
}

override func set(propertyName: String, propertyValue: LoxValue) throws {
throw RuntimeError.onlyInstancesHaveProperties
}

static func == (lhs: LoxList, rhs: LoxList) -> Bool {
return lhs.elements == rhs.elements
}

// TODO: Need to think about how to handle invalid indices!!!
subscript(index: Int) -> LoxValue {
get {
return elements[index]
}
set(newValue) {
elements[index] = newValue
}
}
}