int WriteOutput(string disassembly) { // no errors finish up // save to disk var section = _services.Options.OutputSection; var outputFile = _services.Options.OutputFile; var objectCode = _services.Output.GetCompilation(section); if (!string.IsNullOrEmpty(_services.Options.Patch)) { if (!string.IsNullOrEmpty(_services.Options.OutputFile) || !string.IsNullOrEmpty(_services.Options.Format)) { _services.Log.LogEntrySimple("Output options ignored for patch mode.", false); } if (_services.Options.LabelsAddressesOnly && string.IsNullOrEmpty(_services.Options.LabelFile)) { _services.Log.LogEntrySimple("Label listing not specified; option '-labels-addresses-only' ignored.", false); } try { var offsetLine = new Preprocessor(_processorOptions).ProcessDefine("patch=" + _services.Options.Patch); var patchExp = offsetLine.Operands.GetIterator(); var offset = _services.Evaluator.Evaluate(patchExp, 0, ushort.MaxValue); if (patchExp.Current != null || _services.PassNeeded) { throw new Exception(); } var filePath = Preprocessor.GetFullPath(outputFile, _services.Options.IncludePath); var fileBytes = File.ReadAllBytes(filePath); Array.Copy(objectCode.ToArray(), 0, fileBytes, (int)offset, objectCode.Count); File.WriteAllBytes(filePath, fileBytes); } catch { throw new Exception($"Cannot patch file \"{outputFile}\". One or more arguments is not valid."); } } else { var formatProvider = _services.FormatSelector?.Invoke(_services.CPU, _services.OutputFormat); if (formatProvider != null) { var startAddress = _services.Output.ProgramStart; if (!string.IsNullOrEmpty(section)) { startAddress = _services.Output.GetSectionStart(section); } var format = _services.Options.CaseSensitive ? _services.OutputFormat : _services.OutputFormat.ToLower(); var info = new FormatInfo(outputFile, format, startAddress, objectCode); File.WriteAllBytes(outputFile, formatProvider.GetFormat(info).ToArray()); } else { File.WriteAllBytes(outputFile, objectCode.ToArray()); } } // write disassembly if (!string.IsNullOrEmpty(disassembly) && !string.IsNullOrEmpty(_services.Options.ListingFile)) { File.WriteAllText(_services.Options.ListingFile, disassembly); } // write labels if (!string.IsNullOrEmpty(_services.Options.LabelFile)) { File.WriteAllText(_services.Options.LabelFile, _services.SymbolManager.ListLabels(!_services.Options.LabelsAddressesOnly)); } if (_services.Log.HasWarnings) { _services.Log.DumpWarnings(); } if (!_services.Options.NoStats) { Console.WriteLine("\n*********************************"); Console.WriteLine($"Assembly start: ${_services.Output.ProgramStart:X4}"); if (_services.Output.ProgramEnd > BinaryOutput.MaxAddress && _services.Options.LongAddressing) { Console.WriteLine($"Assembly end: ${_services.Output.ProgramEnd:X6}"); } else { Console.WriteLine($"Assembly end: ${_services.Output.ProgramEnd & BinaryOutput.MaxAddress:X4}"); } Console.WriteLine($"Passes: {_services.CurrentPass + 1}"); } return(objectCode.Count); }
/// <summary> /// Begin the assembly process. /// </summary> /// <returns>The time in seconds for the assembly to complete.</returns> /// <exception cref="Exception"></exception> public double Assemble() { if (_services.Log.HasErrors) { _services.Log.DumpErrors(); return(double.NaN); } if (_services.Options.InputFiles.Count == 0) { throw new Exception("One or more required input files was not specified."); } var stopWatch = new Stopwatch(); stopWatch.Start(); // process cmd line args if (_services.Options.Quiet) { Console.SetOut(TextWriter.Null); } var preprocessor = new Preprocessor(_processorOptions); var processed = new List <SourceLine>(); try { // preprocess all passed option defines and sections foreach (var define in _services.Options.LabelDefines) { processed.Add(preprocessor.ProcessDefine(define)); } foreach (var section in _services.Options.Sections) { processed.AddRange(preprocessor.Process(string.Empty, processed.Count, $".dsection {section}")); } // preprocess all input files foreach (var inputFile in _services.Options.InputFiles) { processed.AddRange(preprocessor.Process(inputFile)); } if (!_services.Options.NoStats) { Console.WriteLine($"{Assembler.AssemblerName}"); Console.WriteLine($"{Assembler.AssemblerVersion}"); } var multiLineAssembler = new MultiLineAssembler() .WithAssemblers(_assemblers) .WithOptions(new MultiLineAssembler.Options { AllowReturn = false, DisassembleAll = _services.Options.VerboseList, ErrorHandler = AssemblyErrorHandler, StopDisassembly = () => _services.PrintOff, Evaluator = _services.Evaluator }); var disassembly = string.Empty; // while passes are needed while (_services.PassNeeded && !_services.Log.HasErrors) { if (_services.DoNewPass() == 4) { throw new Exception("Too many passes attempted."); } _ = multiLineAssembler.AssembleLines(processed.GetIterator(), out disassembly); if (!_services.PassNeeded && _services.CurrentPass == 0) { _services.PassNeeded = _services.SymbolManager.SearchedNotFound; } } if (!_services.Options.WarnNotUnusedSections && !_services.Log.HasErrors) { var unused = _services.Output.UnusedSections; if (unused.Any()) { foreach (var section in unused) { _services.Log.LogEntrySimple($"Section {section} was defined but never used.", false); } } } if (!_services.Options.NoWarnings && _services.Log.HasWarnings) { _services.Log.DumpWarnings(); } var byteCount = 0; if (_services.Log.HasErrors) { _services.Log.DumpErrors(); } else { var passedArgs = _services.Options.GetPassedArgs(); var exec = Process.GetCurrentProcess().MainModule.ModuleName; var inputFiles = string.Join("\n// ", preprocessor.GetInputFiles()); var fullDisasm = $"// {Assembler.AssemblerNameSimple}\n" + $"// {exec} {string.Join(' ', passedArgs)}\n" + $"// {DateTime.Now:f}\n\n// Input files:\n\n" + $"// {inputFiles}\n\n" + disassembly; byteCount = WriteOutput(fullDisasm); } if (!_services.Options.NoStats) { Console.WriteLine($"Number of errors: {_services.Log.ErrorCount}"); Console.WriteLine($"Number of warnings: {_services.Log.WarningCount}"); } stopWatch.Stop(); var ts = stopWatch.Elapsed.TotalSeconds; if (!_services.Log.HasErrors) { var section = _services.Options.OutputSection; if (!_services.Options.NoStats) { if (!string.IsNullOrEmpty(section)) { Console.Write($"[{section}] "); } if (!string.IsNullOrEmpty(_services.Options.Patch)) { Console.WriteLine($"{byteCount} (Offs:{_services.Options.Patch}), {ts} sec."); } else { Console.WriteLine($"{byteCount} bytes, {ts} sec."); } if (_services.Options.ShowChecksums) { Console.WriteLine($"Checksum: {_services.Output.GetOutputHash(section)}"); } Console.WriteLine("*********************************"); Console.Write("Assembly completed successfully."); } } else { _services.Log.ClearAll(); } return(ts); } catch (Exception ex) { _services.Log.LogEntrySimple(ex.Message); return(double.NaN); } finally { if (_services.Log.HasErrors) { _services.Log.DumpErrors(); } } }
/// <summary> /// Constructs a new instance of an <see cref="AssemblyController"/>, which controls /// the assembly process. /// </summary> /// <param name="args">The commandline arguments.</param> /// <param name="cpuSetHandler">The <see cref="CpuAssembler"/> selection handler.</param> /// <param name="formatSelector">The format selector.</param> /// <exception cref="ArgumentNullException"></exception> public AssemblyController(IEnumerable <string> args, Func <string, AssemblyServices, CpuAssembler> cpuSetHandler, Func <string, string, IBinaryFormatProvider> formatSelector) { if (args == null || cpuSetHandler == null || formatSelector == null) { throw new ArgumentNullException(); } _services = new AssemblyServices(Options.FromArgs(args)); _services.PassChanged += (s, a) => _services.Output.Reset(); _services.PassChanged += (s, a) => _services.SymbolManager.Reset(); _services.FormatSelector = formatSelector; _processorOptions = new ProcessorOptions { CaseSensitive = _services.Options.CaseSensitive, Log = _services.Log, IncludePath = _services.Options.IncludePath, IgnoreCommentColons = _services.Options.IgnoreColons, WarnOnLabelLeft = _services.Options.WarnLeft, InstructionLookup = symbol => _services.InstructionLookupRules.Any(ilr => ilr(symbol)) }; CpuAssembler cpuAssembler = null; var cpu = _services.Options.CPU; if (!string.IsNullOrEmpty(cpu)) { cpuAssembler = cpuSetHandler(cpu, _services); } if (_services.Options.InputFiles.Count > 0) { var src = new Preprocessor(_processorOptions).ProcessToFirstDirective(_services.Options.InputFiles[0]); if (src != null && src.Instruction != null && src.Instruction.Name.Equals(".cpu", _services.StringViewComparer)) { if (src.Operands.Count != 1 || !src.Operands[0].IsDoubleQuote()) { _services.Log.LogEntry(src.Instruction, "Invalid expression for directive \".cpu\"."); } else { cpu = src.Operands[0].Name.ToString().TrimOnce('"'); } } } _services.CPU = cpu; if (!string.IsNullOrEmpty(cpu) && cpuAssembler != null && !cpuAssembler.IsCpuValid(cpu)) { _services.Log.LogEntrySimple($"Invalid CPU \"{cpu}\" specified."); } else { if (cpuAssembler == null) { cpuAssembler = cpuSetHandler(cpu, _services); } _assemblers = new List <AssemblerBase> { new AssignmentAssembler(_services), new BlockAssembler(_services), new EncodingAssembler(_services), new PseudoAssembler(_services), new MiscAssembler(_services), cpuAssembler }; _processorOptions.IsMacroNameValid = symbol => !_assemblers.Any(asm => asm.Assembles(symbol)); _processorOptions.LineTerminates = _services.LineTerminates; } }
/// <summary> /// Begin the assembly process. /// </summary> /// <exception cref="Exception"></exception> public void Assemble() { var stopWatch = new Stopwatch(); stopWatch.Start(); try { // process cmd line args if (Assembler.Options.Quiet) { Console.SetOut(TextWriter.Null); } Console.WriteLine(Assembler.AssemblerName); Console.WriteLine(Assembler.AssemblerVersion); // init all line assemblers var multiLineAssembler = new MultiLineAssembler(); _assemblers.Add(multiLineAssembler); _assemblers.Add(new AssignmentAssembler()); _assemblers.Add(new EncodingAssembler()); _assemblers.Add(new PseudoAssembler()); _assemblers.Add(new MiscAssembler()); // preprocess all input files var preprocessor = new Preprocessor(); var processed = new List <SourceLine>(); // define all passed option defines if (Assembler.Options.LabelDefines.Count > 0) { foreach (var define in Assembler.Options.LabelDefines) { processed.AddRange(preprocessor.PreprocessSource(define)); } } foreach (var path in Assembler.Options.InputFiles) { processed.AddRange(preprocessor.PreprocessFile(path)); } // set the iterator Assembler.LineIterator = processed.GetIterator(); var exec = Process.GetCurrentProcess().MainModule.ModuleName; var argsPassed = string.Join(' ', Assembler.Options.Arguments); var inputFiles = string.Join("\n// ", preprocessor.GetInputFiles()); var disasmHeader = $"// {Assembler.AssemblerNameSimple}\n// {exec} {argsPassed}\n// {DateTime.Now:f}\n\n// Input files:" + $"\n\n// {inputFiles}\n\n"; StringBuilder disassembly = null; // while passes are needed while (Assembler.PassNeeded && !Assembler.Log.HasErrors) { if (Assembler.CurrentPass++ == 4) { throw new Exception("Too many passes attempted."); } disassembly = new StringBuilder(disasmHeader); foreach (var line in Assembler.LineIterator) { try { if (line.Label != null || line.Instruction != null) { var asm = _assemblers.FirstOrDefault(a => a.AssemblesLine(line)); if (asm != null) { var disasm = asm.AssembleLine(line); if (!string.IsNullOrWhiteSpace(disasm) && !Assembler.PrintOff) { disassembly.AppendLine(disasm); } } else if (line.Instruction != null) { Assembler.Log.LogEntry(line, line.Instruction.Position, $"Unknown instruction \"{line.InstructionName}\"."); } } else if (Assembler.Options.VerboseList) { disassembly.AppendLine(line.UnparsedSource.PadLeft(50, ' ')); } } catch (Exception ex) { if (ex is SymbolException symbEx) { Assembler.Log.LogEntry(line, symbEx.Position, symbEx.Message, true); } else if (ex is FormatException fmtEx) { Assembler.Log.LogEntry(line, line.Operand, $"There was a problem with the format string:\n{fmtEx.Message}.", true); } else if (Assembler.CurrentPass > 0 && !Assembler.PassNeeded) { if (ex is ExpressionException expEx) { if (ex is IllegalQuantityException) { Assembler.Log.LogEntry(line, expEx.Position, $"Illegal quantity for \"{line.Instruction}\" in expression \"{line.Operand}\"."); } else { Assembler.Log.LogEntry(line, expEx.Position, ex.Message); } } else if (ex is OverflowException) { Assembler.Log.LogEntry(line, line.Operand.Position, $"Illegal quantity for \"{line.Instruction}\" in expression \"{line.Operand}\"."); } else if (ex is InvalidPCAssignmentException pcEx) { Assembler.Log.LogEntry(line, line.Instruction, $"Invalid Program Counter assignment in expression \"{line.Operand}\"."); } else if (ex is ProgramOverflowException prgEx) { Assembler.Log.LogEntry(line, line.Instruction, "Program Overflow."); } else { Assembler.Log.LogEntry(line, line.Operand.Position, ex.Message); } } else { Assembler.PassNeeded = true; } } } if (multiLineAssembler.InAnActiveBlock) { SourceLine activeLine = multiLineAssembler.ActiveBlockLine; Assembler.Log.LogEntry(activeLine, activeLine.Instruction, $"End of source file reached before finding block closure for \"{activeLine.InstructionName}\"."); } Assembler.LineIterator.Reset(); } if (!Assembler.Options.NoWarnings && Assembler.Log.HasWarnings) { Assembler.Log.DumpWarnings(); } if (Assembler.Log.HasErrors) { Assembler.Log.DumpErrors(); } else { WriteOutput(disassembly?.ToString()); } Console.WriteLine($"Number of errors: {Assembler.Log.ErrorCount}"); Console.WriteLine($"Number of warnings: {Assembler.Log.WarningCount}"); if (!Assembler.Log.HasErrors) { stopWatch.Stop(); var ts = stopWatch.Elapsed.TotalSeconds; Console.WriteLine($"{Assembler.Output.GetCompilation().Count} bytes, {ts} sec."); if (Assembler.Options.ShowChecksums) { Console.WriteLine($"Checksum: {Assembler.Output.GetOutputHash()}"); } Console.WriteLine("*********************************"); Console.WriteLine("Assembly completed successfully."); } } catch (Exception ex) { Console.Error.WriteLine(ex.Message); } }