summaryrefslogtreecommitdiff
path: root/Assembler/Assembler.cs
diff options
context:
space:
mode:
authorMelodi <62381002+Melodi17@users.noreply.github.com>2025-08-05 21:24:16 +1000
committerMelodi <62381002+Melodi17@users.noreply.github.com>2025-08-05 21:24:16 +1000
commit16f3cb74564cf7c19da6e13666c98aca36094716 (patch)
tree7346a7c12a82bc372ed82dad8e9a5d9ad6b098b7 /Assembler/Assembler.cs
parent72f297a479b6749674ec80e91be53b8c482123d2 (diff)
updates
Diffstat (limited to 'Assembler/Assembler.cs')
-rw-r--r--Assembler/Assembler.cs297
1 files changed, 213 insertions, 84 deletions
diff --git a/Assembler/Assembler.cs b/Assembler/Assembler.cs
index 5df068d..09ac30e 100644
--- a/Assembler/Assembler.cs
+++ b/Assembler/Assembler.cs
@@ -2,16 +2,32 @@ namespace Q1Emu.Assembler;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
+using Chip;
public class Assembler
{
public readonly BinaryWriter Writer;
public readonly StringReader Reader;
- private Dictionary<string, u16> _labels = new();
- private u16 _addressOffset = 0;
+ private Dictionary<string, u16> _labels = new();
+ private Dictionary<u16, string> _unloadedLabels = new();
+ private u16 _addressOffset = 0;
+
+ public static Dictionary<u16, string> RegisterLookup = new()
+ {
+ { 0x40, "AX" },
+ { 0x41, "AH" },
+ { 0x42, "AL" },
+ { 0x43, "DX" },
+ { 0x44, "DH" },
+ { 0x45, "DL" },
+ { 0x46, "LX" },
+ { 0x50, "PC" },
+ { 0x51, "SP" },
+ };
public Assembler(StringReader reader, BinaryWriter writer)
{
@@ -21,8 +37,10 @@ public class Assembler
public void Assemble()
{
+ int lineNumber = 0;
while (this.Reader.ReadLine() is { } line)
{
+ lineNumber++;
string trimmedLine = line.Split(";")[0].Trim(); // Remove comments
if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith(';'))
continue;
@@ -32,17 +50,91 @@ public class Assembler
// Handle .org directive
string orgValue = trimmedLine[5..].Trim();
this._addressOffset = this.GetConstant(orgValue);
- Console.WriteLine($"Setting address offset to {this._addressOffset:X4}");
+ }
+
+ else if (trimmedLine.StartsWith(".ascii"))
+ {
+ string asciiValue = trimmedLine[7..].Trim()
+ [1..^1]; // Remove quotes
+
+ for (int i = 0; i < asciiValue.Length; i++)
+ {
+ char c = asciiValue[i];
+ if (c == '\\')
+ c = asciiValue[++i] switch
+ {
+ 'n' => '\n',
+ 't' => '\t',
+ '\\' => '\\',
+ '\'' => '\'',
+ '"' => '"',
+ _ => throw new AssemblerException($"Unknown escape sequence: \\{asciiValue[i]}"),
+ };
+
+ this.Write((u8) c);
+ }
+ }
+
+ else if (trimmedLine.StartsWith(".raw"))
+ {
+ string[] values = trimmedLine[5..].Split(',');
+ foreach (string value in values)
+ this.Write(GetConstant(value.Trim()));
+ }
+
+ else if (trimmedLine.StartsWith(".include"))
+ {
+ string includeFile = trimmedLine[9..].Trim()[1..^1];
+ if (!File.Exists(includeFile))
+ {
+ WriteError(line, lineNumber, $"Include file '{includeFile}' not found");
+ return;
+ }
+
+ foreach (byte b in File.ReadAllBytes(includeFile))
+ this.Writer.Write(b);
}
else if (trimmedLine.EndsWith(":"))
this._labels[trimmedLine[..^1]] = (u16) (this.Writer.BaseStream.Position + this._addressOffset);
-
+
+ else
+ {
+ try
+ {
+ this.ParseInstruction(trimmedLine);
+ }
+ catch (AssemblerException ex)
+ {
+ WriteError(line, lineNumber, ex.Message);
+ return;
+ }
+ }
+ }
+
+ foreach ((u16 key, string value) in this._unloadedLabels)
+ {
+ if (this._labels.TryGetValue(value, out u16 address))
+ {
+ Console.WriteLine($"Resolving label '{value}' at address {address:X4} for key {key:X4}");
+ this.Writer.BaseStream.Seek(key, SeekOrigin.Begin);
+ this.Write(address);
+ }
else
- this.ParseInstruction(trimmedLine);
+ {
+ WriteError($"", 0, "Label not defined");
+ }
}
}
+ private void WriteError(string line, int lineNumber, string message)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"Error on line {lineNumber}: {message}");
+ Console.WriteLine($" --> {line}");
+ Console.ResetColor();
+ }
+
private void ParseInstruction(string instruction)
{
string[] parts = instruction.Split(' ', 2);
@@ -50,37 +142,93 @@ public class Assembler
parts = parts.Length > 1 ? parts[1].Split(',') : [];
if (parts.Length > 2)
- throw new ArgumentException($"Invalid instruction format: {instruction}");
+ throw new AssemblerException($"Invalid instruction format");
- u8 opcodeValue = this.ParseOpcode(opcode);
+ u8 opcodeValue = this.ParseOpcode(opcode, parts.Length, out u8 addressingIndex);
- u8 m1 = 0, m2 = 0;
- if (parts.Length > 0)
- m1 = this.ParseMode(parts[0]);
- if (parts.Length > 1)
- m2 = this.ParseMode(parts[1]);
+ u8 m1, m2;
+ switch (addressingIndex)
+ {
+ case 0: // Implicit
+ if (parts.Length > 0)
+ throw new AssemblerException($"Implicit instruction should not have operands");
+
+ m1 = 0x00;
+ m2 = 0x00;
+ break;
+
+ case 1: // Simple addressing mode
+ if (parts.Length > 2)
+ throw new AssemblerException($"Simple addressing mode should have at most one operand");
+ if (parts.Length == 0)
+ throw new AssemblerException($"Simple addressing mode requires an operand");
+
+ m1 = this.ParseMode(parts[0]);
+ m2 = 0x00;
+ break;
+
+ case 2: // Extended implicit
+ if (parts.Length > 0)
+ throw new AssemblerException($"Extended implicit instruction should not have operands");
+
+ m1 = 0x00;
+ m2 = 0x01;
+ break;
+
+ case 3: // Dual addressing mode
+ if (parts.Length != 2)
+ throw new AssemblerException($"Dual addressing mode requires exactly two operands");
+
+ m1 = this.ParseMode(parts[0]);
+ m2 = this.ParseMode(parts[1]);
+ break;
+
+ default:
+ throw new UnreachableException("Invalid addressing index: " + addressingIndex);
+ }
- Console.WriteLine($"Parsed instruction: {opcode} with modes {m1:X2}, {m2:X2} (at address {this.Writer.BaseStream.Position + this._addressOffset:X4})");
this.WriteInstruction(opcodeValue, m1, m2);
+
if (parts.Length > 0)
this.WriteMode(m1, parts[0]);
if (parts.Length > 1)
this.WriteMode(m2, parts[1]);
}
- private u8 ParseOpcode(string opcode)
+ private u8 ParseOpcode(string opcode, int parts, out u8 addressingIndex)
{
- return opcode switch
+ List<int> addressingIndices = new();
+ u8? opCode = null;
+ foreach ((u8 key, string[] value) in Q1Cpu.InstructionLookupStrings)
{
- "NOP" => 0x00, "JMP" => 0x00, "MOV" => 0x00,
- "RET" => 0x01, "CALL" => 0x01, "CMP" => 0x02,
- "BR" => 0x02, "BZ" => 0x02, "LT" => 0x02,
- "GT" => 0x03,
- "INC" => 0x08, "ADD" => 0x08,
- "DEC" => 0x09,
- "MUL" => 0x0B,
- _ => throw new ArgumentException($"Unknown opcode: {opcode}")
- };
+ if (value.Contains(opcode))
+ {
+ addressingIndices.AddRange(value.AllIndexesOf(opcode));
+ if (opCode is not null && opCode != key)
+ throw new AssemblerException($"Ambiguous opcode '{opcode}' matches multiple instructions: {opCode:X4} and {key:X4}");
+
+ opCode = key;
+ }
+ }
+
+ if (opCode is null)
+ throw new AssemblerException($"Unknown opcode '{opcode}'");
+
+ int Parts(int index)
+ => index switch
+ {
+ 0 => 0, // Implicit
+ 1 => 1, // Simple addressing mode
+ 2 => 0, // Extended implicit
+ 3 => 2, // Dual addressing mode
+ _ => throw new UnreachableException("Invalid addressing index: " + index)
+ };
+
+ if (addressingIndices.All(x => Parts(x) != parts))
+ throw new AssemblerException($"Opcode '{opcode}' does not match the expected number of operands: {parts}");
+
+ addressingIndex = (u8) addressingIndices.First(x => Parts(x) == parts);
+ return opCode.Value;
}
private u8 ParseMode(string mode)
@@ -92,20 +240,7 @@ public class Assembler
if (text.Length > 1 && text[0] == 'V')
return true;
- string[] hardcodedRegisters =
- [
- "AX",
- "AH",
- "AL",
- "DX",
- "DH",
- "DL",
- "LX",
- "PC",
- "SP",
- ];
-
- return hardcodedRegisters.Contains(text);
+ return Assembler.RegisterLookup.ContainsValue(text);
}
if (this._labels.ContainsKey(mode))
@@ -157,12 +292,12 @@ public class Assembler
this.Write(immediateValue);
break;
- case 0x02: // Direct
+ case 0x02: // Direct
u16 directAddress = this.GetConstant(operand); // Remove brackets
this.Write(directAddress);
break;
- case 0x03: // Indirect
+ case 0x03: // Indirect
u16 indirectAddress = this.GetConstant(operand); // Remove brackets and @
this.Write(indirectAddress);
break;
@@ -177,7 +312,7 @@ public class Assembler
Write(regIndex);
break;
- case 0x06: // Register Indirect
+ case 0x06: // Register Indirect
u8 indirectRegIndex = this.GetRegisterIndex(operand); // Remove brackets
Write(indirectRegIndex);
break;
@@ -190,54 +325,26 @@ public class Assembler
break;
}
}
- private i16 GetSignedConstant(string value)
- {
- value = this.Clean(value);
-
- if (value.StartsWith("$"))
- {
- // Hexadecimal offset
- return Convert.ToInt16(value[1..], 16);
- }
- else if (i16.TryParse(value, out i16 result))
- // Decimal offset
- return result;
- else
- {
- throw new ArgumentException($"Invalid offset value: {value}");
- }
- }
private u8 GetRegisterIndex(string reg)
{
reg = this.Clean(reg);
-
+
if (reg.StartsWith("V"))
if (u8.TryParse(reg[1..], out u8 index) && index < 0x3F)
return index;
- return reg switch
+ if (Assembler.RegisterLookup.ContainsValue(reg))
{
- "AX" => 0x40,
- "AH" => 0x41,
- "AL" => 0x42,
- "DX" => 0x43,
- "DH" => 0x44,
- "DL" => 0x45,
- "LX" => 0x46,
- "PC" => 0x50,
- "SP" => 0x51,
- _ => throw new ArgumentException($"Invalid register index: {reg}")
- };
+ // Find the register index by value
+ return (u8) Assembler.RegisterLookup.FirstOrDefault(x => x.Value == reg).Key;
+ }
+
+ throw new AssemblerException($"Invalid register: {reg}");
}
private u16 GetConstant(string value)
{
value = this.Clean(value);
-
- if (this._labels.ContainsKey(value))
- {
- // Label reference
- return this._labels[value];
- }
+
if (value.StartsWith("$"))
{
// Hexadecimal constant (backwards) $1FFF
@@ -249,7 +356,26 @@ public class Assembler
return result;
else
{
- throw new ArgumentException($"Invalid constant value: {value}");
+ this._unloadedLabels[(u16) this.Writer.BaseStream.Position] = value;
+ return 0;
+ }
+ }
+ private i16 GetSignedConstant(string value)
+ {
+ value = this.Clean(value);
+
+ if (value.StartsWith("$"))
+ {
+ // Hexadecimal offset
+ return Convert.ToInt16(value[1..], 16);
+ }
+ else if (i16.TryParse(value, out i16 result))
+ // Decimal offset
+ return result;
+ else
+ {
+ this._unloadedLabels[(u16) this.Writer.BaseStream.Position] = value;
+ return 0;
}
}
@@ -257,27 +383,30 @@ public class Assembler
{
if (value.StartsWith("@"))
value = value[1..]; // Remove @ for indirect addressing
-
+
if (value.StartsWith("[") && value.EndsWith("]"))
value = value[1..^1].Trim(); // Remove brackets for direct addressing
-
+
return value.Trim();
}
-
+
private void Write(u16 value)
{
u16 swappedValue = (u16) ((value >> 8) | (value << 8));
this.Writer.Write(swappedValue);
}
-
+
private void Write(u8 value)
{
this.Writer.Write(value);
}
-
+
private void Write(i16 value)
{
u16 swappedValue = (u16) ((value >> 8) | (value << 8));
this.Writer.Write(swappedValue);
}
-} \ No newline at end of file
+}
+
+public class AssemblerException(string message)
+ : Exception(message); \ No newline at end of file