diff --git a/LEGv8-Simulator.xcodeproj/project.pbxproj b/LEGv8-Simulator.xcodeproj/project.pbxproj index f5107bd..196c6ad 100644 --- a/LEGv8-Simulator.xcodeproj/project.pbxproj +++ b/LEGv8-Simulator.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 4080D12228E3FD9D00DCA947 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4080D12128E3FD9D00DCA947 /* Document.swift */; }; 4080D12728E4081200DCA947 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4080D12628E4081200DCA947 /* SettingsView.swift */; }; 4080D12B28E40CF400DCA947 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4080D12A28E40CF400DCA947 /* AboutView.swift */; }; + 4095520828EA6A3300697ED9 /* SettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4095520728EA6A3300697ED9 /* SettingsModel.swift */; }; 40D605CD28E74A2F00654D3C /* FlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D605CC28E74A2F00654D3C /* FlagView.swift */; }; 40D605CF28E7520500654D3C /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D605CE28E7520500654D3C /* RegisterView.swift */; }; 40D605D128E7523500654D3C /* MemoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D605D028E7523500654D3C /* MemoryView.swift */; }; @@ -42,6 +43,7 @@ 4080D12128E3FD9D00DCA947 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 4080D12628E4081200DCA947 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 4080D12A28E40CF400DCA947 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + 4095520728EA6A3300697ED9 /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = ""; }; 40D605CC28E74A2F00654D3C /* FlagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlagView.swift; sourceTree = ""; }; 40D605CE28E7520500654D3C /* RegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterView.swift; sourceTree = ""; }; 40D605D028E7523500654D3C /* MemoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryView.swift; sourceTree = ""; }; @@ -108,6 +110,7 @@ children = ( 404364A928E0DD620052F25D /* CPUModel.swift */, 4080D12128E3FD9D00DCA947 /* Document.swift */, + 4095520728EA6A3300697ED9 /* SettingsModel.swift */, ); path = Models; sourceTree = ""; @@ -255,6 +258,7 @@ 40D605CF28E7520500654D3C /* RegisterView.swift in Sources */, 403D871A28E1EF38002E5E4A /* ConsoleView.swift in Sources */, 40D605D128E7523500654D3C /* MemoryView.swift in Sources */, + 4095520828EA6A3300697ED9 /* SettingsModel.swift in Sources */, 403D871828E125AE002E5E4A /* Interpreter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/LEGv8-Simulator/LEGv8_SimulatorApp.swift b/LEGv8-Simulator/LEGv8_SimulatorApp.swift index 615d84e..6583e86 100644 --- a/LEGv8-Simulator/LEGv8_SimulatorApp.swift +++ b/LEGv8-Simulator/LEGv8_SimulatorApp.swift @@ -9,15 +9,18 @@ import SwiftUI @main struct LEGv8_SimulatorApp: App { + @StateObject var settings: SettingsModel = SettingsModel() var body: some Scene { DocumentGroup(newDocument: Document()) { file in ContentView(document: file.$document) + .environmentObject(settings) } #if os(macOS) Settings { SettingsView() + .environmentObject(settings) } #endif } diff --git a/LEGv8-Simulator/Models/CPUModel.swift b/LEGv8-Simulator/Models/CPUModel.swift index cdcf629..7cc6357 100644 --- a/LEGv8-Simulator/Models/CPUModel.swift +++ b/LEGv8-Simulator/Models/CPUModel.swift @@ -455,6 +455,56 @@ class CPUModel: ObservableObject { registers[destination] = registers[operand1]! & operand2 } + func ands(_ destination: String, _ operand1: String, _ operand2: String) throws { + // verify valid registers + try isValidRegister(destination, true) + try isValidRegister(operand1) + try isValidRegister(operand2) + + touchedFlags = true + + // flags + var (n, z, c, v) = (false, false, false, false) + + let result = registers[operand1]! & registers[operand2]! + + // check z flag + if result == 0 { + z = true + } + + // TODO: more flag checks may be needed + + flags = [n, z, c, v] + + registers[destination] = result + } + + func andis(_ destination: String, _ operand1: String, _ operand2: UInt64) throws { + // verify valid registers + try isValidRegister(destination, true) + try isValidRegister(operand1) + try isValidLiteral(operand2) + + touchedFlags = true + + // flags + var (n, z, c, v) = (false, false, false, false) + + let result = registers[operand1]! & operand2 + + // check z flag + if result == 0 { + z = true + } + + // TODO: more flag checks may be needed + + flags = [n, z, c, v] + + registers[destination] = result + } + func orr(_ destination: String, _ operand1: String, _ operand2: String) throws { // verify valid registers try isValidRegister(destination, true) @@ -520,9 +570,4 @@ class CPUModel: ObservableObject { registers[destination] = registers[operand1]! >> operand2 } - - // conditional branch - - // unconditional branch - } diff --git a/LEGv8-Simulator/Models/SettingsModel.swift b/LEGv8-Simulator/Models/SettingsModel.swift new file mode 100644 index 0000000..cc47d3e --- /dev/null +++ b/LEGv8-Simulator/Models/SettingsModel.swift @@ -0,0 +1,13 @@ +// +// SettingsModel.swift +// LEGv8-Simulator +// +// Created by Adin Ackerman on 10/2/22. +// + +import Foundation + +class SettingsModel: ObservableObject { + @Published var executionLimit: Int = 1000 + @Published var buildOnType: Bool = false +} diff --git a/LEGv8-Simulator/Utils/Interpreter.swift b/LEGv8-Simulator/Utils/Interpreter.swift index 17ea0bb..c783c52 100644 --- a/LEGv8-Simulator/Utils/Interpreter.swift +++ b/LEGv8-Simulator/Utils/Interpreter.swift @@ -38,6 +38,7 @@ class Interpreter: ObservableObject { @Published var assembled: Bool = false @Published var error: Bool = false @Published var programCounter: Int = 0 + var executionLimit: Int = 0 @Published var lastTouchedRegister: String? @Published var lastTouchedMemory: UInt64? @@ -107,6 +108,7 @@ class Interpreter: ObservableObject { objectWillChange.send() } + // linting helpers private func isValidRegister(_ register: String) throws { guard cpu.registers.keys.contains(register) else { throw AssemblerError.invalidRegister(register) } } @@ -125,6 +127,7 @@ class Interpreter: ObservableObject { } } + // branching func b(_ label: String) throws { // verify label exists try isValidLabel(label) @@ -223,7 +226,7 @@ class Interpreter: ObservableObject { } func step(mode: RunMode) { - if programCounter > 1000 { + if programCounter > executionLimit { writeToLog("[InstructionLimitExceeded] The maximum execution count has been exceeded, this could be due to infinite recursion. You can change this limit in Preferences.", type: .error) running = false return @@ -281,12 +284,12 @@ class Interpreter: ObservableObject { try verifyArgumentCount(arguments.count, [2]) try isValidRegister(arguments[0]) try isValidRegister(arguments[1]) - case "and", "orr", "eor": + case "and", "ands", "orr", "eor": try verifyArgumentCount(arguments.count, [3]) try isValidRegister(arguments[0]) try isValidRegister(arguments[1]) try isValidRegister(arguments[2]) - case "andi", "orri", "eori": + case "andi", "andis", "orri", "eori": try verifyArgumentCount(arguments.count, [3]) try isValidRegister(arguments[0]) try isValidRegister(arguments[1]) @@ -380,6 +383,12 @@ class Interpreter: ObservableObject { case "andi": try cpu.andi(arguments[0], arguments[1], parseLiteral(arguments[2])) lastTouchedRegister = arguments[0] + case "ands": + try cpu.ands(arguments[0], arguments[1], arguments[2]) + lastTouchedRegister = arguments[0] + case "andis": + try cpu.andis(arguments[0], arguments[1], parseLiteral(arguments[2])) + lastTouchedRegister = arguments[0] case "orr": try cpu.orr(arguments[0], arguments[1], arguments[2]) lastTouchedRegister = arguments[0] diff --git a/LEGv8-Simulator/Utils/Lexer.swift b/LEGv8-Simulator/Utils/Lexer.swift index fe4a609..e079d4d 100644 --- a/LEGv8-Simulator/Utils/Lexer.swift +++ b/LEGv8-Simulator/Utils/Lexer.swift @@ -11,12 +11,13 @@ class Lexer: ObservableObject { var lines: [String] @Published var cursor: Int = 0 - let specialCharacters: [Character] = ["\t"] + let specialCharacters: [Character] = ["\t", "\r"] init(text: String) { lines = text.components(separatedBy: "\n").map { sub in String(sub)} } + // TODO: lexer does not differentiate commas and whitespace func parseNextLine() -> (String, [String]) { guard cursor < lines.count else { return ("_end", []) } @@ -27,6 +28,11 @@ class Lexer: ObservableObject { let line: [String] = lines[cursor].map({ char in if specialCharacters.contains(char) { return " " } else { return char }}).split(separator: " ").map { sub in String(sub)} + if line.count == 0{ + cursor += 1 + return parseNextLine() + } + let instruction: String = line[0].filter({ char in !specialCharacters.contains(char)}) if instruction.contains("/") { // line is only comment cursor += 1 diff --git a/LEGv8-Simulator/Views/ContentView.swift b/LEGv8-Simulator/Views/ContentView.swift index 85ef974..880f095 100644 --- a/LEGv8-Simulator/Views/ContentView.swift +++ b/LEGv8-Simulator/Views/ContentView.swift @@ -17,12 +17,14 @@ typealias _vsplitview = VStack #endif struct ContentView: View { - // monaco - let syntax = SyntaxHighlight(title: "asm", fileURL: Bundle.main.url(forResource: "asm", withExtension: "js")!) + @EnvironmentObject var settings: SettingsModel @StateObject var interpreter: Interpreter = Interpreter() @Binding var document: Document + // monaco + let syntax = SyntaxHighlight(title: "asm", fileURL: Bundle.main.url(forResource: "asm", withExtension: "js")!) + var body: some View { _hsplitview { _vsplitview { @@ -110,6 +112,18 @@ struct ContentView: View { .onChange(of: document.text) { newValue in interpreter.running = false interpreter.assembled = false + + if settings.buildOnType { + withAnimation { + interpreter.assemble(document.text) + } + } + } + .onChange(of: settings.executionLimit) { newValue in + interpreter.executionLimit = newValue + } + .onAppear { + interpreter.executionLimit = settings.executionLimit } } } diff --git a/LEGv8-Simulator/Views/Settings/SettingsView.swift b/LEGv8-Simulator/Views/Settings/SettingsView.swift index 1f816e3..316cda4 100644 --- a/LEGv8-Simulator/Views/Settings/SettingsView.swift +++ b/LEGv8-Simulator/Views/Settings/SettingsView.swift @@ -8,7 +8,8 @@ import SwiftUI struct SettingsView: View { - @State var execLimit: String = "1000" + @EnvironmentObject var model: SettingsModel + @State var execLimitString: String = "" var body: some View { TabView() { @@ -16,15 +17,26 @@ struct SettingsView: View { HStack { Text("Execution limit:") - TextField("", text: $execLimit) + TextField("", text: $execLimitString) .textFieldStyle(.roundedBorder) .frame(width: 100) + .onChange(of: execLimitString) { newValue in + if let val = Int(newValue) { + model.executionLimit = val + } + } } + + Toggle("Build on type", isOn: $model.buildOnType) + .toggleStyle(.switch) } .frame(width: 400, height: 400) .tabItem { Label("Settings", systemImage: "gear") } + .onAppear { + execLimitString = String(model.executionLimit) + } AboutView() .tabItem { @@ -34,8 +46,8 @@ struct SettingsView: View { } } -struct SettingsView_Previews: PreviewProvider { - static var previews: some View { - SettingsView() - } -} +//struct SettingsView_Previews: PreviewProvider { +// static var previews: some View { +// SettingsView() +// } +//}