diff options
| author | Melodi <62381002+Melodi17@users.noreply.github.com> | 2026-07-11 09:03:50 +1000 |
|---|---|---|
| committer | Melodi <62381002+Melodi17@users.noreply.github.com> | 2026-07-11 09:03:50 +1000 |
| commit | cb3cc7e94327c57a0f7539d8f8f2406ca4b04037 (patch) | |
| tree | 8e4d241a466e58b438579622a8a1a6e2339489e9 /Q1.core/Pipeline/Assembler.cs | |
| parent | 512a5f0efeea46994d01124b409ef51f9b9177a0 (diff) | |
checkpointstability-rewrite
Diffstat (limited to 'Q1.core/Pipeline/Assembler.cs')
| -rw-r--r-- | Q1.core/Pipeline/Assembler.cs | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/Q1.core/Pipeline/Assembler.cs b/Q1.core/Pipeline/Assembler.cs new file mode 100644 index 0000000..2a11bbb --- /dev/null +++ b/Q1.core/Pipeline/Assembler.cs @@ -0,0 +1,320 @@ +namespace Q1.core.Pipeline; + +using Q1.core.Arch; +using Q1.core.Arch.Constants; +using Q1.core.Arch.Lookups; + +public class Assembler +{ + private Dictionary<string, AssemblerBlock> _blocks = new(); + private string _currentBlock = ""; + private u16 _addressOffset = 0; + + public MemoryStream Assemble(TextReader source) + { + this._blocks[this._currentBlock] = new AssemblerBlock(); + + while (source.ReadLine() is { } line) + { + line = this.StripComments(line); + if (string.IsNullOrEmpty(line)) + continue; + + if (line.StartsWith('.')) + this.HandleDirective(line[1..]); + else if (line.EndsWith(':')) + this.HandleLabel(line[..^1]); + else + this.HandleInstruction(line); + } + + return this.CreateFinalBinary(); + } + + private MemoryStream CreateFinalBinary() + { + Dictionary<string, u16> labelAddresses = new(); + u16 currentAddress = this._addressOffset; + foreach ((string key, AssemblerBlock value) in this._blocks) + { + labelAddresses[key] = currentAddress; + currentAddress += (u16) value.Size; + } + + MemoryStream finalBinary = new(); + foreach ((_, AssemblerBlock value) in this._blocks) + { + foreach ((i64 offset, (bool word, string constant)) in value.DeferredConstants) + { + if (!labelAddresses.TryGetValue(constant, out u16 address)) + throw new Exception($"Undefined label: {constant}"); + + if (word) + value.ResolveWordConstant((i32) offset, address); + else + value.ResolveByteConstant((i32) offset, (u8) address); + } + + value.WriteToStream(finalBinary); + } + + return finalBinary; + } + + private void HandleDirective(string directive) + { + string[] parts = directive + .Split([' ', '\t'], StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.ToLower()) + .ToArray(); + + string directiveName = parts[0]; + string[] args = parts[1..]; + + if (directiveName == "org") + { + if (args.Length != 1) + throw new Exception($"Expected 1 argument for .org directive, got {args.Length}"); + + if (args[0].StartsWith("0x")) + this._addressOffset = Convert.ToUInt16(args[0][2..], 16); + else if (args[0].StartsWith("0b")) + this._addressOffset = Convert.ToUInt16(args[0][2..], 2); + else if (i32.TryParse(args[0], out i32 decimalValue)) + this._addressOffset = (u16) decimalValue; + else + throw new Exception($"Invalid argument for .org directive: {args[0]}"); + } + else + throw new Exception($"Unknown directive: {directiveName}"); + } + + private void HandleLabel(string label) + { + this._currentBlock = label; + if (this._blocks.ContainsKey(label)) + throw new Exception($"Duplicate label: {label}"); + this._blocks[label] = new AssemblerBlock(); + } + + private void HandleInstruction(string instruction) + { + string[] parts = instruction + .Split([' ', '\t', ','], StringSplitOptions.RemoveEmptyEntries); + + string opcodeName = parts[0].ToUpper(); + bool isWordInstruction = !(parts.Length > 1 && parts[1].Equals("byte", StringComparison.InvariantCultureIgnoreCase)); + + string[] args = isWordInstruction ? parts[1..] : parts[2..]; + + var opcode = InstructionSet.GetOpcodeWithIndex(opcodeName, out u8 opcodeIndex); + switch (opcodeIndex) + { + case 0: // Implicit + { + if (args.Length != 0) + throw new Exception($"Expected 0 arguments for opcode {opcodeName}, got {args.Length}"); + + this.WriteInstruction(opcode, 0, 0, isWordInstruction); + + break; + } + + case 1: // Simple Addressing + { + if (args.Length != 1) + throw new Exception($"Expected 1 argument for opcode {opcodeName}, got {args.Length}"); + + u8 m1 = this.GetArgumentType(args[0]); + this.WriteInstruction(opcode, m1, 0, isWordInstruction); + this.WriteArgument(args[0], isWordInstruction); + + break; + } + + case 2: // Extended Implicit + { + if (args.Length != 0) + throw new Exception($"Expected 0 arguments for opcode {opcodeName}, got {args.Length}"); + + this.WriteInstruction(opcode, 0, 1, isWordInstruction); + + break; + } + + case 3: // Extended Addressing + { + if (args.Length != 2) + throw new Exception($"Expected 2 arguments for opcode {opcodeName}, got {args.Length}"); + + u8 m1 = this.GetArgumentType(args[0]); + u8 m2 = this.GetArgumentType(args[1]); + + this.WriteInstruction(opcode, m1, m2, isWordInstruction); + this.WriteArgument(args[0], isWordInstruction); + this.WriteArgument(args[1], isWordInstruction); + + break; + } + } + } + + private void WriteInstruction(u8 opcode, u8 m1, u8 m2, bool word) + { + u16 instruction = InstructionEncoding.Encode(opcode, m1, m2, word); + this.WriteWordToBlock(instruction); + } + + private u8 GetArgumentType(string arg) + { + if (arg.StartsWith('[') && arg.EndsWith(']')) + { + string inner = arg[1..^1]; + + if (ChipRegisters.GetRegisterIndex(inner.ToUpper()) != null) + return AddressingModes.GetAddressingMode("Register Indirect"); + + if (inner.Contains('+')) + return AddressingModes.GetAddressingMode("Register Indirect with Offset"); + + return AddressingModes.GetAddressingMode("Direct"); + } + + if (arg.StartsWith('@')) + { + if (arg.StartsWith("@[") && arg.EndsWith(']')) + return AddressingModes.GetAddressingMode("Indirect"); + else + throw new Exception($"Invalid indirect argument format: {arg}"); + } + + if (ChipRegisters.GetRegisterIndex(arg.ToUpper()) != null) + return AddressingModes.GetAddressingMode("Register"); + + return AddressingModes.GetAddressingMode("Immediate"); + } + + private void WriteArgument(string arg, bool isWord) + { + if (arg.StartsWith('[') && arg.EndsWith(']')) + { + string inner = arg[1..^1]; + + if (ChipRegisters.GetRegisterIndex(inner.ToUpper()) is { } registerIndex) + this.WriteByteToBlock(registerIndex); + + else if (inner.Contains('+')) + { + string[] parts = inner.Split('+', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) + throw new Exception($"Invalid register indirect with offset format: {arg}"); + + string registerPart = parts[0].Trim().ToUpper(); + string offsetPart = parts[1].Trim(); + + if (ChipRegisters.GetRegisterIndex(registerPart) is not { } regIndex) + throw new Exception($"Invalid register in register indirect with offset: {registerPart}"); + + this.WriteByteToBlock(regIndex); + this.WriteConstant(offsetPart, isWord); + } + else + this.WriteConstant(inner, isWord); + } + + else if (arg.StartsWith('@')) + { + if (arg.StartsWith("@[") && arg.EndsWith(']')) + { + string inner = arg[2..^1]; + + this.WriteConstant(inner, isWord); + } + else + throw new Exception($"Invalid indirect argument format: {arg}"); + } + + else if (ChipRegisters.GetRegisterIndex(arg.ToUpper()) is { } registerIndex) + this.WriteByteToBlock(registerIndex); + else + this.WriteConstant(arg, isWord); + } + + private void WriteConstant(string constant, bool isWord) + { + // hex 0x0-9a-fA-F + if (constant.StartsWith("0X", StringComparison.InvariantCultureIgnoreCase)) + { + string hexPart = constant[2..]; + if (isWord) + this.WriteWordToBlock(Convert.ToUInt16(hexPart, 16)); + else + this.WriteByteToBlock(Convert.ToByte(hexPart, 16)); + } + // binary 0b0-1 + else if (constant.StartsWith("0B", StringComparison.InvariantCultureIgnoreCase)) + { + string binaryPart = constant[2..]; + if (isWord) + this.WriteWordToBlock(Convert.ToUInt16(binaryPart, 2)); + else + this.WriteByteToBlock(Convert.ToByte(binaryPart, 2)); + } + // char `a, etc. + else if (constant.StartsWith('`') && constant.Length == 2) + { + char charValue = constant[1]; + if (isWord) + this.WriteWordToBlock(charValue); + else + this.WriteByteToBlock((u8) charValue); + } + + // decimal 0-9+ + else if (i32.TryParse(constant, out i32 decimalValue)) + { + if (isWord) + this.WriteWordToBlock((u16) decimalValue); + else + this.WriteByteToBlock((u8) decimalValue); + } + + // label + else + { + if (isWord) + this.DeferWriteWordToBlock(constant); + else + this.DeferWriteByteToBlock(constant); + } + } + + private void WriteByteToBlock(u8 data) + { + this._blocks[this._currentBlock].WriteByte(data); + } + + private void WriteWordToBlock(u16 data) + { + this._blocks[this._currentBlock].WriteWord(data); + } + + private void DeferWriteByteToBlock(string constant) + { + this._blocks[this._currentBlock].DeferByteConstant(constant); + } + + private void DeferWriteWordToBlock(string constant) + { + this._blocks[this._currentBlock].DeferWordConstant(constant); + } + + private string StripComments(string line) + { + int commentIndex = line.IndexOf(';'); + if (commentIndex >= 0) + return line[..commentIndex].Trim(); + + return line.Trim(); + } +}
\ No newline at end of file |
