diff --git a/SAP1EMU.Assembler/Assemble.cs b/SAP1EMU.Assembler/Assemble.cs index 061cabc4..d96ac2ab 100644 --- a/SAP1EMU.Assembler/Assemble.cs +++ b/SAP1EMU.Assembler/Assemble.cs @@ -11,12 +11,12 @@ namespace SAP1EMU.Assembler { public static class Assemble { - - public static List Parse(List unchecked_assembly) + private const string DefaultInstructionSetName = "SAP1Emu"; + public static List Parse(List unchecked_assembly, string InstructionSetName = DefaultInstructionSetName) { // Get Instruction Set - InstructionSet iset = OpCodeLoader.GetSet("SAP1Emu"); + InstructionSet iset = OpCodeLoader.GetSet(InstructionSetName); // ********************************************************************* // Sanitize * @@ -142,14 +142,14 @@ private static bool IsValid(List unchecked_assembly, InstructionSet iset if(nibbles.Length == 0) { - throw new ParseException($"SAP1ASM: Line cannot be blank (line: {line_number})", new ParseException("Use \"NOP 0x0\" for a no-operation command")); + throw new ParseException($"SAP1ASM: Line cannot be blank (line: {line_number}).", new ParseException("Use \"NOP 0x0\" for a no-operation command")); } string instruction = nibbles[0]; if (nibbles.Length < 2) { - throw new ParseException($"SAP1ASM: No lower nibble detected (line: {line_number})", new ParseException($"{instruction} must be paired with a valid address in the range of 0x0 - 0xF")); + throw new ParseException($"SAP1ASM: No lower nibble detected (line: {line_number}).", new ParseException($"{instruction} must be paired with a valid address in the range of 0x0 - 0xF")); } string addr = nibbles[1]; @@ -158,7 +158,7 @@ private static bool IsValid(List unchecked_assembly, InstructionSet iset // Check Intruction if (instruction.Length != 3) { - throw new ParseException($"SAP1ASM: invalid intruction on line {line_number}", new ParseException($"{instruction} is not a recognized instruction")); + throw new ParseException($"SAP1ASM: invalid intruction on line {line_number}.", new ParseException($"{instruction} is not a recognized instruction")); } @@ -166,7 +166,7 @@ private static bool IsValid(List unchecked_assembly, InstructionSet iset { if (!Regex.IsMatch(instruction, "^0[xX][0-9a-fA-F]$")) // Make sure it isnt data { - throw new ParseException($"SAP1ASM: invalid intruction on line {line_number}", new ParseException($"{instruction} is not a recognized instruction or valid data")); + throw new ParseException($"SAP1ASM: invalid intruction on line {line_number}.", new ParseException($"{instruction} is not a recognized instruction or valid data")); } } @@ -175,21 +175,21 @@ private static bool IsValid(List unchecked_assembly, InstructionSet iset // Check Address if (addr.Length != 3) // should be no more than 3 { - throw new ParseException($"SAP1ASM: invalid address on line {line_number}", new ParseException($"{addr} is not of the form \"0xX\"")); + throw new ParseException($"SAP1ASM: invalid address on line {line_number}.", new ParseException($"{addr} is not of the form \"0xX\"")); } if (!Regex.IsMatch(addr, "^0[xX][0-9a-fA-F]$")) // should be of the form 0xX { - throw new ParseException($"SAP1ASM: invalid address on line {line_number}", new ParseException($"{addr} is not of the form \"0xX\"")); + throw new ParseException($"SAP1ASM: invalid address on line {line_number}.", new ParseException($"{addr} is not of the form \"0xX\"")); } int hex_addr = (int)(Convert.ToUInt32(addr.Substring(2, 1), 16)); if (hex_addr < 0 || hex_addr >= 16) // must tbe between 0-15 { - throw new ParseException($"SAP1ASM: address out of range on line {line_number}", new ParseException($"{addr} must be betweeen 0x0 and 0xF")); + throw new ParseException($"SAP1ASM: address out of range on line {line_number}.", new ParseException($"{addr} must be betweeen 0x0 and 0xF")); } if(line.Contains("...")) { - throw new ParseException($"SAP1ASM: invalid use of \"...\" on line {line_number}", new ParseException($"{line} must only contain \"...\" with no extra charecters or spaces")); + throw new ParseException($"SAP1ASM: invalid use of \"...\" on line {line_number}.", new ParseException($"{line} must only contain \"...\" with no extra charecters or spaces")); } } @@ -201,7 +201,7 @@ private static bool IsValid(List unchecked_assembly, InstructionSet iset } else { - throw new ParseException($"SAP1ASM: invalid use of \"...\" {line_number}", new ParseException($"{line} must only contain once instance of \"...\" in the program")); + throw new ParseException($"SAP1ASM: invalid use of \"...\" {line_number}.", new ParseException($"{line} must only contain once instance of \"...\" in the program")); } } diff --git a/SAP1EMU.Engine-CLI/Program.cs b/SAP1EMU.Engine-CLI/Program.cs index 2f979c1f..5f2cb371 100644 --- a/SAP1EMU.Engine-CLI/Program.cs +++ b/SAP1EMU.Engine-CLI/Program.cs @@ -3,7 +3,9 @@ using System.IO; using System.Text; using System.Threading; + using CommandLine; + using SAP1EMU.Assembler; using SAP1EMU.Engine; using SAP1EMU.Lib; @@ -53,6 +55,12 @@ public class Options public bool Debug { get; set; } // ******************************************** + // Instruction Set **************************** + [Option('i', "instructionSet", Required = false, HelpText = "Sets the Instruction Set to use\nParameters:\n SAP1Emu\tUses expanded SAP1EMU Instruction Set (default)\n Malvino\tUses Malvino's Instruction Set\n BenEater\tUses Ben Eater's Instruction Set", Default = "SAP1Emu")] + public string InstructionSetName { get; set; } + // ******************************************** + + } @@ -124,7 +132,7 @@ static void Main(string[] args) } - if(!string.IsNullOrEmpty(o.FOframe)) + if (!string.IsNullOrEmpty(o.FOframe)) { if (o.FOframe.ToLower() != "no-format" && o.FOframe.ToLower() != "std") { @@ -132,16 +140,50 @@ static void Main(string[] args) o.FOframe = "std"; } } - - + + if (o.InstructionSetName.ToLower() != "sap1emu" && o.InstructionSetName.ToLower() != "malvino" && o.InstructionSetName.ToLower() != "beneater") + { + Console.Error.WriteLine($"SAP1EMU: warning: {o.InstructionSetName}: invalid argument: Defaulting to \"SAP1Emu\"."); + o.InstructionSetName = "SAP1Emu"; + } - List compiled_binary; + + + + List compiled_binary = null; if (fileType == FileType.S) { - compiled_binary = Assemble.Parse(source_file_contents); + try + { + compiled_binary = Assemble.Parse(source_file_contents, o.InstructionSetName); + } + catch (ParseException pe) + { + //Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); + //Console.SetError(new StreamWriter(Console.OpenStandardError())); + + var tempColor = Console.ForegroundColor; + if (Console.BackgroundColor == ConsoleColor.Red) + { + Console.ForegroundColor = ConsoleColor.Cyan; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + } + Console.Error.WriteLine($"SAP1ASM: fatal error: " + pe.Message + " " + pe.InnerException.Message); + Console.ForegroundColor = tempColor; + Console.Error.WriteLine("assembly terminated"); + + + + Console.Error.Flush(); + + System.Environment.Exit(1); + } } else { @@ -164,7 +206,7 @@ static void Main(string[] args) //Console.SetError(writer_error); - engine.Init(rmp); + engine.Init(rmp, o.InstructionSetName); try { engine.Run(); @@ -172,10 +214,20 @@ static void Main(string[] args) } catch (EngineRuntimeException ere) { - Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); - Console.SetError(new StreamWriter(Console.OpenStandardError())); + var tempColor = Console.ForegroundColor; + if (Console.BackgroundColor == ConsoleColor.Red) + { + Console.ForegroundColor = ConsoleColor.Cyan; + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + } + //Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); + //Console.SetError(new StreamWriter(Console.OpenStandardError())); - Console.Error.WriteLine($"SAP1EMU: fatal error: " + ere.Message); + Console.Error.WriteLine($"SAP1EMU: fatal error: " + ere.Message + " " + ere.InnerException.Message); + Console.ForegroundColor = tempColor; Console.Error.WriteLine("emulation terminated"); Console.Error.Flush(); @@ -208,9 +260,9 @@ static void Main(string[] args) engine_output += "\n" + sb.ToString(); } - else if(o.FOframe != null) + else if (o.FOframe != null) { - engine_output =null; // Clear the output + engine_output = null; // Clear the output StringBuilder sb = new StringBuilder(); StringWriter fw = new StringWriter(sb); @@ -223,16 +275,16 @@ static void Main(string[] args) { fw.WriteLine(frame.OutputRegister()); } - else if(o.FOframe.ToLower() == "no-format") + else if (o.FOframe.ToLower() == "no-format") { string temp = frame.OReg; - if(string.IsNullOrEmpty(temp)) + if (string.IsNullOrEmpty(temp)) { temp = "00000000"; } fw.WriteLine(temp); } - + } } fw.Flush(); diff --git a/SAP1EMU.Engine/EngineProc.cs b/SAP1EMU.Engine/EngineProc.cs index 45d81a99..8680f760 100644 --- a/SAP1EMU.Engine/EngineProc.cs +++ b/SAP1EMU.Engine/EngineProc.cs @@ -16,14 +16,15 @@ public class EngineProc private readonly List _FrameStack = new List(); private RAMProgram Program { get; set; } private InstructionSet InstructionSet { get; set; } + private const string DefaultInstructionSetName = "SAP1Emu"; // ************************************************************************* // Init Engine // ************************************************************************* - public void Init(RAMProgram program) + public void Init(RAMProgram program, string InstructionSetName = DefaultInstructionSetName) { // Get Instruction Set - InstructionSet = OpCodeLoader.GetSet("SAP1Emu"); + InstructionSet = OpCodeLoader.GetSet(InstructionSetName); // Init RAM if (program == null) diff --git a/SAP1EMU.Lib.Test/AssemblerTest.cs b/SAP1EMU.Lib.Test/AssemblerTest.cs index ecc92d2e..56841ee2 100644 --- a/SAP1EMU.Lib.Test/AssemblerTest.cs +++ b/SAP1EMU.Lib.Test/AssemblerTest.cs @@ -417,5 +417,56 @@ public void TestParseList_Invalid_Code_3() #endregion + + + // Malvino Op Code Loop Detection Test ************************************** + [TestMethod] + public void Test_MalvinoCodes_1() + { + List asm = new List + { + "LDA 0xF", + "OUT 0x0", + "HLT 0x0", + "... ", + "0xA 0xA" + }; + try + { + Assemble.Parse(asm, "Malvino"); + } + catch(Exception e) + { + Assert.Fail(e.ToString()); + } + } + + + [TestMethod] + public void Test_MalvinoCodes_2() + { + List asm = new List + { + "LDA 0xF", + "STA 0xE", + "OUT 0x0", + "HLT 0x0", + "... ", + "0x0 0x0", + "0xA 0xA" + }; + try + { + Assemble.Parse(asm, "Malvino"); + Assert.Fail(); + } + catch (Exception e) + { + + } + } + // ************************************************************************** + + } } diff --git a/SAP1EMU.Lib.Test/EngineTest.cs b/SAP1EMU.Lib.Test/EngineTest.cs index 166a8dc0..52703ada 100644 --- a/SAP1EMU.Lib.Test/EngineTest.cs +++ b/SAP1EMU.Lib.Test/EngineTest.cs @@ -1138,7 +1138,7 @@ public void Infinite_Loop_Test() } // ************************************************************************** - + } } \ No newline at end of file diff --git a/SAP1EMU.Lib/InstructionSets.json b/SAP1EMU.Lib/InstructionSets.json index b26ee05c..7d4be3fa 100644 --- a/SAP1EMU.Lib/InstructionSets.json +++ b/SAP1EMU.Lib/InstructionSets.json @@ -140,6 +140,42 @@ "00101110000111", "00111100011111" ] + }, + { + "OpCode": "SUB", + "BinCode": "0010", + "MicroCode": [ + "01011110001111", + "10111110001111", + "00100110001111", + "00011010001111", + "00101110000111", + "00111100111111" + ] + }, + { + "OpCode": "OUT", + "BinCode": "1110", + "MicroCode": [ + "01011110001111", + "10111110001111", + "00100110001111", + "00111111001011", + "00111110001111", + "00111110001111" + ] + }, + { + "OpCode": "HLT", + "BinCode": "1111", + "MicroCode": [ + "01011110001111", + "10111110001111", + "00100110001111", + "00111110001111", + "00111110001111", + "00111110001111" + ] } ] }, @@ -156,7 +192,7 @@ ] }, { - "OpCode": "LDA", + "OpCode": "OUT", "BinCode": "0000", "MicroCode": [ "01010",