Exemple #1
0
        public static string GenerateListing(AssemblyOutput output)
        {
            // I know this can be optimized, I might optmize it eventually
            int maxLineNumber   = output.Listing.Max(l => l.CodeType == CodeType.Directive ? 0 : l.LineNumber).ToString().Length;
            int maxFileLength   = output.Listing.Max(l => l.FileName.Length);
            int maxBinaryLength = output.Listing.Max(l =>
            {
                if (l.Output == null || l.Output.Length == 0)
                {
                    return(0);
                }
                return(l.Output.Length * 3 - 1);
            });
            int    addressLength       = output.InstructionSet.WordSize / 4 + 2;
            string formatString        = "{0,-" + maxFileLength + "}:{1,-" + maxLineNumber + "} ({2}): {3,-" + maxBinaryLength + "}  {4}" + Environment.NewLine;
            string errorFormatString   = "{0,-" + maxFileLength + "}:{1,-" + maxLineNumber + "} {2}: {3}" + Environment.NewLine;
            string addressFormatString = "X" + addressLength;
            // Listing format looks something like this:
            // file.asm/1 (0x1234): DE AD BE EF    ld a, 0xBEEF
            // file.asm/2 (0x1236):              label:
            // file.asm/3 (0x1236):              #directive
            var    builder = new StringBuilder();
            string file, address, binary, code;
            int    line;

            foreach (var entry in output.Listing)
            {
                file    = entry.FileName;
                line    = entry.LineNumber;
                address = "0x" + entry.Address.ToString(addressFormatString);
                code    = entry.Code;
                if (entry.Output != null && entry.Output.Length != 0 && entry.CodeType != CodeType.Directive)
                {
                    binary = string.Empty;
                    for (int i = 0; i < entry.Output.Length; i++)
                    {
                        binary += entry.Output[i].ToString("X2") + " ";
                    }
                    binary = binary.Remove(binary.Length - 1);
                    code   = "  " + code;
                }
                else
                {
                    binary = string.Empty;
                }
                if (entry.Error != AssemblyError.None)
                {
                    builder.AppendFormat(errorFormatString, file, line, "Error", entry.Error);
                }
                if (entry.Warning != AssemblyWarning.None)
                {
                    builder.AppendFormat(errorFormatString, file, line, "Warning", entry.Warning);
                }
                builder.AppendFormat(formatString, file, line, address, binary, code);
            }
            return(builder.ToString());
        }
Exemple #2
0
 public static string GenerateListing(AssemblyOutput output)
 {
     // I know this can be optimized, I might optmize it eventually
     int maxLineNumber = output.Listing.Max(l => l.CodeType == CodeType.Directive ? 0 : l.LineNumber).ToString().Length;
     int maxFileLength = output.Listing.Max(l => l.FileName.Length);
     int maxBinaryLength = output.Listing.Max(l =>
         {
             if (l.Output == null || l.Output.Length == 0)
                 return 0;
             return l.Output.Length * 3 - 1;
         });
     int addressLength = output.InstructionSet.WordSize / 4 + 2;
     string formatString = "{0,-" + maxFileLength + "}:{1,-" + maxLineNumber + "} ({2}): {3,-" + maxBinaryLength + "}  {4}" + Environment.NewLine;
     string errorFormatString = "{0,-" + maxFileLength + "}:{1,-" + maxLineNumber + "} {2}: {3}" + Environment.NewLine;
     string addressFormatString = "X" + addressLength;
     // Listing format looks something like this:
     // file.asm/1 (0x1234): DE AD BE EF    ld a, 0xBEEF
     // file.asm/2 (0x1236):              label:
     // file.asm/3 (0x1236):              #directive
     var builder = new StringBuilder();
     string file, address, binary, code;
     int line;
     foreach (var entry in output.Listing)
     {
         file = entry.FileName;
         line = entry.LineNumber;
         address = "0x" + entry.Address.ToString(addressFormatString);
         code = entry.Code;
         if (entry.Output != null && entry.Output.Length != 0 && entry.CodeType != CodeType.Directive)
         {
             binary = string.Empty;
             for (int i = 0; i < entry.Output.Length; i++)
                 binary += entry.Output[i].ToString("X2") + " ";
             binary = binary.Remove(binary.Length - 1);
             code = "  " + code;
         }
         else
             binary = string.Empty;
         if (entry.Error != AssemblyError.None)
             builder.AppendFormat(errorFormatString, file, line, "Error", entry.Error);
         if (entry.Warning != AssemblyWarning.None)
             builder.AppendFormat(errorFormatString, file, line, "Warning", entry.Warning);
         builder.AppendFormat(formatString, file, line, address, binary, code);
     }
     return builder.ToString();
 }
Exemple #3
0
 private AssemblyOutput Finish(AssemblyOutput output)
 {
     var finalBinary = new List<byte>();
     ExpressionEngine.LastGlobalLabel = null;
     for (int i = 0; i < output.Listing.Count; i++)
     {
         var entry = output.Listing[i];
         RootLineNumber = entry.RootLineNumber;
         PC = entry.Address;
         LineNumbers = new Stack<int>(new[] { entry.LineNumber });
         if (entry.CodeType == CodeType.Directive)
         {
             if (entry.PostponeEvalulation)
                 output.Listing[i] = HandleDirective(entry.Code, true);
             if (output.Listing[i].Output != null)
                 finalBinary.AddRange(output.Listing[i].Output);
             continue;
         }
         if (entry.Error != AssemblyError.None)
             continue;
         if (entry.CodeType == CodeType.Label)
         {
             var name = entry.Code.Remove(entry.Code.IndexOf(':')).Trim(':').ToLower();
             if (!name.StartsWith(".") && name != "_")
                 ExpressionEngine.LastGlobalLabel = name;
         }
         else if (entry.CodeType == CodeType.Instruction)
         {
             // Assemble output string
             string instruction = entry.Instruction.Value.ToLower();
             foreach (var operand in entry.Instruction.Operands)
                 instruction = instruction.Replace("@" + operand.Key, operand.Value.Value);
             foreach (var value in entry.Instruction.ImmediateValues)
             {
                 try
                 {
                     bool truncated;
                     if (value.Value.RelativeToPC)
                     {
                         var exp = ExpressionEngine.Evaluate(value.Value.Value, entry.Address, entry.RootLineNumber);
                         instruction = instruction.Replace("^" + value.Key, ConvertToBinary(
                             (exp - entry.Instruction.Length) - entry.Address,
                             value.Value.Bits, true, out truncated));
                     }
                     else if (value.Value.RstOnly)
                     {
                         truncated = false;
                         var rst = (byte)ExpressionEngine.Evaluate(value.Value.Value, entry.Address, entry.RootLineNumber);
                         if ((rst & ~0x7) != rst || rst > 0x38)
                             entry.Error = AssemblyError.InvalidExpression;
                         else
                         {
                             instruction = instruction.Replace("&" + value.Key,
                                 ConvertToBinary((ulong)rst >> 3, 3, false, out truncated));
                         }
                     }
                     else
                         instruction = instruction.Replace("%" + value.Key, ConvertToBinary(
                             ExpressionEngine.Evaluate(value.Value.Value, entry.Address, entry.RootLineNumber),
                             value.Value.Bits, false, out truncated));
                     if (truncated)
                         entry.Error = AssemblyError.ValueTruncated;
                 }
                 catch (KeyNotFoundException)
                 {
                     entry.Error = AssemblyError.UnknownSymbol;
                 }
                 catch (InvalidOperationException)
                 {
                     entry.Error = AssemblyError.InvalidExpression;
                 }
             }
             if (entry.Error == AssemblyError.None)
             {
                 entry.Output = ExpressionEngine.ConvertFromBinary(instruction);
                 finalBinary.AddRange(entry.Output);
             }
             else
                 finalBinary.AddRange(new byte[entry.Instruction.Length]);
         }
     }
     output.Data = finalBinary.ToArray();
     return output;
 }
Exemple #4
0
        public AssemblyOutput Assemble(string assembly, string fileName = null)
        {
            Output = new AssemblyOutput();
            Output.InstructionSet = InstructionSet;
            assembly = assembly.Replace("\r", "");
            PC = 0;
            Lines = assembly.Split('\n');
            FileNames.Push(Path.GetFileName(fileName));
            LineNumbers.Push(0);
            RootLineNumber = 0;
            IfStack.Push(true);
            for (CurrentIndex = 0; CurrentIndex < Lines.Length; CurrentIndex++)
            {
                CurrentLine = Lines[CurrentIndex].Trim().TrimComments();
                if (SuspendedLines == 0)
                {
                    LineNumbers.Push(LineNumbers.Pop() + 1);
                    RootLineNumber++;
                }
                else
                    SuspendedLines--;

                if (!IfStack.Peek())
                {
                    bool match = false;
                    if (CurrentLine.StartsWith("#") || CurrentLine.StartsWith("."))
                    {
                        var directive = CurrentLine.Substring(1);
                        if (CurrentLine.Contains((' ')))
                            directive = directive.Remove(CurrentLine.IndexOf((' '))).Trim();
                        if (ifDirectives.Contains(directive.ToLower()))
                            match = true;
                    }
                    if (!match)
                        continue;
                }

                if (CurrentLine.SafeContains(".equ") && !CurrentLine.StartsWith(".equ"))
                {
                    var name = CurrentLine.Remove(CurrentLine.SafeIndexOf(".equ"));
                    var definition = CurrentLine.Substring(CurrentLine.SafeIndexOf(".equ") + 4);
                    CurrentLine = ".equ " + name.Trim() + " " + definition.Trim();
                }

                // Check for macro
                if (!CurrentLine.StartsWith(".macro") && !CurrentLine.StartsWith("#macro") && !CurrentLine.StartsWith(".undefine") && !CurrentLine.StartsWith("#undefine"))
                {
                    Macro macroMatch = null;
                    string[] parameters = null;
                    string parameterDefinition = null;
                    foreach (var macro in Macros)
                    {
                        if (CurrentLine.ToLower().SafeContains(macro.Name))
                        {
                            // Try to match
                            int startIndex = CurrentLine.ToLower().SafeIndexOf(macro.Name);
                            int endIndex = startIndex + macro.Name.Length - 1;
                            if (macro.Parameters.Length != 0)
                            {
                                if (endIndex + 1 >= CurrentLine.Length)
                                    continue;
                                if (CurrentLine.Length < endIndex + 1 || CurrentLine[endIndex + 1] != '(')
                                    continue;
                                if (macroMatch != null && macro.Name.Length < macroMatch.Name.Length)
                                    continue;
                                parameterDefinition = CurrentLine.Substring(endIndex + 2, CurrentLine.LastIndexOf(')') - (endIndex + 2));
                                parameters = parameterDefinition.SafeSplit(',');
                                if (parameters.Length != macro.Parameters.Length)
                                    continue;
                                // Matched
                                macroMatch = macro;
                            }
                            else
                                macroMatch = macro;
                        }
                    }
                    if (macroMatch != null)
                    {
                        // Add an entry to the listing
                        AddOutput(CodeType.Directive);
                        var code = macroMatch.Code;
                        int index = 0;
                        foreach (var parameter in macroMatch.Parameters)
                            code = code.Replace(parameter.Trim(), parameters[index++].Trim());
                        string newLine;
                        if (parameterDefinition != null)
                            newLine = CurrentLine.Replace(macroMatch.Name + "(" + parameterDefinition + ")", code, StringComparison.InvariantCultureIgnoreCase);
                        else
                        {
                            if (CurrentLine.Substring(CurrentLine.ToLower().IndexOf(macroMatch.Name) + macroMatch.Name.Length).StartsWith("()"))
                                newLine = CurrentLine.Replace(macroMatch.Name + "()", code, StringComparison.InvariantCultureIgnoreCase);
                            else
                                newLine = CurrentLine.Replace(macroMatch.Name, code, StringComparison.InvariantCultureIgnoreCase);
                        }
                        var newLines = newLine.Replace("\r\n", "\n").Split('\n');
                        SuspendedLines += newLines.Length;
                        // Insert macro
                        Lines = Lines.Take(CurrentIndex).Concat(newLines).Concat(Lines.Skip(CurrentIndex + 1)).ToArray();
                        CurrentIndex--;
                        continue;
                    }
                }

                // Find same-line labels
                if (CurrentLine.Contains(":"))
                {
                    int length = 0;
                    bool isLabel = true;
                    for (int j = 0; j < CurrentLine.Length; j++)
                    {
                        if (char.IsLetterOrDigit(CurrentLine[j]) || CurrentLine[j] == '_')
                            length++;
                        else if (CurrentLine[j] == ':')
                            break;
                        else
                        {
                            isLabel = false;
                            break;
                        }
                    }
                    if (isLabel)
                    {
                        var label = CurrentLine.Remove(length).ToLower();
                        label = label.ToLower();
                        if (label == "_")
                        {
                            // Relative
                            ExpressionEngine.RelativeLabels.Add(new RelativeLabel
                            {
                                Address = PC,
                                RootLineNumber = RootLineNumber
                            });
                            AddOutput(CodeType.Label);
                        }
                        else
                        {
                            bool local = label.StartsWith(".");
                            if (local)
                                label = label.Substring(1) + "@" + ExpressionEngine.LastGlobalLabel;
                            bool valid = true;
                            for (int k = 0; k < label.Length; k++) // Validate label
                            {
                                if (!char.IsLetterOrDigit(label[k]) && label[k] != '_')
                                {
                                    if (local && label[k] == '@')
                                        continue;
                                    valid = false;
                                    break;
                                }
                            }
                            if (!valid)
                                AddError(CodeType.Label, AssemblyError.InvalidLabel);
                            else if (ExpressionEngine.Symbols.ContainsKey(label.ToLower()))
                                AddError(CodeType.Label, AssemblyError.DuplicateName);
                            else
                            {
                                AddOutput(CodeType.Label);
                                ExpressionEngine.Symbols.Add(label.ToLower(), new Symbol(PC, true));
                                if (!local)
                                    ExpressionEngine.LastGlobalLabel = label.ToLower();
                            }
                        }
                        CurrentLine = CurrentLine.Substring(length + 1).Trim();
                    }
                }

                if (CurrentLine.StartsWith(":") || CurrentLine.EndsWith(":")) // Label
                {
                    string label;
                    if (CurrentLine.StartsWith(":"))
                        label = CurrentLine.Substring(1).Trim();
                    else
                        label = CurrentLine.Remove(CurrentLine.Length - 1).Trim();
                    label = label.ToLower();
                    if (label == "_")
                    {
                        // Relative
                        ExpressionEngine.RelativeLabels.Add(new RelativeLabel
                        {
                            Address = PC,
                            RootLineNumber = RootLineNumber
                        });
                        AddOutput(CodeType.Label);
                    }
                    else
                    {
                        bool local = label.StartsWith(".");
                        if (local)
                            label = label.Substring(1) + "@" + ExpressionEngine.LastGlobalLabel;
                        bool valid = true;
                        for (int k = 0; k < label.Length; k++) // Validate label
                        {
                            if (!char.IsLetterOrDigit(label[k]) && label[k] != '_')
                            {
                                if (local && label[k] == '@')
                                    continue;
                                valid = false;
                                break;
                            }
                        }
                        if (!valid)
                            AddError(CodeType.Label, AssemblyError.InvalidLabel);
                        else if (ExpressionEngine.Symbols.ContainsKey(label.ToLower()))
                            AddError(CodeType.Label, AssemblyError.DuplicateName);
                        else
                        {
                            AddOutput(CodeType.Label);
                            ExpressionEngine.Symbols.Add(label.ToLower(), new Symbol(PC, true));
                            if (!local)
                                ExpressionEngine.LastGlobalLabel = label.ToLower();
                        }
                    }
                    continue;
                }

                if (CurrentLine.SafeContains('\\'))
                {
                    // Split lines up
                    var split = CurrentLine.SafeSplit('\\');
                    Lines = Lines.Take(CurrentIndex).Concat(split).
                        Concat(Lines.Skip(CurrentIndex + 1)).ToArray();
                    SuspendedLines = split.Length;
                    CurrentIndex--;
                    continue;
                }

                if (CurrentLine.StartsWith(".") || CurrentLine.StartsWith("#")) // Directive
                {
                    // Some directives need to be handled higher up
                    var directive = CurrentLine.Substring(1).Trim();
                    string[] parameters = new string[0];
                    if (directive.SafeIndexOf(' ') != -1)
                        parameters = directive.Substring(directive.SafeIndexOf(' ')).Trim().SafeSplit(' ');
                    if (directive.ToLower().StartsWith("macro"))
                    {
                        var definitionLine = CurrentLine; // Used to update the listing later
                        if (parameters.Length == 0)
                        {
                            AddError(CodeType.Directive, AssemblyError.InvalidDirective);
                            continue;
                        }
                        string definition = directive.Substring(directive.SafeIndexOf(' ')).Trim();
                        var macro = new Macro();
                        if (definition.Contains("("))
                        {
                            var parameterDefinition = definition.Substring(definition.SafeIndexOf('(') + 1);
                            parameterDefinition = parameterDefinition.Remove(parameterDefinition.SafeIndexOf(')'));
                            // NOTE: This probably introduces the ability to use ".macro foo(bar)this_doesnt_cause_errors"
                            if (string.IsNullOrEmpty(parameterDefinition))
                                macro.Parameters = new string[0];
                            else
                                macro.Parameters = parameterDefinition.SafeSplit(',');
                            macro.Name = definition.Remove(definition.SafeIndexOf('(')).ToLower();
                        }
                        else
                            macro.Name = definition.ToLower(); // TODO: Consider enforcing character usage restrictions
                        for (CurrentIndex++; CurrentIndex < Lines.Length; CurrentIndex++)
                        {
                            CurrentLine = Lines[CurrentIndex].Trim().TrimComments();
                            LineNumbers.Push(LineNumbers.Pop() + 1);
                            RootLineNumber++;
                            if (CurrentLine == ".endmacro" || CurrentLine == "#endmacro")
                                break;
                            macro.Code += CurrentLine + Environment.NewLine;
                        }
                        macro.Code = macro.Code.Remove(macro.Code.Length - Environment.NewLine.Length);
                        macro.Name = macro.Name.ToLower();
                        if (Macros.Any(m => m.Name == macro.Name && m.Parameters.Length == macro.Parameters.Length))
                        {
                            AddError(CodeType.Label, AssemblyError.DuplicateName);
                            continue;
                        }
                        Macros.Add(macro);
                        // Add an entry to the listing
                        Output.Listing.Add(new Listing
                        {
                            Code = definitionLine,
                            CodeType = CodeType.Directive,
                            Error = AssemblyError.None,
                            Warning = AssemblyWarning.None,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                    }
                    else
                    {
                        var result = HandleDirective(CurrentLine);
                        if (result != null)
                            Output.Listing.Add(result);
                    }
                    continue;
                }
                else
                {
                    if (string.IsNullOrEmpty(CurrentLine) || !Listing)
                        continue;
                    // Check instructions
                    var match = InstructionSet.Match(CurrentLine);
                    if (match == null)
                        AddError(CodeType.Instruction, AssemblyError.InvalidInstruction); // Unknown instruction
                    else
                    {
                        // Instruction to be fully assembled in the next pass
                        Output.Listing.Add(new Listing
                        {
                            Code = CurrentLine,
                            CodeType = CodeType.Instruction,
                            Error = AssemblyError.None,
                            Warning = AssemblyWarning.None,
                            Instruction = match,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                        PC += match.Length;
                    }
                }
            }
            return Finish(Output);
        }
Exemple #5
0
 private AssemblyOutput Finish(AssemblyOutput output)
 {
     List<byte> finalBinary = new List<byte>();
     for (int i = 0; i < output.Listing.Count; i++)
     {
         var entry = output.Listing[i];
         RootLineNumber = entry.RootLineNumber;
         PC = entry.Address;
         LineNumbers = new Stack<int>(new[] { entry.LineNumber });
         if (entry.CodeType == CodeType.Directive)
         {
             if (entry.PostponeEvalulation)
                 output.Listing[i] = HandleDirective(entry.Code, true);
             if (output.Listing[i].Output != null)
                 finalBinary.AddRange(output.Listing[i].Output);
             continue;
         }
         if (entry.Error != AssemblyError.None)
             continue;
         if (entry.CodeType == CodeType.Instruction)
         {
             // Assemble output string
             string instruction = entry.Instruction.Value.ToLower();
             foreach (var operand in entry.Instruction.Operands)
                 instruction = instruction.Replace("@" + operand.Key, operand.Value.Value);
             foreach (var value in entry.Instruction.ImmediateValues)
             {
                 // TODO: Truncation warning
                 if (value.Value.RelativeToPC)
                     instruction = instruction.Replace("^" + value.Key, ConvertToBinary(
                         entry.Address -
                         (ExpressionEngine.Evaluate(value.Value.Value, entry.Address) + entry.Instruction.Length),
                         value.Value.Bits));
                 else
                     instruction = instruction.Replace("%" + value.Key, ConvertToBinary(
                         ExpressionEngine.Evaluate(value.Value.Value, entry.Address),
                         value.Value.Bits));
             }
             entry.Output = ExpressionEngine.ConvertFromBinary(instruction);
             finalBinary.AddRange(entry.Output);
         }
     }
     output.Data = finalBinary.ToArray();
     return output;
 }
Exemple #6
0
        public AssemblyOutput Assemble(string assembly, string fileName = null)
        {
            var output = new AssemblyOutput();
            assembly = assembly.Replace("\r", "");
            PC = 0;
            Lines = assembly.Split('\n');
            FileNames.Push(fileName);
            LineNumbers.Push(0);
            RootLineNumber = 0;
            for (CurrentIndex = 0; CurrentIndex < Lines.Length; CurrentIndex++)
            {
                string line = Lines[CurrentIndex].Trim().TrimComments().ToLower();
                if (SuspendedLines == 0)
                {
                    LineNumbers.Push(LineNumbers.Pop() + 1);
                    RootLineNumber++;
                }
                else
                    SuspendedLines--;

                if (line.SafeContains('\\'))
                {
                    // Split lines up
                    var split = line.SafeSplit('\\');
                    Lines = Lines.Take(CurrentIndex).Concat(split).
                        Concat(Lines.Skip(CurrentIndex + 1)).ToArray();
                    SuspendedLines = split.Length;
                    CurrentIndex--;
                    continue;
                }

                if (line.SafeContains(".equ") && !line.StartsWith(".equ"))
                {
                    var name = line.Remove(line.SafeIndexOf(".equ"));
                    var definition = line.Substring(line.SafeIndexOf(".equ") + 4);
                    line = ".equ " + name.Trim() + ", " + definition.Trim();
                }

                if (line.StartsWith(".") || line.StartsWith("#")) // Directive
                {
                    // Some directives need to be handled higher up
                    var directive = line.Substring(1).Trim().ToLower();
                    string[] parameters = new string[0];
                    if (directive.SafeIndexOf(' ') != -1)
                        parameters = directive.Substring(directive.SafeIndexOf(' ')).Split(',');
                    if (directive.StartsWith("macro"))
                    {
                        var definitionLine = line; // Used to update the listing later
                        if (parameters.Length == 0)
                        {
                            output.Listing.Add(new Listing
                            {
                                Code = line,
                                CodeType = CodeType.Directive,
                                Error = AssemblyError.InvalidDirective,
                                Warning = AssemblyWarning.None,
                                Address = PC,
                                FileName = FileNames.Peek(),
                                LineNumber = LineNumbers.Peek(),
                                RootLineNumber = RootLineNumber
                            });
                            continue;
                        }
                        string definition = directive.Substring(directive.SafeIndexOf(' ')).Trim();
                        var macro = new Macro();
                        if (definition.Contains("("))
                        {
                            var parameterDefinition = definition.Substring(definition.SafeIndexOf('(') + 1);
                            parameterDefinition = parameterDefinition.Remove(parameterDefinition.SafeIndexOf(')'));
                            // NOTE: This probably introduces the ability to use ".macro foo(bar)this_doesnt_cause_errors"
                            macro.Parameters = parameterDefinition.SafeSplit(',');
                            macro.Name = definition.Remove(definition.SafeIndexOf('('));
                        }
                        else
                            macro.Name = definition; // TODO: Consider enforcing character usage restrictions
                        for (CurrentIndex++; CurrentIndex < Lines.Length; CurrentIndex++)
                        {
                            line = Lines[CurrentIndex].Trim().TrimComments();
                            LineNumbers.Push(LineNumbers.Pop() + 1);
                            RootLineNumber++;
                            if (line == ".endmacro" || line == "#endmacro")
                                break;
                            macro.Code += line + Environment.NewLine;
                        }
                        macro.Code = macro.Code.Remove(macro.Code.Length - Environment.NewLine.Length);
                        macro.Name = macro.Name.ToLower();
                        if (Macros.Any(m => m.Name == macro.Name))
                        {
                            output.Listing.Add(new Listing
                            {
                                Code = line,
                                CodeType = CodeType.Directive,
                                Error = AssemblyError.DuplicateName,
                                Warning = AssemblyWarning.None,
                                Address = PC,
                                FileName = FileNames.Peek(),
                                LineNumber = LineNumbers.Peek(),
                                RootLineNumber = RootLineNumber
                            });
                            continue;
                        }
                        Macros.Add(macro);
                        // Add an entry to the listing
                        output.Listing.Add(new Listing
                        {
                            Code = definitionLine,
                            CodeType = CodeType.Directive,
                            Error = AssemblyError.None,
                            Warning = AssemblyWarning.None,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                    }
                    else if (directive.StartsWith("include"))
                    {

                    }
                    else
                    {
                        var result = HandleDirective(line);
                        if (result != null)
                            output.Listing.Add(result);
                    }
                    continue;
                }
                else if (line.StartsWith(":") || line.EndsWith(":")) // Label
                {
                    string label;
                    if (line.StartsWith(":"))
                        label = line.Substring(1).Trim();
                    else
                        label = line.Remove(line.Length - 1).Trim();
                    label = label.ToLower();
                    bool valid = true;
                    for (int k = 0; k < label.Length; k++) // Validate label
                    {
                        if (!char.IsLetterOrDigit(label[k]) && k != '_')
                        {
                            valid = false;
                            break;
                        }
                    }
                    if (!valid)
                    {
                        output.Listing.Add(new Listing
                        {
                            Code = line,
                            CodeType = CodeType.Label,
                            Error = AssemblyError.InvalidLabel,
                            Warning = AssemblyWarning.None,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                    }
                    output.Listing.Add(new Listing
                    {
                        Code = line,
                        CodeType = CodeType.Label,
                        Error = AssemblyError.None,
                        Warning = AssemblyWarning.None,
                        Address = PC,
                        FileName = FileNames.Peek(),
                        LineNumber = LineNumbers.Peek(),
                        RootLineNumber = RootLineNumber
                    });
                    ExpressionEngine.Equates.Add(label, PC);
                }
                else
                {
                    // Check for macro
                    Macro macroMatch = null;
                    string[] parameters = null;
                    string parameterDefinition = null;
                    foreach (var macro in Macros)
                    {
                        if (line.SafeContains(macro.Name))
                        {
                            // Try to match
                            int startIndex = line.SafeIndexOf(macro.Name);
                            int endIndex = startIndex + macro.Name.Length - 1;
                            if (macro.Parameters.Length != 0)
                            {
                                if (line[endIndex + 1] != '(')
                                    continue;
                                parameterDefinition = line.Substring(endIndex + 2, line.SafeIndexOf(')') - (endIndex + 2));
                                parameters = parameterDefinition.SafeSplit(',');
                                if (parameters.Length != macro.Parameters.Length)
                                    continue;
                                // Matched
                                macroMatch = macro;
                                break;
                            }
                        }
                    }
                    if (macroMatch != null)
                    {
                        // Add an entry to the listing
                        output.Listing.Add(new Listing
                        {
                            Code = line,
                            CodeType = CodeType.Directive,
                            Error = AssemblyError.None,
                            Warning = AssemblyWarning.None,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                        var code = macroMatch.Code;
                        int index = 0;
                        foreach (var parameter in macroMatch.Parameters)
                            code = code.Replace(parameter, parameters[index++]);
                        string newLine;
                        if (parameterDefinition != null)
                            newLine = line.Replace(macroMatch.Name + "(" + parameterDefinition + ")", code);
                        else
                            newLine = line.Replace(macroMatch.Name, code);
                        var newLines = newLine.Replace("\r\n", "\n").Split('\n');
                        SuspendedLines += newLines.Length;
                        // Insert macro
                        Lines = Lines.Take(CurrentIndex).Concat(newLines).Concat(Lines.Skip(CurrentIndex + 1)).ToArray();
                        CurrentIndex--;
                        continue;
                    }
                    if (string.IsNullOrEmpty(line))
                        continue;
                    // Check instructions
                    var match = InstructionSet.Match(line);
                    if (match == null)
                    {
                        // Unknown instruction
                        output.Listing.Add(new Listing
                        {
                            Code = line,
                            CodeType = CodeType.Instruction,
                            Error = AssemblyError.InvalidInstruction,
                            Warning = AssemblyWarning.None,
                            Instruction = match,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                    }
                    else
                    {
                        // Instruction to be fully assembled in the next pass
                        output.Listing.Add(new Listing
                        {
                            Code = line,
                            CodeType = CodeType.Instruction,
                            Error = AssemblyError.None,
                            Warning = AssemblyWarning.None,
                            Instruction = match,
                            Address = PC,
                            FileName = FileNames.Peek(),
                            LineNumber = LineNumbers.Peek(),
                            RootLineNumber = RootLineNumber
                        });
                        PC += match.Length;
                    }
                }
            }
            return Finish(output);
        }