void assembler_HandleCodeLine(object sender, HandleCodeEventArgs e) { // These things aren't really relevant to a DCPU-16 program, so // we just discard them so it doesn't throw an error. if (e.Code == ".text") { e.Handled = true; e.Output.CodeType = CodeType.Directive; e.Output.Output = new ushort[0]; } if (e.Code.StartsWith(".globl")) { e.Handled = true; e.Output.CodeType = CodeType.Directive; e.Output.Output = new ushort[0]; } if (e.Code == ".data") { e.Handled = true; e.Output.CodeType = CodeType.Directive; e.Output.Output = new ushort[0]; } if (e.Code.StartsWith(".short ")) { var expression = assembler.ParseExpression(e.Code.Substring(7)); // TODO: Postpone evalulation? e.Output.Output = new[] { expression.Value }; e.Output.CodeType = CodeType.Directive; e.Handled = true; } if (e.Code.StartsWith(".byte ")) { var expression = assembler.ParseExpression(e.Code.Substring(5)); e.Output.Output = new[] { expression.Value }; e.Output.CodeType = CodeType.Directive; e.Handled = true; } if (e.Code.StartsWith(".comm ")) { e.Code = e.Code.Replace(".comm", ".equ").Replace(",", " "); } }
/// <summary> /// Assembles the provided code. /// This will use the current directory to fetch include files and such. /// </summary> /// <returns>A listing for the code</returns> public List<ListEntry> Assemble(string code, string FileName) { FileNames = new Stack<string>(); LineNumbers = new Stack<int>(); FileNames.Push(FileName); LineNumbers.Push(0); RootLineNumber = 0; IfStack.Push(true); bool isFromExpanded; // Pass one string[] lines = code.Replace("\r", "").Split('\n'); List<ListEntry> output = new List<ListEntry>(); for (int i = 0; i < lines.Length; i++) { isFromExpanded = false; if (SuspendedLineCounts.Count == 0) { LineNumbers.Push(LineNumbers.Pop() + 1); RootLineNumber++; } else { isFromExpanded = true; int count = SuspendedLineCounts.Pop(); count--; if (count > 0) SuspendedLineCounts.Push(count); } string line = lines[i].TrimComments().TrimExcessWhitespace(); if (string.IsNullOrEmpty(line)) continue; string[] sublines = line.SafeSplit('\\'); if (sublines.Length > 1) { string[] newLines = new string[lines.Length + sublines.Length - 1]; Array.Copy(lines, 0, newLines, 0, i); Array.Copy(sublines, 0, newLines, i, sublines.Length); if (lines.Length > i + 1) Array.Copy(lines, i + 1, newLines, i + sublines.Length, lines.Length - i - 1); lines = newLines; i--; SuspendedLineCounts.Push(sublines.Length); continue; } ListEntry listEntry = new ListEntry(line, FileNames.Peek(), LineNumbers.Peek(), currentAddress, !noList, isFromExpanded); listEntry.RootLineNumber = RootLineNumber; if (HandleCodeLine != null) { HandleCodeEventArgs args = new HandleCodeEventArgs(); args.Code = line; args.Handled = false; args.Output = listEntry; HandleCodeLine(this, args); if (args.Handled) { output.Add(args.Output); continue; } listEntry = args.Output; line = args.Code; } if (line.SafeContains(':') && !noList) { if (!IfStack.Peek()) continue; listEntry.CodeType = CodeType.Directive; // Parse labels string label = line; if (line.StartsWith(":")) { label = label.Substring(1); if (line.Contains(' ')) line = line.Substring(line.IndexOf(' ') + 1).Trim(); else line = ""; } else { label = label.Remove(label.IndexOf(':')); line = line.Substring(line.IndexOf(':') + 1); } line = line.Trim(); if (label.Contains(" ")) label = label.Remove(label.IndexOf(' ')); if (label == "$") { RelativeLabels.Add(GetRootNumber(LineNumbers), currentAddress); output.Add(listEntry); continue; } if (label.Contains(' ') || label.Contains('\t') || !(char.IsLetter(label[0]) || label[0] == '.' || label[0] == '_')) { listEntry.ErrorCode = ErrorCode.InvalidLabel; output.Add(listEntry); continue; } bool invalid = false; if (label.StartsWith("_")) { listEntry.ErrorCode = ErrorCode.InvalidLabel; output.Add(listEntry); continue; } foreach (char c in label) { if (!char.IsLetterOrDigit(c) && c != '_' && c != '.') { listEntry.ErrorCode = ErrorCode.InvalidLabel; output.Add(listEntry); invalid = true; break; } } if (invalid) continue; if (Values.ContainsKey(label) || LabelValues.ContainsKey(label)) { listEntry.ErrorCode = ErrorCode.DuplicateName; output.Add(listEntry); continue; } if (label.StartsWith(".")) label = PriorGlobalLabel + "_" + label.Substring(1); else PriorGlobalLabel = label; LabelValues.Add(new Label() { LineNumber = LineNumbers.Peek(), Name = label, RootLineNumber = listEntry.RootLineNumber, Address = currentAddress, }); if (!IsRelocating) LabelValues[LabelValues.Count - 1].RelocationGroup = -1; else LabelValues[LabelValues.Count - 1].RelocationGroup = RelocationGroup; listEntry.CodeType = CodeType.Label; output.Add(listEntry); } if (string.IsNullOrEmpty(line)) continue; if (line.Contains(".equ") && !line.StartsWith(".equ")) // TASM compatibility { line = ".equ " + line.Replace(".equ", "").TrimExcessWhitespace(); } if (line.StartsWith("dat ")) { line = "." + line; } if (line.StartsWith(".") || line.StartsWith("#")) { // #include has to be handled in this method if (line.StartsWith("#include") || line.StartsWith(".include")) { if (!IfStack.Peek()) continue; string includedFileName = line.Substring(line.IndexOf(" ") + 1); includedFileName = includedFileName.Trim('"', '\''); if (includedFileName.StartsWith("<") && includedFileName.EndsWith(">")) { // Find included file includedFileName = includedFileName.Trim('<', '>'); string[] paths = IncludePath.Split(new []{';'}, StringSplitOptions.RemoveEmptyEntries ); foreach (var path in paths) { if (File.Exists(Path.Combine(path, includedFileName))) { includedFileName = Path.Combine(path, includedFileName); break; } } } if (!File.Exists(includedFileName)) { listEntry.ErrorCode = ErrorCode.FileNotFound; output.Add(listEntry); } else if (IncludedFiles.Contains(includedFileName)) { } else { using (Stream includedFile = File.Open(includedFileName, FileMode.Open)) { StreamReader sr = new StreamReader(includedFile); string contents = sr.ReadToEnd(); sr.Close(); string[] newSource = contents.Replace("\r", "").Split('\n'); string[] newLines = new string[newSource.Length + lines.Length]; Array.Copy(lines, newLines, i); Array.Copy(newSource, 0, newLines, i, newSource.Length); newLines[i + newSource.Length] = "#endfile"; if (lines.Length > i + 1) Array.Copy(lines, i + 1, newLines, i + newSource.Length + 1, lines.Length - i - 1); lines = newLines; } WorkingDirectories.Push(Directory.GetCurrentDirectory()); if (Path.IsPathRooted(includedFileName)) Directory.SetCurrentDirectory(GetDirectory(includedFileName)); else Directory.SetCurrentDirectory(Path.Combine(Directory.GetCurrentDirectory(), GetDirectory(includedFileName))); FileNames.Push(includedFileName); LineNumbers.Push(0); IncludedFiles.Add(includedFileName); i--; continue; } } else if ((line.StartsWith("#incbin") || line.StartsWith(".incbin")) && !noList) { if (!IfStack.Peek()) continue; string includedFileName = line.Substring(line.IndexOf(" ") + 1); includedFileName = includedFileName.Trim('"', '\''); if (includedFileName.StartsWith("<") && includedFileName.EndsWith(">")) { // Find included file includedFileName = includedFileName.Trim('<', '>'); string[] paths = IncludePath.Split(';'); foreach (var path in paths) { if (File.Exists(Path.Combine(path, includedFileName))) { includedFileName = Path.Combine(path, includedFileName); break; } } } if (!File.Exists(includedFileName)) { listEntry.ErrorCode = ErrorCode.FileNotFound; output.Add(listEntry); } else { using (Stream includedFile = File.Open(includedFileName, FileMode.Open)) { byte[] rawData = new byte[includedFile.Length]; includedFile.Read(rawData, 0, (int)includedFile.Length); List<ushort> binOutput = new List<ushort>(); foreach (byte b in rawData) binOutput.Add(b); listEntry.Output = binOutput.ToArray(); output.Add(listEntry); output[output.Count - 1].CodeType = CodeType.Directive; if (!noList) currentAddress += (ushort)binOutput.Count; } } } else if ((line.StartsWith("#incpack") || line.StartsWith(".incpack")) && !noList) { if (!IfStack.Peek()) continue; string includedFileName = line.Substring(line.IndexOf(" ") + 1); includedFileName = includedFileName.Trim('"', '\''); if (includedFileName.StartsWith("<") && includedFileName.EndsWith(">")) { // Find included file includedFileName = includedFileName.Trim('<', '>'); string[] paths = IncludePath.Split(';'); foreach (var path in paths) { if (File.Exists(Path.Combine(path, includedFileName))) { includedFileName = Path.Combine(path, includedFileName); break; } } } if (!File.Exists(includedFileName)) { listEntry.ErrorCode = ErrorCode.FileNotFound; output.Add(listEntry); } else { using (Stream includedFile = File.Open(includedFileName, FileMode.Open)) { byte[] rawData = new byte[includedFile.Length]; includedFile.Read(rawData, 0, (int)includedFile.Length); List<ushort> binOutput = new List<ushort>(); for (int j = 0; j < rawData.Length; j += 2) { binOutput.Add((ushort)( rawData[j + 1] | (rawData[j] << 8) )); } listEntry.Output = binOutput.ToArray(); output.Add(listEntry); output[output.Count - 1].CodeType = CodeType.Directive; if (!noList) currentAddress += (ushort)binOutput.Count; } } } else if (line == "#endfile" || line == ".endfile") { if (!IfStack.Peek()) continue; FileNames.Pop(); LineNumbers.Pop(); RootLineNumber--; Directory.SetCurrentDirectory(WorkingDirectories.Pop()); } else if (line.StartsWith(".macro") && !noList) { if (!IfStack.Peek()) continue; string macroDefinition = line.Substring(7).Trim(); Macro macro = new Macro(); macro.Args = new string[0]; if (macroDefinition.EndsWith("{")) macroDefinition = macroDefinition.Remove(macroDefinition.Length - 1).Trim(); if (macroDefinition.Contains("(")) { string paramDefinition = macroDefinition.Substring(macroDefinition.IndexOf("(") + 1); macro.Name = macroDefinition.Remove(macroDefinition.IndexOf("(")).Trim(); if (!paramDefinition.EndsWith(")")) { listEntry.ErrorCode = ErrorCode.InvalidMacroDefintion; output.Add(listEntry); } else { paramDefinition = paramDefinition.Remove(paramDefinition.Length - 1); if (paramDefinition.Length > 0) { string[] parameters = paramDefinition.Split(','); bool continueEvaluation = true; for (int j = 0; j < parameters.Length; j++) { string parameter = parameters[j].Trim(); if (!char.IsLetter(parameter[0])) { continueEvaluation = false; break; } foreach (char c in parameter) { if (!char.IsLetterOrDigit(c) && c != '_') { continueEvaluation = false; break; } } if (!continueEvaluation) break; macro.Args = macro.Args.Concat(new string[] { parameter }).ToArray(); } if (!continueEvaluation) continue; } } } else macro.Name = macroDefinition; // Isolate macro code macro.Code = ""; bool foundEndmacro = false; string macroLine = line; i++; for (; i < lines.Length; i++) { line = lines[i].TrimComments().TrimExcessWhitespace(); LineNumbers.Push(LineNumbers.Pop() + 1); if (line == ".endmacro" || line == "#endmacro" || line == "}") { foundEndmacro = true; break; } if (line != "{") macro.Code += "\n" + line; } if (!foundEndmacro) { listEntry.ErrorCode = ErrorCode.UncoupledStatement; output.Add(listEntry); continue; } macro.Code = macro.Code.Trim('\n'); Macros.Add(macro); output.Add(new ListEntry(".macro " + macroDefinition, FileNames.Peek(), LineNumbers.Peek(), currentAddress, isFromExpanded)); output[output.Count - 1].CodeType = CodeType.Directive; foreach (var codeLine in macro.Code.Split('\n')) { output.Add( new ListEntry( codeLine, FileNames.Peek(), LineNumbers.Peek(), currentAddress, isFromExpanded ) ); output[output.Count - 1].CodeType = CodeType.Directive; } output.Add( new ListEntry( ".endmacro", FileNames.Peek(), LineNumbers.Peek(), currentAddress, isFromExpanded ) ); output[output.Count - 1].CodeType = CodeType.Directive; } else { // Parse preprocessor directives ParseDirectives( output, line, isFromExpanded ); } } else { if (!IfStack.Peek()) continue; // Search through macros bool mayHaveMacro = false; foreach (Macro macro in Macros) { if (line.StartsWith(macro.Name)) { mayHaveMacro = true; break; } } if (line.SafeContains('(') && line.SafeContains(')') && mayHaveMacro) { Macro userMacro = new Macro(); userMacro.Args = new string[0]; string macroDefinition = line; string paramDefinition = macroDefinition.Substring(macroDefinition.IndexOf("(") + 1); userMacro.Name = macroDefinition.Remove(macroDefinition.IndexOf("(")).Trim(); if (!paramDefinition.EndsWith(")")) { listEntry.ErrorCode = ErrorCode.InvalidMacroDefintion; output.Add(listEntry); } else { paramDefinition = paramDefinition.Remove(paramDefinition.Length - 1); if (paramDefinition.Length > 0) { string[] parameters = paramDefinition.SafeSplit(','); for (int j = 0; j < parameters.Length; j++) { string parameter = parameters[j].Trim(); userMacro.Args = userMacro.Args.Concat(new[] { parameter }).ToArray(); } } } bool macroMatched = false; foreach (Macro macro in Macros) { if (macro.Name == userMacro.Name && macro.Args.Length == userMacro.Args.Length) { // Expand the macro userMacro.Code = macro.Code; for (int j = 0; j < macro.Args.Length; j++) userMacro.Code = userMacro.Code.Replace(macro.Args[j], userMacro.Args[j]); string[] macroCode = userMacro.Code.Replace("\r", "\n").Split('\n'); string[] newLines = new string[lines.Length + macroCode.Length - 1]; Array.Copy(lines, 0, newLines, 0, i); Array.Copy(macroCode, 0, newLines, i, macroCode.Length); if (lines.Length > i + 1) Array.Copy(lines, i + 1, newLines, i + macroCode.Length, lines.Length - i - 1); lines = newLines; output.Add(listEntry); output[output.Count - 1].CodeType = CodeType.Directive; line = lines[i].TrimComments().TrimExcessWhitespace(); macroMatched = true; SuspendedLineCounts.Push(macroCode.Length); // Suspend the line counts for the expanded macro } } if (macroMatched) { i--; continue; } // We'll just let the opcode matcher yell at them if it isn't found } // Check for OPCodes var opcode = MatchString(line, OpcodeTable); bool nonBasic = false; if (opcode == null) { opcode = MatchString(line, NonBasicOpcodeTable); nonBasic = true; } if (opcode == null) { listEntry.ErrorCode = ErrorCode.InvalidOpcode; output.Add(listEntry); continue; } else { listEntry.Opcode = opcode; StringMatch valueA = null, valueB = null; listEntry.Output = new ushort[1]; if (!nonBasic) { listEntry.CodeType = CodeType.BasicInstruction; if (opcode.valueA != null) valueA = MatchString(opcode.valueA, ValueTable); if (opcode.valueB != null) valueB = MatchString(opcode.valueB, ValueTable); if (nonBasic == false && (opcode.valueA == null || opcode.valueB == null)) { listEntry.ErrorCode = ErrorCode.InvalidOpcode; output.Add(listEntry); continue; } if (valueA.value == valueB.value && valueA.value != 0x1E && valueB.value != 0x1E && opcode.value == 0x1) listEntry.WarningCode = WarningCode.RedundantStatement; if (valueB.value == 0x1F && !opcode.match.Contains("IF")) listEntry.WarningCode = WarningCode.AssignToLiteral; listEntry.ValueA = valueA; listEntry.ValueB = valueB; // De-localize labels if (listEntry.ValueA.isLiteral) { listEntry.Output = listEntry.Output.Concat(new ushort[1]).ToArray(); var result = ParseExpression(listEntry.ValueA.literal); foreach (var reference in result.References) { if (reference.StartsWith(".")) listEntry.ValueA.literal = listEntry.ValueA.literal.Replace(reference, PriorGlobalLabel + "_" + reference.Substring(1)); } } if (listEntry.ValueB.isLiteral) { listEntry.Output = listEntry.Output.Concat(new ushort[1]).ToArray(); var result = ParseExpression(listEntry.ValueB.literal); foreach (var reference in result.References) { if (reference.StartsWith(".")) listEntry.ValueB.literal = listEntry.ValueB.literal.Replace(reference, PriorGlobalLabel + "_" + reference.Substring(1)); } } } else { listEntry.CodeType = CodeType.NonBasicInstruction; if (opcode.valueA != null) valueA = MatchString(opcode.valueA, ValueTable); listEntry.ValueA = valueA; // De-localize labels if (listEntry.ValueA.isLiteral) { listEntry.Output = listEntry.Output.Concat(new ushort[1]).ToArray(); var result = ParseExpression(listEntry.ValueA.literal); foreach (var reference in result.References) { if (reference.StartsWith(".") || reference.StartsWith("_")) listEntry.ValueA.literal = listEntry.ValueA.literal.Replace(reference, PriorGlobalLabel + "_" + reference.Substring(1)); } } } output.Add(listEntry); currentAddress++; if (valueA != null) if (valueA.isLiteral) currentAddress++; if (valueB != null) if (valueB.isLiteral) currentAddress++; } } } return EvaluateAssembly(output); }
void assembler_HandleCodeLine(object sender, HandleCodeEventArgs e) { // Handle custom directives if (e.Code.ToLower().StartsWith(".prereq")) { CurrentSection = e.Code.Substring(7).Trim(); e.Handled = true; } else if (e.Code.ToLower().StartsWith(".test")) { if (IsInTest) { e.Handled = true; // Error } string code = e.Code; if (TestFile == null) Assembler.noList = true; else e.Code = "SET PC, end_test_" + e.Code.Substring(5).Trim(); // change the code to jump past the test under normal conditions Tests.Add(new UnitTest(code.Substring(5).Trim(), Assembler.currentAddress)); IsInTest = true; } else if (e.Code.ToLower().StartsWith(".endtest")) { if (!IsInTest) e.Output.ErrorCode = ErrorCode.UncoupledStatement; else Tests[Tests.Count - 1].EndAddress = Assembler.currentAddress; e.Code = "end_test_" + Tests[Tests.Count - 1].Name + ":"; IsInTest = false; } else if (e.Code.ToLower().StartsWith(".assert")) { Tests[Tests.Count - 1].Assersions.Add(new Assertion() { Address = Assembler.currentAddress, Expression = e.Code.Substring(7).Trim() }); e.Handled = true; } else if (e.Code.ToLower().StartsWith(".dump")) { // TODO } else if (e.Code.ToLower().StartsWith(".log")) { // TODO } e.Output.Tags["dunit-section"] = CurrentSection; }