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 dictionary #36

Merged
merged 14 commits into from Apr 1, 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
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -29,6 +29,9 @@ So far, the following have been implemented in `slox`:
- Native functions for lists, `append()` and `deleteAt()`
- `break` and `continue` for flow control within loops
- Computed properties (inside classes, not at the top-level)
- Dictionary literals using square brackets and colons
- Native properties for dictionaries, `keys` and `values`
- Native functions for dictionaries, `merge()` and `removeValue()`

# Design

Expand Down
12 changes: 12 additions & 0 deletions slox.xcodeproj/project.pbxproj
Expand Up @@ -15,6 +15,7 @@
8702307C2B9575610056FE57 /* ResolvedStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873283932B95122800E49035 /* ResolvedStatement.swift */; };
8702307E2B95AA2A0056FE57 /* ResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8702307D2B95AA2A0056FE57 /* ResolverTests.swift */; };
870230802B96E9580056FE57 /* LoxClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8702307F2B96E9580056FE57 /* LoxClass.swift */; };
8730DDE92BB250CE00372548 /* LoxDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8730DDE82BB250CE00372548 /* LoxDictionary.swift */; };
8732838F2B93F89300E49035 /* NativeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732838E2B93F89300E49035 /* NativeFunction.swift */; };
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732838E2B93F89300E49035 /* NativeFunction.swift */; };
873283922B95118A00E49035 /* ResolvedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873283912B95118A00E49035 /* ResolvedExpression.swift */; };
Expand All @@ -37,6 +38,7 @@
873CCB342B8EE0FF00FC249A /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB322B8ED8B900FC249A /* Environment.swift */; };
8755B8B42B91983F00530DC4 /* UserDefinedFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */; };
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC482B9179CB0013E5FE /* LoxCallable.swift */; };
8764AB632BB8E5A7006D4B9D /* StandardLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8792A9982BB36C66009842D8 /* StandardLibrary.swift */; };
876560032B882259002BDE42 /* TokenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876560022B882259002BDE42 /* TokenType.swift */; };
876560052B8825AC002BDE42 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876560042B8825AC002BDE42 /* Token.swift */; };
876560072B8827F9002BDE42 /* Scanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876560062B8827F9002BDE42 /* Scanner.swift */; };
Expand All @@ -54,9 +56,11 @@
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 */; };
8792A9992BB36C66009842D8 /* StandardLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8792A9982BB36C66009842D8 /* StandardLibrary.swift */; };
87BAFC492B9179CB0013E5FE /* LoxCallable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC482B9179CB0013E5FE /* LoxCallable.swift */; };
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */; };
87C2F3742B91C2BA00126707 /* JumpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* JumpType.swift */; };
87CB69762BB35EBA002A2E69 /* LoxDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8730DDE82BB250CE00372548 /* LoxDictionary.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 All @@ -79,6 +83,7 @@
870230762B9574A90056FE57 /* TestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestPlan.xctestplan; sourceTree = "<group>"; };
8702307D2B95AA2A0056FE57 /* ResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverTests.swift; sourceTree = "<group>"; };
8702307F2B96E9580056FE57 /* LoxClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxClass.swift; sourceTree = "<group>"; };
8730DDE82BB250CE00372548 /* LoxDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxDictionary.swift; sourceTree = "<group>"; };
8732838E2B93F89300E49035 /* NativeFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFunction.swift; sourceTree = "<group>"; };
873283912B95118A00E49035 /* ResolvedExpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolvedExpression.swift; sourceTree = "<group>"; };
873283932B95122800E49035 /* ResolvedStatement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolvedStatement.swift; sourceTree = "<group>"; };
Expand All @@ -104,6 +109,7 @@
876A31682B8C3AAB0085A350 /* ParseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseError.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>"; };
8792A9982BB36C66009842D8 /* StandardLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardLibrary.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 @@ -156,6 +162,7 @@
873CCB242B8D765D00FC249A /* Lox.swift */,
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */,
8702307F2B96E9580056FE57 /* LoxClass.swift */,
8730DDE82BB250CE00372548 /* LoxDictionary.swift */,
87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */,
87777E3B2BA0FF70002E38F2 /* LoxList.swift */,
876A315E2B897EEB0085A350 /* LoxValue.swift */,
Expand All @@ -170,6 +177,7 @@
873CCB222B8D617C00FC249A /* RuntimeError.swift */,
876A31612B8986630085A350 /* ScanError.swift */,
876560062B8827F9002BDE42 /* Scanner.swift */,
8792A9982BB36C66009842D8 /* StandardLibrary.swift */,
873CCB2F2B8EAEC100FC249A /* Statement.swift */,
876560042B8825AC002BDE42 /* Token.swift */,
876560022B882259002BDE42 /* TokenType.swift */,
Expand Down Expand Up @@ -293,6 +301,7 @@
876A31672B8C11810085A350 /* Parser.swift in Sources */,
876560072B8827F9002BDE42 /* Scanner.swift in Sources */,
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */,
8792A9992BB36C66009842D8 /* StandardLibrary.swift in Sources */,
873CCB332B8ED8B900FC249A /* Environment.swift in Sources */,
876A31652B8C04990085A350 /* Expression.swift in Sources */,
876560032B882259002BDE42 /* TokenType.swift in Sources */,
Expand All @@ -301,6 +310,7 @@
8732838F2B93F89300E49035 /* NativeFunction.swift in Sources */,
873283922B95118A00E49035 /* ResolvedExpression.swift in Sources */,
876A31692B8C3AAB0085A350 /* ParseError.swift in Sources */,
8730DDE92BB250CE00372548 /* LoxDictionary.swift in Sources */,
870230752B9571490056FE57 /* MutableCollection+Extension.swift in Sources */,
873CCB252B8D765D00FC249A /* Lox.swift in Sources */,
);
Expand All @@ -310,6 +320,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8764AB632BB8E5A7006D4B9D /* StandardLibrary.swift in Sources */,
8702307C2B9575610056FE57 /* ResolvedStatement.swift in Sources */,
873CCB2D2B8E88C900FC249A /* Interpreter.swift in Sources */,
876A31632B8987740085A350 /* ScanError.swift in Sources */,
Expand All @@ -325,6 +336,7 @@
876A31602B89827B0085A350 /* LoxValue.swift in Sources */,
873CCB312B8EBB7800FC249A /* Statement.swift in Sources */,
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */,
87CB69762BB35EBA002A2E69 /* LoxDictionary.swift in Sources */,
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */,
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */,
8702307B2B95755E0056FE57 /* ResolvedExpression.swift in Sources */,
Expand Down
52 changes: 52 additions & 0 deletions slox/Expression.swift
Expand Up @@ -22,4 +22,56 @@ indirect enum Expression: Equatable {
case list([Expression])
case subscriptGet(Expression, Expression)
case subscriptSet(Expression, Expression, Expression)
case dictionary([(Expression, Expression)])

static func == (lhs: Expression, rhs: Expression) -> Bool {
switch (lhs, rhs) {
case (.binary(let lhsExpr1, let lhsOper, let lhsExpr2), .binary(let rhsExpr1, let rhsOper, let rhsExpr2)):
return lhsExpr1 == rhsExpr1 && lhsOper == rhsOper && lhsExpr2 == rhsExpr2
case (.unary(let lhsOper, let lhsExpr), .unary(let rhsOper, let rhsExpr)):
return lhsOper == rhsOper && lhsExpr == rhsExpr
case (.literal(let lhsValue), .literal(let rhsValue)):
return lhsValue == rhsValue
case (.grouping(let lhsExpr), .grouping(let rhsExpr)):
return lhsExpr == rhsExpr
case (.variable(let lhsToken), .variable(let rhsToken)):
return lhsToken == rhsToken
case (.assignment(let lhsName, let lhsExpr), .assignment(let rhsName, let rhsExpr)):
return lhsName == rhsName && lhsExpr == rhsExpr
case (.logical(let lhsExpr1, let lhsOper, let lhsExpr2), .logical(let rhsExpr1, let rhsOper, let rhsExpr2)):
return lhsExpr1 == rhsExpr1 && lhsOper == rhsOper && lhsExpr2 == rhsExpr2
case (.call(let lhsCallee, let lhsToken, let lhsArgs), .call(let rhsCallee, let rhsToken, let rhsArgs)):
return lhsCallee == rhsCallee && lhsToken == rhsToken && lhsArgs == rhsArgs
case (.lambda(let lhsParams, let lhsBody), .lambda(let rhsParams, let rhsBody)):
return lhsParams == rhsParams && lhsBody == rhsBody
case (.get(let lhsExpr, let lhsName), .get(let rhsExpr, let rhsName)):
return lhsExpr == rhsExpr && lhsName == rhsName
case (.set(let lhsExpr1, let lhsName, let lhsExpr2), .set(let rhsExpr1, let rhsName, let rhsExpr2)):
return lhsExpr1 == rhsExpr1 && lhsName == rhsName && lhsExpr2 == rhsExpr2
case (.this(let lhsToken), .this(let rhsToken)):
return lhsToken == rhsToken
case (.super(let lhsSuper, let lhsMethod), .super(let rhsSuper, let rhsMethod)):
return lhsSuper == rhsSuper && lhsMethod == rhsMethod
case (.list(let lhsExprs), .list(let rhsExprs)):
return lhsExprs == rhsExprs
case (.subscriptGet(let lhsList, let lhsIdx), .subscriptGet(let rhsList, let rhsIdx)):
return lhsList == rhsList && lhsIdx == rhsIdx
case (.subscriptSet(let lhsList, let lhsIdx, let lhsExpr), .subscriptSet(let rhsList, let rhsIdx, let rhsExpr)):
return lhsList == rhsList && lhsIdx == rhsIdx && lhsExpr == rhsExpr
case (.dictionary(let lhsKVPairs), .dictionary(let rhsKVPairs)):
if lhsKVPairs.count != rhsKVPairs.count {
return false
}

for ((lhsKey, lhsValue), (rhsKey, rhsValue)) in zip(lhsKVPairs, rhsKVPairs) {
if lhsKey != rhsKey || lhsValue != rhsValue {
return false
}
}

return true
default:
return false
}
}
}
118 changes: 61 additions & 57 deletions slox/Interpreter.swift
Expand Up @@ -8,44 +8,6 @@
import Foundation

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

deleteAt(index) {
return deleteAtNative(this, index);
}

map(fn) {
var result = [];
for (var i = 0; i < this.count; i = i + 1) {
var newElement = fn(this[i]);
result.append(newElement);
}
return result;
}

filter(fn) {
var result = [];
for (var i = 0; i < this.count; i = i + 1) {
if (fn(this[i])) {
result.append(this[i]);
}
}
return result;
}

reduce(initial, fn) {
var result = initial;
for (var i = 0; i < this.count; i = i + 1) {
result = fn(result, this[i]);
}
return result;
}
}
"""
var environment: Environment = Environment()

init() {
Expand All @@ -68,7 +30,7 @@ class Interpreter {
value: .nativeFunction(nativeFunction))
}

try! interpret(source: Self.standardLibrary)
try! interpret(source: standardLibrary)
}

func interpret(source: String) throws {
Expand Down Expand Up @@ -350,11 +312,13 @@ class Interpreter {
case .list(let elements):
return try handleListExpression(elements: elements)
case .subscriptGet(let listExpr, let indexExpr):
return try handleSubscriptGetExpression(listExpr: listExpr, indexExpr: indexExpr)
return try handleSubscriptGetExpression(collectionExpr: listExpr, indexExpr: indexExpr)
case .subscriptSet(let listExpr, let indexExpr, let valueExpr):
return try handleSubscriptSetExpression(listExpr: listExpr,
return try handleSubscriptSetExpression(collectionExpr: listExpr,
indexExpr: indexExpr,
valueExpr: valueExpr)
case .dictionary(let kvPairs):
return try handleDictionary(kvExprPairs: kvPairs)
}
}

Expand Down Expand Up @@ -595,34 +559,65 @@ class Interpreter {
return try makeList(elements: elementValues)
}

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

guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
}
switch collection {
case .instance(let list as LoxList):
guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
}

return list[Int(index)]
case .instance(let dictionary as LoxDictionary):
let key = try evaluate(expr: indexExpr)

return list[Int(index)]
return dictionary[key]
default:
throw RuntimeError.notAListOrDictionary
}
}

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

switch collection {
case .instance(let list as LoxList):
guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
}

list[Int(index)] = value
case .instance(let dictionary as LoxDictionary):
let key = try evaluate(expr: indexExpr)

dictionary[key] = value
default:
throw RuntimeError.notAListOrDictionary
}

guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
return value
}

private func handleDictionary(kvExprPairs: [(ResolvedExpression, ResolvedExpression)]) throws -> LoxValue {
var kvPairs: [LoxValue: LoxValue] = [:]

for (keyExpr, valueExpr) in kvExprPairs {
let key = try evaluate(expr: keyExpr)
let value = try evaluate(expr: valueExpr)
kvPairs[key] = value
}

let value = try evaluate(expr: valueExpr)
guard case .instance(let dictionaryClass as LoxClass) = try environment.getValue(name: "Dictionary") else {
fatalError()
}

list[Int(index)] = value
return value
let dictionary = LoxDictionary(kvPairs: kvPairs, klass: dictionaryClass)
return .instance(dictionary)
}

func makeList(elements: [LoxValue]) throws -> LoxValue {
Expand All @@ -633,4 +628,13 @@ class Interpreter {
let list = LoxList(elements: elements, klass: listClass)
return .instance(list)
}

func makeDictionary(kvPairs: [LoxValue: LoxValue]) throws -> LoxValue {
guard case .instance(let dictionaryClass as LoxClass) = try environment.getValue(name: "Dictionary") else {
fatalError()
}

let dictionary = LoxDictionary(kvPairs: kvPairs, klass: dictionaryClass)
return .instance(dictionary)
}
}
2 changes: 2 additions & 0 deletions slox/LoxClass.swift
Expand Up @@ -19,6 +19,8 @@ class LoxClass: LoxInstance, LoxCallable {
var instanceType: LoxInstance.Type {
if self.name == "List" {
LoxList.self
} else if self.name == "Dictionary" {
LoxDictionary.self
} else {
LoxInstance.self
}
Expand Down
42 changes: 42 additions & 0 deletions slox/LoxDictionary.swift
@@ -0,0 +1,42 @@
//
// LoxDictionary.swift
// slox
//
// Created by Danielle Kefford on 3/25/24.
//

class LoxDictionary: LoxInstance {
var kvPairs: [LoxValue: LoxValue]

convenience init(kvPairs: [LoxValue: LoxValue], klass: LoxClass) {
self.init(klass: klass)
self.kvPairs = kvPairs
}

required init(klass: LoxClass?) {
self.kvPairs = [:]
super.init(klass: klass)
}

override func get(propertyName: String) throws -> LoxValue {
switch propertyName {
case "count":
return .int(self.kvPairs.count)
default:
return try super.get(propertyName: propertyName)
}
}

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

subscript(key: LoxValue) -> LoxValue {
get {
return kvPairs[key] ?? .nil
}
set(newValue) {
kvPairs[key] = newValue
}
}
}