├── Sources ├── uikonf2020 │ ├── main.swift │ ├── Repl.swift │ ├── Tree.swift │ ├── TreeView.swift │ ├── Boilerplate.swift │ └── Views.swift ├── Step0Parsing │ ├── Template.swift │ └── Parsing.swift ├── Step1Evaluation │ ├── Template.swift │ ├── Evaluation.swift │ └── Parsing.swift ├── Template │ ├── Template.swift │ ├── Evaluation.swift │ └── Parsing.swift └── Step2Annotation │ ├── Template.swift │ ├── Evaluation.swift │ └── Parsing.swift ├── .gitignore ├── Tests ├── LinuxMain.swift ├── uikonf2020Tests │ └── XCTestManifests.swift └── TemplateTests │ ├── EvalutationTests.swift │ └── ParsingTests.swift ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── uikonf2020.xcscheme ├── README.md ├── Package.resolved └── Package.swift /Sources/uikonf2020/main.swift: -------------------------------------------------------------------------------- 1 | 2 | repl() 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import uikonf2020Tests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += uikonf2020Tests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/uikonf2020Tests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(uikonf2020Tests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uikonf2020 2 | 3 | Sample code for my 2020 [UIKonf](https://uikonf.com/) talk. Includes different stages of the language (as different modules) and a "final" version including a REPL (type "gui" in the REPL to bring up a small graphical parser/evaluator, I made this specifically for the presentation). 4 | -------------------------------------------------------------------------------- /Sources/Step0Parsing/Template.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | indirect public enum Expression: Equatable, Hashable { 11 | case variable(String) 12 | case intLiteral(Int) 13 | case stringLiteral(String) 14 | case function(parameters: [String], body: Expression) 15 | case call(Expression, arguments: [Expression]) 16 | case `let`(name: String, value: Expression, in: Expression) 17 | case tag(name: String, body: [Expression]) 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Step1Evaluation/Template.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | indirect public enum Expression: Equatable, Hashable { 11 | case variable(String) 12 | case intLiteral(Int) 13 | case stringLiteral(String) 14 | case function(parameters: [String], body: Expression) 15 | case call(Expression, arguments: [Expression]) 16 | case `let`(name: String, value: Expression, in: Expression) 17 | case tag(name: String, body: [Expression]) 18 | } 19 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "LineNoise", 6 | "repositoryURL": "https://github.com/andybest/linenoise-swift", 7 | "state": { 8 | "branch": null, 9 | "revision": "afc897c02b030af7333105c4fedcd843a8339238", 10 | "version": "0.0.3" 11 | } 12 | }, 13 | { 14 | "package": "Nimble", 15 | "repositoryURL": "https://github.com/Quick/Nimble.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "e9d769113660769a4d9dd3afb855562c0b7ae7b0", 19 | "version": "7.3.4" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "uikonf2020", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | dependencies: [ 12 | .package(name: "LineNoise", url: "https://github.com/andybest/linenoise-swift", from: "0.0.3"), 13 | ], 14 | targets: [ 15 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 16 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 17 | .target( 18 | name: "uikonf2020", 19 | dependencies: ["Template", "LineNoise"]), 20 | .target( 21 | name: "Template", 22 | dependencies: []), 23 | .target( 24 | name: "Step0Parsing", 25 | dependencies: []), 26 | .target( 27 | name: "Step1Evaluation", 28 | dependencies: []), 29 | .target( 30 | name: "Step2Annotation", 31 | dependencies: []), 32 | .testTarget( 33 | name: "uikonf2020Tests", 34 | dependencies: ["uikonf2020"]), 35 | .testTarget( 36 | name: "TemplateTests", 37 | dependencies: ["Template"]), 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /Tests/TemplateTests/EvalutationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Template 3 | 4 | final class EvaluationTests: XCTestCase { 5 | var input: String! 6 | func parsed(file: StaticString = #file, line: UInt = #line) -> AnnotatedExpression { 7 | do { 8 | return try input.parse() 9 | } catch { 10 | let e = error as! ParseError 11 | let lineRange = input!.lineRange(for: e.position.. Value { 29 | let p = parsed(file: file, line: line) 30 | return try p.run().0.get() 31 | } 32 | 33 | func testInt() throws { 34 | input = """ 35 | 42 36 | """ 37 | try XCTAssertEqual(evaluated(), .int(42)) 38 | } 39 | 40 | func testVariable() throws { 41 | input = """ 42 | let const = func(x,y){ y } in const(1, 42) 43 | """ 44 | try XCTAssertEqual(evaluated(), .int(42)) 45 | } 46 | 47 | func testTag() throws { 48 | input = """ 49 | let t = func(name){ { name } } in 50 | { t("My < Title") } 51 | """ 52 | try XCTAssertEqual(evaluated(), .html("My < Title")) 53 | } 54 | // 55 | // func testFunction() throws { 56 | // input = """ 57 | // { x, y in x } 58 | // """ 59 | // try XCTAssertEqual(parsed(), .function(parameters: ["x", "y"], body: .variable("x"))) 60 | // } 61 | // 62 | // func testFunctionCall() throws { 63 | // input = """ 64 | // foo(hello) 65 | // """ 66 | // try XCTAssertEqual(parsed(), .call(.variable("foo"), arguments: [.variable("hello")])) 67 | // } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/uikonf2020/Repl.swift: -------------------------------------------------------------------------------- 1 | import LineNoise 2 | import Template 3 | import SwiftUI 4 | 5 | struct PrettyError: Error { 6 | var string = "" 7 | } 8 | 9 | func parse(_ input: String) throws -> AnnotatedExpression { 10 | var e = PrettyError() 11 | do { 12 | return try input.parse() 13 | } catch let err as ParseError { 14 | let lineRange = input.lineRange(for: err.position.. ") 31 | ln.addHistory(input) 32 | print("") 33 | if input == "gui" { 34 | run(view: 35 | TreeView() 36 | .frame(width: 1920/2, height: 1080/2) 37 | .background(Color.black) 38 | ) 39 | } 40 | let parsed = try input.parse() 41 | let result = try parsed.run().0.get() 42 | print(result.pretty) 43 | } catch let e as ParseError { 44 | let lineRange = input.lineRange(for: e.position..: Identifiable { 13 | public let id: UUID 14 | public var label: A 15 | public var children: [(name: String?, value: Tree)] = [] 16 | } 17 | 18 | public struct ExpressionNode { 19 | var label: Text 20 | var state: State = .none 21 | 22 | init(_ label: Text, state: State) { 23 | self.label = label 24 | self.state = state 25 | } 26 | 27 | enum State: Hashable { 28 | case none 29 | case inProgress(context: [String:Value]) 30 | case done(Result) 31 | } 32 | } 33 | 34 | extension Text { 35 | var keyword: Text { bold() } 36 | } 37 | 38 | extension AnnotatedExpression { 39 | func tree(trace: [UUID:ExpressionNode.State]) -> Tree { 40 | let state = trace[id] ?? .none 41 | switch self.expression { 42 | case let .variable(name): 43 | let text = Text(".variable").keyword + Text("(" + name + ")") 44 | return Tree(id: id, label: .init(text, state: state)) 45 | case let .intLiteral(value): 46 | let text = Text(".int").keyword + Text("(\(value))") 47 | return Tree(id: id, label: .init(text, state: state)) 48 | case let .stringLiteral(str): 49 | let text = Text(".string").keyword + Text("(\"\(str)\")") 50 | return Tree(id: id, label: .init(text, state: state)) 51 | case let .function(parameters: parameters, body: body): 52 | let params = "(" + parameters.joined(separator: ", ") + ")" 53 | let text = Text(".function").keyword + Text(params) 54 | return Tree(id: id, label: .init(text, state: state), children: [ 55 | ("body", body.tree(trace: trace)) 56 | ]) 57 | 58 | case let .call(lhs, arguments: arguments): 59 | return Tree(id: id, label: .init(Text(".call").keyword, state: state), children: [ 60 | ("lhs", lhs.tree(trace: trace)) 61 | ] + arguments.map { arg in 62 | (nil, arg.tree(trace: trace)) 63 | }) 64 | case let .let(name: name, value: value, in: body): 65 | return Tree(id: id, label: .init(Text(".let").keyword + Text("(\(name))"), state: state), children: [ 66 | ("value", value.tree(trace: trace)), 67 | ("body", body.tree(trace: trace)) 68 | ]) 69 | case let .tag(name: name, body: body): 70 | return Tree(id: id, label: .init(Text(".tag").keyword + Text("(\(name))"), state: state), children: body.map { 71 | (nil, $0.tree(trace: trace)) 72 | }) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/TemplateTests/ParsingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Template 3 | 4 | final class TemplateTests: XCTestCase { 5 | func expectParseError(_ f: @autoclosure () throws -> A, check: (ParseError) -> ()) { 6 | do { 7 | _ = try f() 8 | XCTFail() 9 | } catch { 10 | check(error as! ParseError) 11 | } 12 | } 13 | 14 | override func tearDown() { 15 | input = nil 16 | } 17 | var input: String! 18 | func parsed(file: StaticString = #file, line: UInt = #line) throws -> SimpleExpression { 19 | do { 20 | return try input.parse().simplify 21 | } catch { 22 | let e = error as! ParseError 23 | let lineRange = input!.lineRange(for: e.position..{ foo } 81 | """ 82 | try XCTAssertEqual(parsed(), .tag(name: "title", body: [.variable("foo")])) 83 | } 84 | 85 | // MARK: Parse failures 86 | func testEmpty() throws { 87 | let input = """ 88 | """ 89 | expectParseError(try input.parse(), check: { err in 90 | XCTAssert(err.reason == .unexpectedEOF) 91 | }) 92 | } 93 | 94 | // MARK: Parse failures 95 | func testGarbage() throws { 96 | let input = """ 97 | * 98 | """ 99 | expectParseError(try input.parse(), check: { err in 100 | XCTAssert(err.reason == .expectedAtom) 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/uikonf2020/TreeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 15.05.20. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import Template 11 | 12 | extension Color { 13 | static let background: Self = Color.black // (NSColor.windowBackgroundColor) 14 | } 15 | 16 | struct Collect: PreferenceKey { 17 | static var defaultValue: [A] { [] } 18 | static func reduce(value: inout [A], nextValue: () -> [A]) { 19 | value.append(contentsOf: nextValue()) 20 | } 21 | } 22 | 23 | struct CollectDict: PreferenceKey { 24 | static var defaultValue: [Key:Value] { [:] } 25 | static func reduce(value: inout [Key:Value], nextValue: () -> [Key:Value]) { 26 | value.merge(nextValue(), uniquingKeysWith: { $1 }) 27 | } 28 | } 29 | 30 | /// Draws an edge from `from` to `to` 31 | struct EdgeShape: Shape { 32 | var from: CGPoint 33 | var to: CGPoint 34 | var animatableData: AnimatablePair { 35 | get { AnimatablePair(from.animatableData, to.animatableData) } 36 | set { 37 | from.animatableData = newValue.first 38 | to.animatableData = newValue.second 39 | } 40 | } 41 | 42 | func path(in rect: CGRect) -> Path { 43 | Path { p in 44 | p.move(to: self.from) 45 | p.addLine(to: self.to) 46 | } 47 | } 48 | } 49 | 50 | /// A simple Diagram. It's not very performant yet, but works great for smallish trees. 51 | struct Diagram: View { 52 | let tree: Tree 53 | var strokeWidth: CGFloat = 1 54 | var node: (A) -> V 55 | 56 | init(tree: Tree, strokeWidth: CGFloat = 1, node: @escaping (A) -> V) { 57 | self.tree = tree 58 | self.strokeWidth = strokeWidth 59 | self.node = node 60 | } 61 | 62 | private typealias Key = CollectDict.ID, Anchor> 63 | 64 | var body: some View { 65 | return VStack(alignment: .center, spacing: 30) { 66 | node(tree.label) 67 | .anchorPreference(key: Key.self, value: .center, transform: { 68 | [self.tree.id: $0] 69 | }) 70 | HStack(alignment: .top, spacing: 15) { 71 | ForEach(tree.children, id: \.1.id, content: { child in 72 | Diagram(tree: child.1, strokeWidth: self.strokeWidth, node: self.node) 73 | .overlay(Group { 74 | if false && child.0 != nil { 75 | Text(child.0!).font(.caption).background(Color.background) 76 | .offset(y: -20) 77 | } 78 | }, alignment: .top) 79 | }) 80 | } 81 | }.backgroundPreferenceValue(Key.self, { (centers: [Tree.ID: Anchor]) in 82 | GeometryReader { proxy in 83 | ForEach(self.tree.children, id: \.1.id, content: { 84 | child in 85 | EdgeShape(from: 86 | proxy[centers[self.tree.id]!], 87 | to: proxy[centers[child.value.id]!]) 88 | .stroke(lineWidth: self.strokeWidth) 89 | }) 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Sources/Step1Evaluation/Evaluation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Value: Hashable { 11 | case string(String) 12 | case int(Int) 13 | case function(parameters: [String], body: Expression) 14 | case html(String) 15 | } 16 | 17 | extension Value { 18 | public var pretty: String { 19 | switch self { 20 | case .string(let s): return "\"\(s)\"" 21 | case .int(let i): return "\(i)" 22 | case .function: 23 | return "\(self)" 24 | case .html(let str): return str 25 | } 26 | } 27 | } 28 | 29 | extension Expression { 30 | public func run() throws -> Value { 31 | try EvaluationContext().evaluate(self) 32 | } 33 | } 34 | 35 | public struct EvaluationError: Error, Hashable { 36 | public enum Reason: Hashable { 37 | case variableMissing(name: String) 38 | case expectedFunction(got: Value) 39 | case wrongNumberOfArguments(expected: Int, got: Int) 40 | case typeError(String) 41 | } 42 | public var reason: Reason 43 | } 44 | 45 | struct EvaluationContext { 46 | var context: [String: Value] = [:] 47 | 48 | func evaluate(_ expression: Expression) throws -> Value { 49 | switch expression { 50 | case .intLiteral(let value): 51 | return .int(value) 52 | case let .stringLiteral(value): 53 | return .string(value) 54 | case .variable(let v): 55 | guard let value = context[v] else { 56 | throw EvaluationError(reason: .variableMissing(name: v)) 57 | } 58 | return value 59 | case .function(parameters: let parameters, body: let body): 60 | return .function(parameters: parameters, body: body) 61 | case .let(name: let name, value: let value, in: let body): 62 | let v = try evaluate(value) 63 | var nestedContext = self 64 | nestedContext.context[name] = v 65 | return try nestedContext.evaluate(body) 66 | case let .call(lhs, arguments: arguments): 67 | let l = try evaluate(lhs) 68 | guard case let .function(parameters, body) = l else { 69 | throw EvaluationError(reason: .expectedFunction(got: l)) 70 | } 71 | guard parameters.count == arguments.count else { 72 | throw EvaluationError(reason: .wrongNumberOfArguments(expected: parameters.count, got: arguments.count)) 73 | } 74 | let args = try arguments.map { try evaluate($0) } 75 | var nestedContext = self 76 | for (name, value) in zip(parameters, args) { 77 | nestedContext.context[name] = value 78 | } 79 | return try nestedContext.evaluate(body) 80 | 81 | case .tag(name: let name, body: let body): 82 | var result = "<\(name)>" 83 | for b in body { 84 | let value = try evaluate(b) 85 | switch value { 86 | case let .html(str): 87 | result.append(str) 88 | case let .string(str): 89 | result.append(str.htmlEscaped) 90 | default: 91 | throw EvaluationError(reason: .typeError("Expected html or string, but got \(value)")) 92 | } 93 | } 94 | result.append("") 95 | return .html(result) 96 | } 97 | } 98 | } 99 | 100 | extension String { 101 | var htmlEscaped: String { 102 | return self 103 | .replacingOccurrences(of: "&", with: "&") 104 | .replacingOccurrences(of: "<", with: "<") 105 | .replacingOccurrences(of: ">", with: ">") 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/Template/Template.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | indirect public enum Expression { 11 | case variable(String) 12 | case intLiteral(Int) 13 | case stringLiteral(String) 14 | case function(parameters: [String], body: R) 15 | case call(R, arguments: [R]) 16 | case `let`(name: String, value: R, in: R) 17 | case tag(name: String, body: [R]) 18 | } 19 | 20 | extension Expression: Equatable where R: Equatable { } 21 | extension Expression: Hashable where R: Hashable { } 22 | 23 | public struct SourceRange: Hashable { 24 | public var startIndex: String.Index 25 | public var endIndex: String.Index 26 | } 27 | 28 | public struct AnnotatedExpression: Equatable, Hashable, Identifiable { 29 | public init(_ range: SourceRange, _ expression: Expression) { 30 | self.range = range 31 | self.expression = expression 32 | } 33 | 34 | public let id = UUID() 35 | public let range: SourceRange 36 | public let expression: Expression 37 | 38 | } 39 | 40 | public struct SimpleExpression: Hashable, CustomStringConvertible { 41 | public let expression: Expression 42 | public init(_ expression: Expression) { 43 | self.expression = expression 44 | } 45 | 46 | public var description: String { 47 | return "\(expression)" 48 | } 49 | } 50 | 51 | public extension SimpleExpression { 52 | typealias R = Self 53 | static func variable(_ string: String) -> Self { 54 | return Self(.variable(string)) 55 | } 56 | static func intLiteral(_ int: Int) -> Self { 57 | return Self(.intLiteral(int)) 58 | } 59 | static func stringLiteral(_ string: String) -> Self { 60 | return Self(.stringLiteral(string)) 61 | } 62 | static func function(parameters: [String], body: R) -> Self { 63 | return Self(.function(parameters: parameters, body: body)) 64 | } 65 | static func call(_ f: R, arguments: [R]) -> Self { 66 | return Self(.call(f, arguments: arguments)) 67 | } 68 | static func `let`(name: String, value: R, in body: R) -> Self { 69 | return Self(.let(name: name, value: value, in: body)) 70 | } 71 | 72 | static func tag(name: String, attributes: [String:Self] = [:], body: [Self] = []) -> Self { 73 | return Self(.tag(name: name, body: body)) 74 | } 75 | } 76 | 77 | extension Expression { 78 | public func map(_ transform: (R) -> B) -> Expression { 79 | switch self { 80 | case let .variable(x): 81 | return .variable(x) 82 | case let .intLiteral(int: int): 83 | return .intLiteral(int) 84 | case let .stringLiteral(str): 85 | return .stringLiteral(str) 86 | case let .function(parameters: parameters, body: body): 87 | return Expression.function(parameters: parameters, body: transform(body)) 88 | case let .call(f, arguments: arguments): 89 | return .call(transform(f), arguments: arguments.map(transform)) 90 | case .let(name: let name, value: let value, in: let body): 91 | return .let(name: name, value: transform(value), in: transform(body)) 92 | case .tag(name: let name, body: let body): 93 | return .tag(name: name, body: body.map(transform)) 94 | } 95 | } 96 | } 97 | 98 | extension AnnotatedExpression { 99 | public var simplify: SimpleExpression { 100 | return SimpleExpression(expression.map { $0.simplify }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Sources/Step2Annotation/Template.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | indirect public enum Expression { 11 | case variable(String) 12 | case intLiteral(Int) 13 | case stringLiteral(String) 14 | case function(parameters: [String], body: R) 15 | case call(R, arguments: [R]) 16 | case `let`(name: String, value: R, in: R) 17 | case tag(name: String, body: [R]) 18 | } 19 | 20 | extension Expression: Equatable where R: Equatable { } 21 | extension Expression: Hashable where R: Hashable { } 22 | 23 | public struct SourceRange: Hashable { 24 | public var startIndex: String.Index 25 | public var endIndex: String.Index 26 | } 27 | 28 | public struct AnnotatedExpression: Equatable, Hashable { 29 | public init(_ range: SourceRange, _ expression: Expression) { 30 | self.range = range 31 | self.expression = expression 32 | } 33 | 34 | public let id = UUID() 35 | public let range: SourceRange 36 | public let expression: Expression 37 | } 38 | 39 | // Useful for testing 40 | 41 | public struct SimpleExpression: Hashable, CustomStringConvertible { 42 | public let expression: Expression 43 | public init(_ expression: Expression) { 44 | self.expression = expression 45 | } 46 | 47 | public var description: String { 48 | return "\(expression)" 49 | } 50 | } 51 | 52 | public extension SimpleExpression { 53 | typealias R = Self 54 | static func variable(_ string: String) -> Self { 55 | return Self(.variable(string)) 56 | } 57 | static func intLiteral(_ int: Int) -> Self { 58 | return Self(.intLiteral(int)) 59 | } 60 | static func stringLiteral(_ string: String) -> Self { 61 | return Self(.stringLiteral(string)) 62 | } 63 | static func function(parameters: [String], body: R) -> Self { 64 | return Self(.function(parameters: parameters, body: body)) 65 | } 66 | static func call(_ f: R, arguments: [R]) -> Self { 67 | return Self(.call(f, arguments: arguments)) 68 | } 69 | static func `let`(name: String, value: R, in body: R) -> Self { 70 | return Self(.let(name: name, value: value, in: body)) 71 | } 72 | 73 | static func tag(name: String, attributes: [String:Self] = [:], body: [Self] = []) -> Self { 74 | return Self(.tag(name: name, body: body)) 75 | } 76 | } 77 | 78 | extension Expression { 79 | public func map(_ transform: (R) -> B) -> Expression { 80 | switch self { 81 | case let .variable(x): 82 | return .variable(x) 83 | case let .intLiteral(int: int): 84 | return .intLiteral(int) 85 | case let .stringLiteral(str): 86 | return .stringLiteral(str) 87 | case let .function(parameters: parameters, body: body): 88 | return Expression.function(parameters: parameters, body: transform(body)) 89 | case let .call(f, arguments: arguments): 90 | return .call(transform(f), arguments: arguments.map(transform)) 91 | case .let(name: let name, value: let value, in: let body): 92 | return .let(name: name, value: transform(value), in: transform(body)) 93 | case .tag(name: let name, body: let body): 94 | return .tag(name: name, body: body.map(transform)) 95 | } 96 | } 97 | } 98 | 99 | extension AnnotatedExpression { 100 | public var simplify: SimpleExpression { 101 | return SimpleExpression(expression.map { $0.simplify }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/uikonf2020/Boilerplate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 01.04.20. 6 | // 7 | 8 | import Cocoa 9 | import SwiftUI 10 | 11 | extension NSApplication { 12 | var customMenu: NSMenu { 13 | let appMenu = NSMenuItem() 14 | appMenu.submenu = NSMenu() 15 | let appName = ProcessInfo.processInfo.processName 16 | appMenu.submenu?.addItem(NSMenuItem(title: "About \(appName)", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: "")) 17 | appMenu.submenu?.addItem(NSMenuItem.separator()) 18 | let services = NSMenuItem(title: "Services", action: nil, keyEquivalent: "") 19 | self.servicesMenu = NSMenu() 20 | services.submenu = self.servicesMenu 21 | appMenu.submenu?.addItem(services) 22 | appMenu.submenu?.addItem(NSMenuItem.separator()) 23 | appMenu.submenu?.addItem(NSMenuItem(title: "Hide \(appName)", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h")) 24 | let hideOthers = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h") 25 | hideOthers.keyEquivalentModifierMask = [.command, .option] 26 | appMenu.submenu?.addItem(hideOthers) 27 | appMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: "")) 28 | appMenu.submenu?.addItem(NSMenuItem.separator()) 29 | appMenu.submenu?.addItem(NSMenuItem(title: "Quit \(appName)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) 30 | 31 | let windowMenu = NSMenuItem() 32 | windowMenu.submenu = NSMenu(title: "Window") 33 | windowMenu.submenu?.addItem(NSMenuItem(title: "Minmize", action: #selector(NSWindow.miniaturize(_:)), keyEquivalent: "m")) 34 | windowMenu.submenu?.addItem(NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: "")) 35 | windowMenu.submenu?.addItem(NSMenuItem.separator()) 36 | windowMenu.submenu?.addItem(NSMenuItem(title: "Show All", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: "m")) 37 | 38 | let mainMenu = NSMenu(title: "Main Menu") 39 | mainMenu.addItem(appMenu) 40 | mainMenu.addItem(windowMenu) 41 | return mainMenu 42 | } 43 | } 44 | 45 | 46 | @available(OSX 10.15, *) 47 | public func run(view: V) { 48 | let delegate = AppDelegate(view) 49 | 50 | // Inspired by https://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html 51 | let app = NSApplication.shared 52 | NSApp.setActivationPolicy(.regular) 53 | app.mainMenu = app.customMenu 54 | app.delegate = delegate 55 | app.run() 56 | } 57 | 58 | @available(OSX 10.15, *) 59 | class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate { 60 | init(_ contentView: V) { 61 | self.contentView = contentView 62 | 63 | } 64 | var window: NSWindow! 65 | var hostingView: NSView? 66 | var contentView: V 67 | 68 | func applicationDidFinishLaunching(_ notification: Notification) { 69 | 70 | // Create the window and set the content view. 71 | window = NSWindow( 72 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), 73 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 74 | backing: .buffered, defer: false) 75 | window.center() 76 | window.setFrameAutosaveName("Main Window") 77 | hostingView = NSHostingView(rootView: contentView) 78 | window.contentView = hostingView 79 | window.makeKeyAndOrderFront(nil) 80 | window.delegate = self 81 | 82 | NSApp.activate(ignoringOtherApps: true) 83 | } 84 | 85 | func windowWillClose(_ notification: Notification) { 86 | NSApplication.shared.terminate(nil) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Step2Annotation/Evaluation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Value: Hashable { 11 | case string(String) 12 | case int(Int) 13 | case function(parameters: [String], body: AnnotatedExpression) 14 | case html(String) 15 | } 16 | 17 | extension Value { 18 | public var pretty: String { 19 | switch self { 20 | case .string(let s): return "\"\(s)\"" 21 | case .int(let i): return "\(i)" 22 | case let .function(parameters: params, body: body): 23 | let e = SimpleExpression(.function(parameters: params, body: body.simplify)) 24 | return "\(e)" 25 | case .html(let str): return str 26 | } 27 | } 28 | } 29 | 30 | extension AnnotatedExpression { 31 | public func run() throws -> Value { 32 | try EvaluationContext().evaluate(self) 33 | } 34 | } 35 | 36 | public struct EvaluationError: Error, Hashable { 37 | public enum Reason: Hashable { 38 | case variableMissing(name: String) 39 | case expectedFunction(got: Value) 40 | case wrongNumberOfArguments(expected: Int, got: Int) 41 | case typeError(String) 42 | } 43 | public var position: SourceRange 44 | public var reason: Reason 45 | } 46 | 47 | struct EvaluationContext { 48 | var context: [String: Value] = [:] 49 | 50 | func evaluate(_ expression: AnnotatedExpression) throws -> Value { 51 | switch expression.expression { 52 | case .intLiteral(let value): 53 | return .int(value) 54 | case let .stringLiteral(value): 55 | return .string(value) 56 | case .variable(let v): 57 | guard let value = context[v] else { 58 | throw EvaluationError(position: expression.range, reason: .variableMissing(name: v)) 59 | } 60 | return value 61 | case .function(parameters: let parameters, body: let body): 62 | return .function(parameters: parameters, body: body) 63 | case .let(name: let name, value: let value, in: let body): 64 | let v = try evaluate(value) 65 | var nestedContext = self 66 | nestedContext.context[name] = v 67 | return try nestedContext.evaluate(body) 68 | case let .call(lhs, arguments: arguments): 69 | let l = try evaluate(lhs) 70 | guard case let .function(parameters, body) = l else { 71 | throw EvaluationError(position: expression.range, reason: .expectedFunction(got: l)) 72 | } 73 | guard parameters.count == arguments.count else { 74 | throw EvaluationError(position: expression.range, reason: .wrongNumberOfArguments(expected: parameters.count, got: arguments.count)) 75 | } 76 | let args = try arguments.map { try evaluate($0) } 77 | var nestedContext = self 78 | for (name, value) in zip(parameters, args) { 79 | nestedContext.context[name] = value 80 | } 81 | return try nestedContext.evaluate(body) 82 | 83 | case .tag(name: let name, body: let body): 84 | var result = "<\(name)>" 85 | for b in body { 86 | let value = try evaluate(b) 87 | switch value { 88 | case let .html(str): 89 | result.append(str) 90 | case let .string(str): 91 | result.append(str.htmlEscaped) 92 | default: 93 | throw EvaluationError(position: b.range, reason: .typeError("Expected html or string, but got \(value)")) 94 | } 95 | } 96 | result.append("") 97 | return .html(result) 98 | } 99 | } 100 | } 101 | 102 | extension String { 103 | var htmlEscaped: String { 104 | return self 105 | .replacingOccurrences(of: "&", with: "&") 106 | .replacingOccurrences(of: "<", with: "<") 107 | .replacingOccurrences(of: ">", with: ">") 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/uikonf2020/Views.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 15.05.20. 6 | // 7 | 8 | import SwiftUI 9 | import Template 10 | 11 | extension Result { 12 | var isError: Bool { 13 | guard case .failure = self else { return false } 14 | return true 15 | } 16 | } 17 | 18 | extension ExpressionNode { 19 | func view(showValue: Bool) -> some View { 20 | var value: Value? = nil 21 | var err: String? = nil 22 | let color: Color 23 | switch self.state { 24 | case .none: color = .gray 25 | case .inProgress: 26 | color = .blue 27 | case .done(let v): 28 | color = v.isError ? . red : .green 29 | switch v { 30 | case let .failure(e): err = "\(e.reason)" 31 | case let .success(x): value = x 32 | } 33 | } 34 | 35 | return VStack { 36 | label.padding(10).background( 37 | Capsule() 38 | .fill(color) 39 | ) 40 | if showValue && value != nil { 41 | value!.minimalPretty.background(Color.background) 42 | } 43 | if showValue && err != nil { 44 | Text(err!).foregroundColor(.red).background(Color.background) 45 | } 46 | } 47 | } 48 | } 49 | 50 | extension Value { 51 | var minimalPretty: Text { 52 | switch self { 53 | case .string(let s): 54 | return Text(s).italic() 55 | case .int(let i): 56 | return Text("\(i)") 57 | case .function: 58 | return Text("").keyword 59 | case .html(let text): 60 | return Text(text).font(.system(.caption, design: .monospaced)) 61 | } 62 | } 63 | } 64 | 65 | extension Array where Element == Trace { 66 | var states: [UUID:ExpressionNode.State] { 67 | var result: [UUID:ExpressionNode.State] = [:] 68 | for t in self { 69 | switch t { 70 | case .start(let id, let context): result[id] = .inProgress(context: context) 71 | case let .end(id, value: value): result[id] = .done(value) 72 | } 73 | } 74 | return result 75 | } 76 | } 77 | 78 | struct TreeView: View { 79 | // var tree: Tree 80 | @State var source = "" 81 | @State var s = "" 82 | @State var result: Result? = nil 83 | var tree: Tree? { 84 | return try? result?.map { $0.tree(trace: traceForCurrentStep ?? [:]) }.get() 85 | } 86 | @State var value: (Result, [Trace])? = nil 87 | 88 | var trace: [Trace]? { 89 | value?.1 90 | } 91 | 92 | var traceForCurrentStep: [UUID:ExpressionNode.State]? { 93 | return trace.map { Array($0.prefix(step)) }?.states 94 | } 95 | 96 | var error: String? { 97 | guard case let .failure(f) = result else { return nil } 98 | if let p = f as? PrettyError { 99 | return p.string 100 | } else { 101 | return "\(f)" 102 | } 103 | } 104 | 105 | @State var step = 0 106 | @State var showValue = false 107 | 108 | var body: some View { 109 | VStack { 110 | if tree != nil { 111 | Diagram(tree: tree!, node: { n in 112 | n.view(showValue: self.showValue) 113 | .animation(.default) 114 | }).padding() 115 | } else if !source.isEmpty { 116 | Text(error!).font(.system(.body, design: .monospaced)) 117 | } 118 | Spacer() 119 | if trace != nil { 120 | HStack { 121 | Stepper("Step \(step)/\(trace!.count)", value: $step, in: 0...trace!.count) 122 | Toggle(isOn: $showValue, label: { Text("Show Values")}) 123 | } 124 | } 125 | 126 | TextField("Title", text: $s, onCommit: { 127 | self.source = self.s 128 | self.result = Result { try parse(self.source) } 129 | self.value = (try? self.result?.get()).map { parsed in 130 | parsed.run() 131 | } 132 | self.step = 0 133 | }).font(.system(.body, design: .monospaced)) 134 | }.padding(50) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Sources/Template/Evaluation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Value: Hashable { 11 | case string(String) 12 | case int(Int) 13 | case function(parameters: [String], body: AnnotatedExpression) 14 | case html(String) 15 | } 16 | 17 | extension Value { 18 | public var pretty: String { 19 | switch self { 20 | case .string(let s): return "\"\(s)\"" 21 | case .int(let i): return "\(i)" 22 | case let .function(parameters: params, body: body): 23 | let e = SimpleExpression(.function(parameters: params, body: body.simplify)) 24 | return "\(e)" 25 | case .html(let str): return str 26 | } 27 | } 28 | } 29 | 30 | extension AnnotatedExpression { 31 | public func run() -> (Result, [Trace]) { 32 | EvaluationContext().evaluate(self) 33 | } 34 | } 35 | 36 | public struct EvaluationError: Error, Hashable { 37 | public enum Reason: Hashable { 38 | case variableMissing(name: String) 39 | case expectedFunction(got: Value) 40 | case wrongNumberOfArguments(expected: Int, got: Int) 41 | case typeError(String) 42 | } 43 | public var position: SourceRange 44 | public var reason: Reason 45 | } 46 | 47 | public enum Trace: Hashable { 48 | case start(UUID, context: [String:Value]) 49 | case end(UUID, value: Result) 50 | } 51 | 52 | struct EvaluationContext { 53 | var context: [String: Value] = [:] 54 | 55 | func evaluate(_ expression: AnnotatedExpression) -> (Result, [Trace]) { 56 | var t: [Trace] = [] 57 | 58 | func run(context: EvaluationContext, _ e: AnnotatedExpression) throws -> Value { 59 | t.append(.start(e.id, context: context.context)) 60 | let value = Result { try context._evaluate(e, evaluate: run) }.mapError { $0 as! EvaluationError } 61 | t.append(.end(e.id, value: value)) 62 | return try value.get() 63 | } 64 | let result = Result { try run(context: self, expression) }.mapError { $0 as! EvaluationError } 65 | return (result, t) 66 | } 67 | 68 | func _evaluate(_ expression: AnnotatedExpression, evaluate: (Self, AnnotatedExpression) throws -> Value) throws -> Value { 69 | switch expression.expression { 70 | case .intLiteral(let value): 71 | return .int(value) 72 | case let .stringLiteral(value): 73 | return .string(value) 74 | case .variable(let v): 75 | guard let value = context[v] else { 76 | throw EvaluationError(position: expression.range, reason: .variableMissing(name: v)) 77 | } 78 | return value 79 | case .function(parameters: let parameters, body: let body): 80 | return .function(parameters: parameters, body: body) 81 | case .let(name: let name, value: let value, in: let body): 82 | let v = try evaluate(self, value) 83 | var nestedContext = self 84 | nestedContext.context[name] = v 85 | return try evaluate(nestedContext, body) 86 | case let .call(lhs, arguments: arguments): 87 | let l = try evaluate(self, lhs) 88 | guard case let .function(parameters, body) = l else { 89 | throw EvaluationError(position: expression.range, reason: .expectedFunction(got: l)) 90 | } 91 | guard parameters.count == arguments.count else { 92 | throw EvaluationError(position: expression.range, reason: .wrongNumberOfArguments(expected: parameters.count, got: arguments.count)) 93 | } 94 | let args = try arguments.map { try evaluate(self, $0) } 95 | var nestedContext = self 96 | for (name, value) in zip(parameters, args) { 97 | nestedContext.context[name] = value 98 | } 99 | return try evaluate(nestedContext, body) 100 | 101 | case .tag(name: let name, body: let body): 102 | var result = "<\(name)>" 103 | for b in body { 104 | let value = try evaluate(self, b) 105 | switch value { 106 | case let .html(str): 107 | result.append(str) 108 | case let .string(str): 109 | result.append(str.htmlEscaped) 110 | default: 111 | throw EvaluationError(position: b.range, reason: .typeError("Expected html or string, but got \(value)")) 112 | } 113 | } 114 | result.append("") 115 | return .html(result) 116 | } 117 | } 118 | } 119 | 120 | extension String { 121 | var htmlEscaped: String { 122 | return self 123 | .replacingOccurrences(of: "&", with: "&") 124 | .replacingOccurrences(of: "<", with: "<") 125 | .replacingOccurrences(of: ">", with: ">") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Sources/Step0Parsing/Parsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Substring { 11 | var position: Index { startIndex } 12 | 13 | mutating func remove(while cond: (Element) -> Bool) -> SubSequence { 14 | var p = position 15 | while p < endIndex, cond(self[p]) { 16 | formIndex(after: &p) 17 | } 18 | let result = self[position..(prefix: S) -> Bool where S: Collection, S.Element == Element { 24 | guard starts(with: prefix) else { return false } 25 | removeFirst(prefix.count) 26 | return true 27 | } 28 | 29 | mutating func remove(keyword: String) -> Bool { 30 | guard hasPrefix(keyword) else { return false } 31 | let index = self.index(startIndex, offsetBy: keyword.count) 32 | guard index < endIndex, !self[index].isIdentifier else { return false } 33 | _ = remove(prefix: keyword) 34 | return true 35 | } 36 | 37 | mutating func skipWS() { 38 | _ = remove(while: { $0.isWhitespace }) 39 | } 40 | } 41 | 42 | extension Substring { 43 | func err(_ reason: Reason) -> ParseError { 44 | ParseError(position: position, reason: reason) 45 | } 46 | 47 | mutating func parseExpression() throws -> Expression { 48 | return try parseDefinition() 49 | } 50 | 51 | mutating func parseDefinition() throws -> Expression { 52 | guard self.remove(keyword: "let") else { return try parseFunctionCall() } 53 | skipWS() 54 | let name = try parseIdentifier() 55 | skipWS() 56 | guard self.parse(operator: "=") else { 57 | throw err(Reason.expected("=")) 58 | } 59 | skipWS() 60 | let value = try parseExpression() 61 | skipWS() 62 | guard self.remove(keyword: "in") else { 63 | throw err(Reason.expectedKeyword("in")) 64 | } 65 | skipWS() 66 | let body = try parseExpression() 67 | return .let(name: name, value: value, in: body) 68 | } 69 | 70 | mutating func parseFunctionCall() throws -> Expression { 71 | var result = try parseAtom() 72 | while remove(prefix: "(") { 73 | skipWS() 74 | var arguments: [Expression] = [] 75 | while let f = first, f != ")" { 76 | arguments.append(try parseExpression()) 77 | skipWS() 78 | if !remove(prefix: ",") { 79 | break 80 | } 81 | skipWS() 82 | } 83 | 84 | guard remove(prefix: ")") else { 85 | throw err(.expected(")")) 86 | } 87 | result = .call(result, arguments: arguments) 88 | } 89 | return result 90 | } 91 | 92 | mutating func parseFunc() throws -> Expression { 93 | guard remove(keyword: "func") else { return try parseAtom() } 94 | skipWS() 95 | var parameters: [String] = [] 96 | guard remove(prefix: "(") else { throw err(.expected("(")) } 97 | while !remove(prefix: ")") { 98 | let identifier = try parseIdentifier() 99 | parameters.append(identifier) 100 | guard remove(prefix: ",") || first == ")" else { 101 | throw err(.expected(", or )")) 102 | } 103 | skipWS() 104 | } 105 | skipWS() 106 | guard remove(prefix: "{") else { throw err(Reason.expected("{")) } 107 | skipWS() 108 | let body = try parseExpression() 109 | skipWS() 110 | guard remove(prefix: "}") else { 111 | throw err(Reason.expected("}")) 112 | } 113 | return .function(parameters: parameters, body: body) 114 | } 115 | 116 | mutating func parseAtom() throws -> Expression { 117 | if let p = first { 118 | if p.isDecimalDigit { 119 | let int = parseInt() 120 | return .intLiteral(int) 121 | } else if p == "\"" { 122 | removeFirst() 123 | let value = remove(while: { $0 != "\"" }) // todo escaping 124 | guard remove(prefix: "\"") else { 125 | throw err(Reason.expected("\"")) 126 | } 127 | return .stringLiteral(String(value)) 128 | } else if p.isIdentifierStart { 129 | let name = try parseIdentifier() 130 | return .variable(name) 131 | } else if p == "<" { 132 | return try parseTag() 133 | } else { 134 | throw err(.expectedAtom) 135 | } 136 | } 137 | throw err(.unexpectedEOF) 138 | } 139 | 140 | mutating func parseInt() -> Int { 141 | return Int(String(remove(while: { $0.isDecimalDigit })))! 142 | } 143 | 144 | mutating func parseTag() throws -> Expression { 145 | if remove(prefix: "<") { 146 | skipWS() 147 | let name = try parseIdentifier() 148 | guard remove(prefix: ">") else { 149 | throw err(Reason.expected(">")) 150 | } 151 | skipWS() 152 | var body: [Expression] = [] 153 | while !remove(prefix: "") { 154 | try body.append(parseTag()) 155 | } 156 | return .tag(name: name, body: body) 157 | } else if remove(prefix: "{") { 158 | skipWS() 159 | let result = try parseExpression() 160 | skipWS() 161 | guard remove(prefix: "}") else { 162 | throw err(Reason.expected("}")) 163 | } 164 | return result 165 | } else { 166 | throw err(Reason.expected("{ or <")) 167 | } 168 | } 169 | 170 | mutating func parseIdentifier() throws -> String { 171 | let name = remove(while: { $0.isIdentifier }) 172 | guard !name.isEmpty else { 173 | throw err(.expectedIdentifier) 174 | } 175 | return String(name) 176 | } 177 | 178 | mutating func parseOperator() throws -> String { 179 | let name = remove(while: { $0.isOperator }) 180 | guard !name.isEmpty else { 181 | throw err(.expectedOperator) 182 | } 183 | return String(name) 184 | } 185 | 186 | mutating func parse(operator expected: String) -> Bool { 187 | var copy = self 188 | do { 189 | let op = try copy.parseOperator() 190 | guard op == expected else { return false } 191 | self = copy 192 | return true 193 | } catch { 194 | return false 195 | } 196 | } 197 | } 198 | 199 | extension Character { 200 | var isDecimalDigit: Bool { 201 | return isHexDigit && hexDigitValue! < 10 202 | } 203 | 204 | var isOperator: Bool { 205 | return self == "=" // todo 206 | } 207 | 208 | var isIdentifierStart: Bool { 209 | return isLetter 210 | } 211 | 212 | var isIdentifier: Bool { 213 | return isLetter || self == "_" 214 | } 215 | } 216 | 217 | public struct ParseError: Error, Hashable { 218 | public var position: String.Index 219 | public var reason: Reason 220 | } 221 | 222 | public enum Reason: Hashable { 223 | case unexpectedEOF 224 | case expectedAtom 225 | case expectedIdentifier 226 | case expectedOperator 227 | case expectedKeyword(String) 228 | case expected(String) 229 | case unexpectedRemainder(String) 230 | } 231 | 232 | extension String { 233 | public func parse() throws -> Expression { 234 | var remainder = self[...] 235 | let result = try remainder.parseExpression() 236 | guard remainder.isEmpty else { 237 | throw remainder.err(.unexpectedRemainder(String(remainder))) 238 | } 239 | return result 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Sources/Step1Evaluation/Parsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Substring { 11 | var position: Index { startIndex } 12 | 13 | mutating func remove(while cond: (Element) -> Bool) -> SubSequence { 14 | var p = position 15 | while p < endIndex, cond(self[p]) { 16 | formIndex(after: &p) 17 | } 18 | let result = self[position..(prefix: S) -> Bool where S: Collection, S.Element == Element { 24 | guard starts(with: prefix) else { return false } 25 | removeFirst(prefix.count) 26 | return true 27 | } 28 | 29 | mutating func remove(keyword: String) -> Bool { 30 | guard hasPrefix(keyword) else { return false } 31 | let index = self.index(startIndex, offsetBy: keyword.count) 32 | guard index < endIndex, !self[index].isIdentifier else { return false } 33 | _ = remove(prefix: keyword) 34 | return true 35 | } 36 | 37 | var remainder: String { 38 | return String(self) 39 | } 40 | 41 | mutating func skipWS() { 42 | _ = remove(while: { $0.isWhitespace }) 43 | } 44 | } 45 | 46 | extension Substring { 47 | func err(_ reason: Reason) -> ParseError { 48 | ParseError(position: position, reason: reason) 49 | } 50 | 51 | mutating func parseExpression() throws -> Expression { 52 | return try parseDefinition() 53 | } 54 | 55 | mutating func parseDefinition() throws -> Expression { 56 | guard self.remove(keyword: "let") else { return try parseFunctionCall() } 57 | skipWS() 58 | let name = try parseIdentifier() 59 | skipWS() 60 | guard self.parse(operator: "=") else { 61 | throw err(Reason.expected("=")) 62 | } 63 | skipWS() 64 | let value = try parseExpression() 65 | skipWS() 66 | guard self.remove(keyword: "in") else { 67 | throw err(Reason.expectedKeyword("in")) 68 | } 69 | skipWS() 70 | let body = try parseExpression() 71 | return .let(name: name, value: value, in: body) 72 | } 73 | 74 | mutating func parseFunctionCall() throws -> Expression { 75 | var result = try parseAtom() 76 | while remove(prefix: "(") { 77 | skipWS() 78 | var arguments: [Expression] = [] 79 | while let f = first, f != ")" { 80 | arguments.append(try parseExpression()) 81 | skipWS() 82 | if !remove(prefix: ",") { 83 | break 84 | } 85 | skipWS() 86 | } 87 | 88 | guard remove(prefix: ")") else { 89 | throw err(.expected(")")) 90 | } 91 | result = .call(result, arguments: arguments) 92 | } 93 | return result 94 | } 95 | 96 | 97 | mutating func parseAtom() throws -> Expression { 98 | if let p = first { 99 | if p.isDecimalDigit { 100 | let int = parseInt() 101 | return .intLiteral(int) 102 | } else if p == "\"" { 103 | removeFirst() 104 | let value = remove(while: { $0 != "\"" }) // todo escaping 105 | guard remove(prefix: "\"") else { 106 | throw err(Reason.expected("\"")) 107 | } 108 | return .stringLiteral(String(value)) 109 | } else if remove(keyword: "func") { 110 | skipWS() 111 | var parameters: [String] = [] 112 | guard remove(prefix: "(") else { throw err(.expected("(")) } 113 | while !remove(prefix: ")") { 114 | let identifier = try parseIdentifier() 115 | parameters.append(identifier) 116 | guard remove(prefix: ",") || first == ")" else { 117 | throw err(.expected(", or )")) 118 | } 119 | skipWS() 120 | } 121 | skipWS() 122 | guard remove(prefix: "{") else { throw err(Reason.expected("{")) } 123 | skipWS() 124 | let body = try parseExpression() 125 | skipWS() 126 | guard remove(prefix: "}") else { 127 | throw err(Reason.expected("}")) 128 | } 129 | return .function(parameters: parameters, body: body) 130 | } else if p.isIdentifierStart { 131 | let name = try parseIdentifier() 132 | return .variable(name) 133 | } else if p == "<" { 134 | return try parseTag() 135 | } else { 136 | throw err(.expectedAtom) 137 | } 138 | } 139 | throw err(.unexpectedEOF) 140 | } 141 | 142 | mutating func parseInt() -> Int { 143 | return Int(String(remove(while: { $0.isDecimalDigit })))! 144 | } 145 | 146 | mutating func parseTag() throws -> Expression { 147 | if remove(prefix: "<") { 148 | skipWS() 149 | let name = try parseIdentifier() 150 | guard remove(prefix: ">") else { 151 | throw err(Reason.expected(">")) 152 | } 153 | skipWS() 154 | var body: [Expression] = [] 155 | while !remove(prefix: "") { 156 | try body.append(parseTag()) 157 | } 158 | return .tag(name: name, body: body) 159 | } else if remove(prefix: "{") { 160 | skipWS() 161 | let result = try parseExpression() 162 | skipWS() 163 | guard remove(prefix: "}") else { 164 | throw err(Reason.expected("}")) 165 | } 166 | return result 167 | } else { 168 | throw err(Reason.expected("{ or <")) 169 | } 170 | } 171 | 172 | mutating func parseIdentifier() throws -> String { 173 | let name = remove(while: { $0.isIdentifier }) 174 | guard !name.isEmpty else { 175 | throw err(.expectedIdentifier) 176 | } 177 | return String(name) 178 | } 179 | 180 | mutating func parseOperator() throws -> String { 181 | let name = remove(while: { $0.isOperator }) 182 | guard !name.isEmpty else { 183 | throw err(.expectedOperator) 184 | } 185 | return String(name) 186 | } 187 | 188 | mutating func parse(operator expected: String) -> Bool { 189 | var copy = self 190 | do { 191 | let op = try copy.parseOperator() 192 | guard op == expected else { return false } 193 | self = copy 194 | return true 195 | } catch { 196 | return false 197 | } 198 | } 199 | } 200 | 201 | extension Character { 202 | var isDecimalDigit: Bool { 203 | return isHexDigit && hexDigitValue! < 10 204 | } 205 | 206 | var isOperator: Bool { 207 | return self == "=" // todo 208 | } 209 | 210 | var isIdentifierStart: Bool { 211 | return isLetter 212 | } 213 | 214 | var isIdentifier: Bool { 215 | return isLetter || self == "_" 216 | } 217 | } 218 | 219 | public struct ParseError: Error, Hashable { 220 | public var position: String.Index 221 | public var reason: Reason 222 | } 223 | 224 | public enum Reason: Hashable { 225 | case unexpectedEOF 226 | case expectedAtom 227 | case expectedIdentifier 228 | case expectedOperator 229 | case expectedKeyword(String) 230 | case expected(String) 231 | case unexpectedRemainder(String) 232 | } 233 | 234 | extension String { 235 | public func parse() throws -> Expression { 236 | var context = self[...] 237 | let result = try context.parseExpression() 238 | guard context.isEmpty else { 239 | throw context.err(.unexpectedRemainder(String(context))) 240 | } 241 | return result 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/uikonf2020.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 57 | 63 | 64 | 65 | 71 | 77 | 78 | 79 | 85 | 91 | 92 | 93 | 99 | 105 | 106 | 107 | 113 | 119 | 120 | 121 | 122 | 123 | 128 | 129 | 131 | 137 | 138 | 139 | 141 | 147 | 148 | 149 | 150 | 151 | 161 | 163 | 169 | 170 | 171 | 172 | 178 | 180 | 186 | 187 | 188 | 189 | 191 | 192 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /Sources/Template/Parsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Substring { 11 | var position: Index { startIndex } 12 | mutating func remove(while cond: (Element) -> Bool) -> SubSequence { 13 | var p = position 14 | while p < endIndex, cond(self[p]) { 15 | formIndex(after: &p) 16 | } 17 | let result = self[position..(prefix: S) -> Bool where S: Collection, S.Element == Element { 25 | guard starts(with: prefix) else { return false } 26 | removeFirst(prefix.count) 27 | return true 28 | } 29 | } 30 | 31 | extension Substring { 32 | var remainder: String { 33 | return String(self) 34 | } 35 | 36 | mutating func skipWS() { 37 | _ = remove(while: { $0.isWhitespace }) 38 | } 39 | 40 | func err(_ reason: Reason) -> ParseError { 41 | ParseError(position: position, reason: reason) 42 | } 43 | 44 | mutating func parseExpression() throws -> AnnotatedExpression { 45 | return try parseDefinition() 46 | } 47 | 48 | mutating func parseDefinition() throws -> AnnotatedExpression { 49 | let start = position 50 | guard self.remove(keyword: "let") else { return try parseFunctionCall() } 51 | skipWS() 52 | let name = try parseIdentifier() 53 | skipWS() 54 | guard self.parse(operator: "=") else { 55 | throw err(Reason.expected("=")) 56 | } 57 | skipWS() 58 | let value = try parseExpression() 59 | skipWS() 60 | guard self.remove(keyword: "in") else { 61 | throw err(Reason.expectedKeyword("in")) 62 | } 63 | skipWS() 64 | let body = try parseExpression() 65 | let end = position 66 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: end), .let(name: name, value: value, in: body)) 67 | } 68 | 69 | mutating func parseFunctionCall() throws -> AnnotatedExpression { 70 | var result = try parseFunc() 71 | while remove(prefix: "(") { 72 | let start = position 73 | skipWS() 74 | var arguments: [AnnotatedExpression] = [] 75 | while let f = first, f != ")" { 76 | arguments.append(try parseExpression()) 77 | skipWS() 78 | if !remove(prefix: ",") { 79 | break 80 | } 81 | skipWS() 82 | } 83 | 84 | guard remove(prefix: ")") else { 85 | throw err(.expected(")")) 86 | } 87 | result = AnnotatedExpression(SourceRange(startIndex: start, endIndex: position), .call(result, arguments: arguments)) 88 | } 89 | return result 90 | } 91 | 92 | mutating func parseFunc() throws -> AnnotatedExpression { 93 | let start = position 94 | guard remove(keyword: "func") else { return try parseAtom() } 95 | skipWS() 96 | var parameters: [String] = [] 97 | guard remove(prefix: "(") else { throw err(.expected("(")) } 98 | // Parse 0 or more identifiers separated by commas 99 | while !remove(prefix: ")") { 100 | let identifier = try parseIdentifier() 101 | parameters.append(identifier) 102 | guard remove(prefix: ",") || first == ")" else { 103 | throw err(.expected(", or )")) 104 | } 105 | skipWS() 106 | } 107 | skipWS() 108 | guard remove(prefix: "{") else { throw err(Reason.expected("{")) } 109 | skipWS() 110 | let body = try parseExpression() 111 | skipWS() 112 | guard remove(prefix: "}") else { 113 | throw err(Reason.expected("}")) 114 | } 115 | let end = position 116 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: end), .function(parameters: parameters, body: body)) 117 | } 118 | 119 | mutating func parseAtom() throws -> AnnotatedExpression { 120 | if let p = first { 121 | if p.isDecimalDigit { 122 | let (range, int) = try annotate { $0.parseInt() } 123 | return AnnotatedExpression(range, .intLiteral(int)) 124 | } else if p == "\"" { 125 | let start = position 126 | removeFirst() 127 | let value = remove(while: { $0 != "\"" }) // todo escaping 128 | guard remove(prefix: "\"") else { 129 | throw err(Reason.expected("\"")) 130 | } 131 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: position), .stringLiteral(String(value))) 132 | } else if p.isIdentifierStart { 133 | let (range, name) = try annotate { try $0.parseIdentifier() } 134 | return AnnotatedExpression(range, .variable(name)) 135 | } else if p == "<" { 136 | return try parseTag() 137 | } else { 138 | throw err(.expectedAtom) 139 | } 140 | } 141 | throw err(.unexpectedEOF) 142 | } 143 | 144 | mutating func parseInt() -> Int { 145 | return Int(String(remove(while: { $0.isDecimalDigit })))! 146 | } 147 | 148 | mutating func parseTag() throws -> AnnotatedExpression { 149 | let start = position 150 | if remove(prefix: "<") { 151 | skipWS() 152 | let name = try parseIdentifier() 153 | guard remove(prefix: ">") else { 154 | throw err(Reason.expected(">")) 155 | } 156 | skipWS() 157 | var body: [AnnotatedExpression] = [] 158 | while !remove(prefix: "") { 159 | try body.append(parseTag()) 160 | } 161 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: position), .tag(name: name, body: body)) 162 | } else if remove(prefix: "{") { 163 | skipWS() 164 | let result = try parseExpression() 165 | skipWS() 166 | guard remove(prefix: "}") else { 167 | throw err(Reason.expected("}")) 168 | } 169 | return result 170 | } else { 171 | throw err(Reason.expected("{ or <")) 172 | } 173 | } 174 | 175 | mutating func parseIdentifier() throws -> String { 176 | let name = remove(while: { $0.isIdentifier }) 177 | guard !name.isEmpty else { 178 | throw err(.expectedIdentifier) 179 | } 180 | return String(name) 181 | } 182 | 183 | mutating func parseOperator() throws -> String { 184 | let name = remove(while: { $0.isOperator }) 185 | guard !name.isEmpty else { 186 | throw err(.expectedOperator) 187 | } 188 | return String(name) 189 | } 190 | 191 | mutating func annotate(_ f: (inout Self) throws -> A) throws -> (SourceRange, A) { 192 | let start = position 193 | let result = try f(&self) 194 | let end = position 195 | return (SourceRange(startIndex: start, endIndex: end), result) 196 | } 197 | 198 | mutating func remove(keyword: String) -> Bool { 199 | guard hasPrefix(keyword) else { return false } 200 | let index = self.index(startIndex, offsetBy: keyword.count) 201 | guard index < endIndex, !self[index].isIdentifier else { return false } 202 | _ = remove(prefix: keyword) 203 | return true 204 | } 205 | 206 | 207 | mutating func parse(operator expected: String) -> Bool { 208 | var copy = self 209 | do { 210 | let op = try copy.parseOperator() 211 | guard op == expected else { return false } 212 | self = copy 213 | return true 214 | } catch { 215 | return false 216 | } 217 | } 218 | } 219 | 220 | extension Character { 221 | var isDecimalDigit: Bool { 222 | return isHexDigit && hexDigitValue! < 10 223 | } 224 | 225 | var isOperator: Bool { 226 | return self == "=" // todo 227 | } 228 | 229 | var isIdentifierStart: Bool { 230 | return isLetter 231 | } 232 | 233 | var isIdentifier: Bool { 234 | return isLetter || self == "_" 235 | } 236 | } 237 | 238 | public struct ParseError: Error, Hashable { 239 | public var position: String.Index 240 | public var reason: Reason 241 | } 242 | 243 | public enum Reason: Hashable { 244 | case unexpectedEOF 245 | case expectedAtom 246 | case expectedIdentifier 247 | case expectedOperator 248 | case expectedKeyword(String) 249 | case expected(String) 250 | case unexpectedRemainder(String) 251 | } 252 | 253 | extension String { 254 | public func parse() throws -> AnnotatedExpression { 255 | var context = self[...] 256 | let result = try context.parseExpression() 257 | guard context.isEmpty else { 258 | throw context.err(.unexpectedRemainder(String(context))) 259 | } 260 | return result 261 | } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /Sources/Step2Annotation/Parsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Chris Eidhof on 09.05.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Substring { 11 | var position: Index { startIndex } 12 | mutating func remove(while cond: (Element) -> Bool) -> SubSequence { 13 | var p = position 14 | while p < endIndex, cond(self[p]) { 15 | formIndex(after: &p) 16 | } 17 | let result = self[position..(prefix: S) -> Bool where S: Collection, S.Element == Element { 25 | guard starts(with: prefix) else { return false } 26 | removeFirst(prefix.count) 27 | return true 28 | } 29 | } 30 | 31 | extension Substring { 32 | var remainder: String { 33 | return String(self) 34 | } 35 | 36 | mutating func skipWS() { 37 | _ = remove(while: { $0.isWhitespace }) 38 | } 39 | 40 | func err(_ reason: Reason) -> ParseError { 41 | ParseError(position: position, reason: reason) 42 | } 43 | 44 | mutating func parseExpression() throws -> AnnotatedExpression { 45 | return try parseDefinition() 46 | } 47 | 48 | mutating func parseDefinition() throws -> AnnotatedExpression { 49 | let start = position 50 | guard self.remove(keyword: "let") else { return try parseFunctionCall() } 51 | skipWS() 52 | let name = try parseIdentifier() 53 | skipWS() 54 | guard self.parse(operator: "=") else { 55 | throw err(Reason.expected("=")) 56 | } 57 | skipWS() 58 | let value = try parseExpression() 59 | skipWS() 60 | guard self.remove(keyword: "in") else { 61 | throw err(Reason.expectedKeyword("in")) 62 | } 63 | skipWS() 64 | let body = try parseExpression() 65 | let end = position 66 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: end), .let(name: name, value: value, in: body)) 67 | } 68 | 69 | mutating func parseFunctionCall() throws -> AnnotatedExpression { 70 | var result = try parseFunc() 71 | while remove(prefix: "(") { 72 | let start = position 73 | skipWS() 74 | var arguments: [AnnotatedExpression] = [] 75 | while let f = first, f != ")" { 76 | arguments.append(try parseExpression()) 77 | skipWS() 78 | if !remove(prefix: ",") { 79 | break 80 | } 81 | skipWS() 82 | } 83 | 84 | guard remove(prefix: ")") else { 85 | throw err(.expected(")")) 86 | } 87 | result = AnnotatedExpression(SourceRange(startIndex: start, endIndex: position), .call(result, arguments: arguments)) 88 | } 89 | return result 90 | } 91 | 92 | mutating func parseFunc() throws -> AnnotatedExpression { 93 | let start = position 94 | guard remove(keyword: "func") else { return try parseAtom() } 95 | skipWS() 96 | var parameters: [String] = [] 97 | guard remove(prefix: "(") else { throw err(.expected("(")) } 98 | // Parse 0 or more identifiers separated by commas 99 | while !remove(prefix: ")") { 100 | let identifier = try parseIdentifier() 101 | parameters.append(identifier) 102 | guard remove(prefix: ",") || first == ")" else { 103 | throw err(.expected(", or )")) 104 | } 105 | skipWS() 106 | } 107 | skipWS() 108 | guard remove(prefix: "{") else { throw err(Reason.expected("{")) } 109 | skipWS() 110 | let body = try parseExpression() 111 | skipWS() 112 | guard remove(prefix: "}") else { 113 | throw err(Reason.expected("}")) 114 | } 115 | let end = position 116 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: end), .function(parameters: parameters, body: body)) 117 | } 118 | 119 | mutating func parseAtom() throws -> AnnotatedExpression { 120 | if let p = first { 121 | if p.isDecimalDigit { 122 | let (range, int) = try annotate { $0.parseInt() } 123 | return AnnotatedExpression(range, .intLiteral(int)) 124 | } else if p == "\"" { 125 | let start = position 126 | removeFirst() 127 | let value = remove(while: { $0 != "\"" }) // todo escaping 128 | guard remove(prefix: "\"") else { 129 | throw err(Reason.expected("\"")) 130 | } 131 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: position), .stringLiteral(String(value))) 132 | } else if p.isIdentifierStart { 133 | let (range, name) = try annotate { try $0.parseIdentifier() } 134 | return AnnotatedExpression(range, .variable(name)) 135 | } else if p == "<" { 136 | return try parseTag() 137 | } else { 138 | throw err(.expectedAtom) 139 | } 140 | } 141 | throw err(.unexpectedEOF) 142 | } 143 | 144 | mutating func parseInt() -> Int { 145 | return Int(String(remove(while: { $0.isDecimalDigit })))! 146 | } 147 | 148 | mutating func parseTag() throws -> AnnotatedExpression { 149 | let start = position 150 | if remove(prefix: "<") { 151 | skipWS() 152 | let name = try parseIdentifier() 153 | guard remove(prefix: ">") else { 154 | throw err(Reason.expected(">")) 155 | } 156 | skipWS() 157 | var body: [AnnotatedExpression] = [] 158 | while !remove(prefix: "") { 159 | try body.append(parseTag()) 160 | } 161 | return AnnotatedExpression(SourceRange(startIndex: start, endIndex: position), .tag(name: name, body: body)) 162 | } else if remove(prefix: "{") { 163 | skipWS() 164 | let result = try parseExpression() 165 | skipWS() 166 | guard remove(prefix: "}") else { 167 | throw err(Reason.expected("}")) 168 | } 169 | return result 170 | } else { 171 | throw err(Reason.expected("{ or <")) 172 | } 173 | } 174 | 175 | mutating func parseIdentifier() throws -> String { 176 | let name = remove(while: { $0.isIdentifier }) 177 | guard !name.isEmpty else { 178 | throw err(.expectedIdentifier) 179 | } 180 | return String(name) 181 | } 182 | 183 | mutating func parseOperator() throws -> String { 184 | let name = remove(while: { $0.isOperator }) 185 | guard !name.isEmpty else { 186 | throw err(.expectedOperator) 187 | } 188 | return String(name) 189 | } 190 | 191 | mutating func annotate(_ f: (inout Self) throws -> A) throws -> (SourceRange, A) { 192 | let start = position 193 | let result = try f(&self) 194 | let end = position 195 | return (SourceRange(startIndex: start, endIndex: end), result) 196 | } 197 | 198 | mutating func remove(keyword: String) -> Bool { 199 | guard hasPrefix(keyword) else { return false } 200 | let index = self.index(startIndex, offsetBy: keyword.count) 201 | guard index < endIndex, !self[index].isIdentifier else { return false } 202 | _ = remove(prefix: keyword) 203 | return true 204 | } 205 | 206 | 207 | mutating func parse(operator expected: String) -> Bool { 208 | var copy = self 209 | do { 210 | let op = try copy.parseOperator() 211 | guard op == expected else { return false } 212 | self = copy 213 | return true 214 | } catch { 215 | return false 216 | } 217 | } 218 | } 219 | 220 | extension Character { 221 | var isDecimalDigit: Bool { 222 | return isHexDigit && hexDigitValue! < 10 223 | } 224 | 225 | var isOperator: Bool { 226 | return self == "=" // todo 227 | } 228 | 229 | var isIdentifierStart: Bool { 230 | return isLetter 231 | } 232 | 233 | var isIdentifier: Bool { 234 | return isLetter || self == "_" 235 | } 236 | } 237 | 238 | public struct ParseError: Error, Hashable { 239 | public var position: String.Index 240 | public var reason: Reason 241 | } 242 | 243 | public enum Reason: Hashable { 244 | case unexpectedEOF 245 | case expectedAtom 246 | case expectedIdentifier 247 | case expectedOperator 248 | case expectedKeyword(String) 249 | case expected(String) 250 | case unexpectedRemainder(String) 251 | } 252 | 253 | extension String { 254 | public func parse() throws -> AnnotatedExpression { 255 | var context = self[...] 256 | let result = try context.parseExpression() 257 | guard context.isEmpty else { 258 | throw context.err(.unexpectedRemainder(String(context))) 259 | } 260 | return result 261 | } 262 | 263 | } 264 | --------------------------------------------------------------------------------