/// <summary> /// Computes the number of lines of output required to hold the formatted output. /// </summary> /// <param name="formatter">Format definition.</param> /// <param name="dfd">Data format descriptor.</param> /// <returns>Line count.</returns> public static int ComputeRequiredLineCount(Formatter formatter, FormatDescriptor dfd) { switch (dfd.FormatType) { case FormatDescriptor.Type.Default: case FormatDescriptor.Type.NumericLE: case FormatDescriptor.Type.NumericBE: case FormatDescriptor.Type.Fill: return(1); case FormatDescriptor.Type.Dense: { // no delimiter, two output bytes per input byte int maxLen = MAX_OPERAND_LEN; int textLen = dfd.Length * 2; return((textLen + maxLen - 1) / maxLen); } case FormatDescriptor.Type.String: { // Subtract two chars, to leave room for start/end delimiter. We use // non-ASCII delimiters on-screen, so there's nothing to escape there. int maxLen = MAX_OPERAND_LEN - 2; // Remove leading length or trailing null byte from string length. int textLen = dfd.Length; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: case FormatDescriptor.SubType.Dci: case FormatDescriptor.SubType.Reverse: case FormatDescriptor.SubType.DciReverse: break; case FormatDescriptor.SubType.CString: case FormatDescriptor.SubType.L8String: textLen--; break; case FormatDescriptor.SubType.L16String: textLen -= 2; break; default: Debug.Assert(false); break; } int strLen = (textLen + maxLen - 1) / maxLen; if (strLen == 0) { // Empty string, but we still need to output a line. strLen = 1; } return(strLen); } default: Debug.Assert(false); return(1); } }
/// <summary> /// Constructs a DefSymbol from a Symbol and a format descriptor. This is used /// for project symbols. /// </summary> /// <param name="sym">Base symbol.</param> /// <param name="dfd">Format descriptor.</param> /// <param name="comment">End-of-line comment.</param> public DefSymbol(Symbol sym, FormatDescriptor dfd, string comment) : this(sym.Label, sym.Value, sym.SymbolSource, sym.SymbolType) { Debug.Assert(comment != null); DataDescriptor = dfd; Comment = comment; Tag = string.Empty; }
public SerFormatDescriptor(FormatDescriptor dfd) { Length = dfd.Length; Format = dfd.FormatType.ToString(); SubFormat = dfd.FormatSubType.ToString(); if (dfd.SymbolRef != null) { SymbolRef = new SerWeakSymbolRef(dfd.SymbolRef); } }
/// <summary> /// Creates an UndoableChange for an operand or data format update. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldFormat">Current format. May be null.</param> /// <param name="newFormat">New format. May be null.</param> /// <returns>Change record.</returns> public static UndoableChange CreateOperandFormatChange(int offset, FormatDescriptor oldFormat, FormatDescriptor newFormat) { if (oldFormat == newFormat) { Debug.WriteLine("No-op format change at +" + offset.ToString("x6") + ": " + oldFormat); } // We currently allow old/new formats with different lengths. There doesn't // seem to be a reason not to, and a slight performance advantage to doing so. // Also, if a change set has two changes at the same offset, undo requires // enumerating the list in reverse order. UndoableChange uc = new UndoableChange(); uc.Type = ChangeType.SetOperandFormat; uc.Offset = offset; uc.OldValue = oldFormat; uc.NewValue = newFormat; // Data-only reanalysis is required if the old or new format has a label. Simply // changing from e.g. default to decimal, or decimal to binary, doesn't matter. // (The format editing code ensures that labels don't appear in the middle of // a formatted region.) Adding, removing, or changing a symbol can change the // layout of uncategorized data, affect data targets, xrefs, etc. // // We can't only check for a symbol, though, because Numeric/Address will // create an auto-label if the reference is within the file. // // If the number of bytes covered by the format changes, or we're adding or // removing a format, we need to redo the analysis of uncategorized data. For // example, an auto-detected string could get larger or smaller. We don't // currently have a separate flag for just that. Also, because we're focused // on just one change, we can't skip reanalysis when (say) one 4-byte numeric // is converted to two two-byte numerics. if ((oldFormat != null && oldFormat.HasSymbolOrAddress) || (newFormat != null && newFormat.HasSymbolOrAddress)) { uc.ReanalysisRequired = ReanalysisScope.DataOnly; } else if (oldFormat == null || newFormat == null || oldFormat.Length != newFormat.Length) { uc.ReanalysisRequired = ReanalysisScope.DataOnly; } else { uc.ReanalysisRequired = ReanalysisScope.None; } return(uc); }
/// <summary> /// Constructor. /// </summary> /// <param name="label">Symbol's label.</param> /// <param name="value">Symbol's value.</param> /// <param name="source">Symbol source (general point of origin).</param> /// <param name="type">Symbol type.</param> /// <param name="formatSubType">Format descriptor sub-type, so we know how the /// user wants the value to be displayed.</param> /// <param name="comment">End-of-line comment.</param> /// <param name="tag">Symbol tag, used for grouping platform symbols.</param> public DefSymbol(string label, int value, Source source, Type type, FormatDescriptor.SubType formatSubType, string comment, string tag) : this(label, value, source, type) { Debug.Assert(comment != null); Debug.Assert(tag != null); // Length doesn't matter; use 1 to get prefab object. DataDescriptor = FormatDescriptor.Create(1, FormatDescriptor.Type.NumericLE, formatSubType); Comment = comment; Tag = tag; }
/// <summary> /// Creates an UndoableChange for an operand or data format update. This method /// refuses to create a change for a no-op, returning null instead. This will /// convert a FormatDescriptor with type REMOVE to null, with the intention of /// removing the descriptor from the format set. /// </summary> /// <param name="offset">Affected offset.</param> /// <param name="oldFormat">Current format. May be null.</param> /// <param name="newFormat">New format. May be null.</param> /// <returns>Change record, or null for a no-op change.</returns> public static UndoableChange CreateActualOperandFormatChange(int offset, FormatDescriptor oldFormat, FormatDescriptor newFormat) { if (newFormat != null && newFormat.FormatType == FormatDescriptor.Type.REMOVE) { Debug.WriteLine("CreateOperandFormatChange: converting REMOVE to null"); newFormat = null; } if (oldFormat == newFormat) { Debug.WriteLine("No-op format change at +" + offset.ToString("x6") + ": " + oldFormat); return(null); } return(CreateOperandFormatChange(offset, oldFormat, newFormat)); }
/// <summary> /// Debugging utility function to dump a sorted list of objects. /// </summary> public static void DebugDumpSortedList(SortedList <int, FormatDescriptor> list) { if (list == null) { Debug.WriteLine("FormatDescriptor list is empty"); return; } Debug.WriteLine("FormatDescriptor list (" + list.Count + " entries)"); foreach (KeyValuePair <int, FormatDescriptor> kvp in list) { int offset = kvp.Key; FormatDescriptor dfd = kvp.Value; Debug.WriteLine(" +" + offset.ToString("x6") + ",+" + (offset + dfd.Length - 1).ToString("x6") + ": " + dfd.FormatType + "(" + dfd.FormatSubType + ")"); } }
/// <summary> /// Creates a FormatDescriptor from a SerFormatDescriptor. /// </summary> /// <param name="sfd">Deserialized data.</param> /// <param name="report">Error report object.</param> /// <param name="dfd">Created FormatDescriptor.</param> /// <returns>True on success.</returns> private static bool CreateFormatDescriptor(SerFormatDescriptor sfd, FileLoadReport report, out FormatDescriptor dfd) { dfd = null; FormatDescriptor.Type format; FormatDescriptor.SubType subFormat; try { format = (FormatDescriptor.Type)Enum.Parse( typeof(FormatDescriptor.Type), sfd.Format); subFormat = (FormatDescriptor.SubType)Enum.Parse( typeof(FormatDescriptor.SubType), sfd.SubFormat); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_FD_FORMAT + ": " + sfd.Format + "/" + sfd.SubFormat); return(false); } if (sfd.SymbolRef == null) { dfd = FormatDescriptor.Create(sfd.Length, format, subFormat); } else { WeakSymbolRef.Part part; try { part = (WeakSymbolRef.Part)Enum.Parse( typeof(WeakSymbolRef.Part), sfd.SymbolRef.Part); } catch (ArgumentException) { report.Add(FileLoadItem.Type.Warning, Res.Strings.ERR_BAD_SYMREF_PART + ": " + sfd.SymbolRef.Part); return(false); } dfd = FormatDescriptor.Create(sfd.Length, new WeakSymbolRef(sfd.SymbolRef.Label, part), format == FormatDescriptor.Type.NumericBE); } return(true); }
/// <summary> /// Format the symbol and adjustment using cc65 expression syntax. /// </summary> private static void FormatNumericSymbolCc65(Formatter formatter, Symbol sym, Dictionary <string, string> labelMap, FormatDescriptor dfd, int operandValue, int operandLen, FormatNumericOpFlags flags, StringBuilder sb) { // The key difference between cc65 and other assemblers with general expressions // is that the bitwise shift and AND operators have higher precedence than the // arithmetic ops like add and subtract. (The bitwise ops are equal to multiply // and divide.) This means that, if we want to mask off the low 16 bits and add one // to a label, we can write "start & $ffff + 1" rather than "(start & $ffff) + 1". // // This is particularly convenient for PEA, since "PEA (start & $ffff)" looks like // we're trying to use a (non-existent) indirect form of PEA. We can write things // in a simpler way. int adjustment, symbolValue; string symLabel = sym.Label; if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; } if (operandLen == 1) { // Use the byte-selection operator to get the right piece. string selOp; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { symbolValue = (sym.Value >> 16) & 0xff; selOp = "^"; } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { symbolValue = (sym.Value >> 8) & 0xff; selOp = ">"; } else { symbolValue = sym.Value & 0xff; if (symbolValue == sym.Value) { selOp = string.Empty; } else { selOp = "<"; } } sb.Append(selOp); sb.Append(symLabel); operandValue &= 0xff; } else if (operandLen <= 4) { uint mask = 0xffffffff >> ((4 - operandLen) * 8); string shOp; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { symbolValue = (sym.Value >> 16); shOp = " >> 16"; } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { symbolValue = (sym.Value >> 8); shOp = " >> 8"; } else { symbolValue = sym.Value; shOp = ""; } if (flags == FormatNumericOpFlags.IsPcRel) { // PC-relative operands are funny, because an 8- or 16-bit value is always // expanded to 24 bits. We output a 16-bit value that the assembler will // convert back to 8-bit or 16-bit. In any event, the bank byte is never // relevant to our computations. operandValue &= 0xffff; symbolValue &= 0xffff; } sb.Append(symLabel); sb.Append(shOp); if (symbolValue > mask) { // Post-shift value won't fit in an operand-size box. symbolValue = (int)(symbolValue & mask); sb.Append(" & "); sb.Append(formatter.FormatHexValue((int)mask, 2)); } operandValue = (int)(operandValue & mask); if (sb.Length != symLabel.Length) { sb.Append(' '); } } else { Debug.Assert(false, "bad numeric len"); sb.Append("?????"); symbolValue = 0; } adjustment = operandValue - symbolValue; sb.Append(formatter.FormatAdjustment(adjustment)); }
/// <summary> /// Format the symbol and adjustment using common expression syntax. /// </summary> private static void FormatNumericSymbolCommon(Formatter formatter, Symbol sym, Dictionary <string, string> labelMap, FormatDescriptor dfd, int operandValue, int operandLen, FormatNumericOpFlags flags, StringBuilder sb) { // We could have some simple code that generated correct output, shifting and // masking every time, but that's ugly and annoying. For single-byte ops we can // just use the byte-select operators, for wider ops we get only as fancy as we // need to be. int adjustment, symbolValue; string symLabel = sym.Label; if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; } if (operandLen == 1) { // Use the byte-selection operator to get the right piece. In 64tass the // selection operator has a very low precedence, similar to Merlin 32. string selOp; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { symbolValue = (sym.Value >> 16) & 0xff; if (formatter.Config.mBankSelectBackQuote) { selOp = "`"; } else { selOp = "^"; } } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { symbolValue = (sym.Value >> 8) & 0xff; selOp = ">"; } else { symbolValue = sym.Value & 0xff; if (symbolValue == sym.Value) { selOp = string.Empty; } else { selOp = "<"; } } operandValue &= 0xff; if (operandValue != symbolValue && dfd.SymbolRef.ValuePart != WeakSymbolRef.Part.Low) { // Adjustment is required to an upper-byte part. sb.Append('('); sb.Append(selOp); sb.Append(symLabel); sb.Append(')'); } else { // no adjustment required sb.Append(selOp); sb.Append(symLabel); } } else if (operandLen <= 4) { // Operands and values should be 8/16/24 bit unsigned quantities. 32-bit // support is really there so you can have a 24-bit pointer in a 32-bit hole. // Might need to adjust this if 32-bit signed quantities become interesting. uint mask = 0xffffffff >> ((4 - operandLen) * 8); int rightShift; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { symbolValue = (sym.Value >> 16); rightShift = 16; } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { symbolValue = (sym.Value >> 8); rightShift = 8; } else { symbolValue = sym.Value; rightShift = 0; } if (flags == FormatNumericOpFlags.IsPcRel) { // PC-relative operands are funny, because an 8- or 16-bit value is always // expanded to 24 bits. We output a 16-bit value that the assembler will // convert back to 8-bit or 16-bit. In any event, the bank byte is never // relevant to our computations. operandValue &= 0xffff; symbolValue &= 0xffff; } bool needMask = false; if (symbolValue > mask) { // Post-shift value won't fit in an operand-size box. symbolValue = (int)(symbolValue & mask); needMask = true; } operandValue = (int)(operandValue & mask); // Generate one of: // label [+ adj] // (label >> rightShift) [+ adj] // (label & mask) [+ adj] // ((label >> rightShift) & mask) [+ adj] if (rightShift != 0 || needMask) { if (flags != FormatNumericOpFlags.HasHashPrefix) { sb.Append("0+"); } if (rightShift != 0 && needMask) { sb.Append("(("); } else { sb.Append("("); } } sb.Append(symLabel); if (rightShift != 0) { sb.Append(" >> "); sb.Append(rightShift.ToString()); sb.Append(')'); } if (needMask) { sb.Append(" & "); sb.Append(formatter.FormatHexValue((int)mask, 2)); sb.Append(')'); } } else { Debug.Assert(false, "bad numeric len"); sb.Append("?????"); symbolValue = 0; } adjustment = operandValue - symbolValue; sb.Append(formatter.FormatAdjustment(adjustment)); }
/// <summary> /// Format a numeric operand value according to the specified sub-format. /// </summary> /// <param name="formatter">Text formatter.</param> /// <param name="symbolTable">Full table of project symbols.</param> /// <param name="labelMap">Symbol label remap, for local label conversion. May be /// null.</param> /// <param name="dfd">Operand format descriptor.</param> /// <param name="operandValue">Operand's value. For most things this comes directly /// out of the code, for relative branches it's a 24-bit absolute address.</param> /// <param name="operandLen">Length of operand, in bytes. For an instruction, this /// does not include the opcode byte. For a relative branch, this will be 2.</param> /// <param name="flags">Special handling.</param> public static string FormatNumericOperand(Formatter formatter, SymbolTable symbolTable, Dictionary <string, string> labelMap, FormatDescriptor dfd, int operandValue, int operandLen, FormatNumericOpFlags flags) { Debug.Assert(operandLen > 0); int hexMinLen = operandLen * 2; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.None: case FormatDescriptor.SubType.Hex: case FormatDescriptor.SubType.Address: return(formatter.FormatHexValue(operandValue, hexMinLen)); case FormatDescriptor.SubType.Decimal: return(formatter.FormatDecimalValue(operandValue)); case FormatDescriptor.SubType.Binary: return(formatter.FormatBinaryValue(operandValue, hexMinLen * 4)); case FormatDescriptor.SubType.Ascii: return(formatter.FormatAsciiOrHex(operandValue)); case FormatDescriptor.SubType.Symbol: if (symbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { StringBuilder sb = new StringBuilder(); switch (formatter.ExpressionMode) { case Formatter.FormatConfig.ExpressionMode.Common: FormatNumericSymbolCommon(formatter, sym, labelMap, dfd, operandValue, operandLen, flags, sb); break; case Formatter.FormatConfig.ExpressionMode.Cc65: FormatNumericSymbolCc65(formatter, sym, labelMap, dfd, operandValue, operandLen, flags, sb); break; case Formatter.FormatConfig.ExpressionMode.Merlin: FormatNumericSymbolMerlin(formatter, sym, labelMap, dfd, operandValue, operandLen, flags, sb); break; default: Debug.Assert(false, "Unknown expression mode " + formatter.ExpressionMode); return("???"); } return(sb.ToString()); } else { return(formatter.FormatHexValue(operandValue, hexMinLen)); } default: Debug.Assert(false); return("???"); } }
/// <summary> /// Generates a pseudo-op statement for the specified data operation. /// /// For most operations, only one output line will be generated. For larger items, /// like long comments, the value may be split into multiple lines. The sub-index /// indicates which line should be formatted. /// </summary> /// <param name="formatter">Format definition.</param> /// <param name="opNames">Table of pseudo-op names.</param> /// <param name="symbolTable">Project symbol table.</param> /// <param name="labelMap">Symbol label map. May be null.</param> /// <param name="dfd">Data format descriptor.</param> /// <param name="data">File data array.</param> /// <param name="offset">Start offset.</param> /// <param name="subIndex">For multi-line items, which line.</param> public static PseudoOut FormatDataOp(Formatter formatter, PseudoOpNames opNames, SymbolTable symbolTable, Dictionary <string, string> labelMap, FormatDescriptor dfd, byte[] data, int offset, int subIndex) { if (dfd == null) { // should never happen //Debug.Assert(false, "Null dfd at offset+" + offset.ToString("x6")); PseudoOut failed = new PseudoOut(); failed.Opcode = failed.Operand = "!FAILED!+" + offset.ToString("x6"); return(failed); } int length = dfd.Length; Debug.Assert(length > 0); // All outputs for a given offset show the same offset and length, even for // multi-line items. PseudoOut po = new PseudoOut(); switch (dfd.FormatType) { case FormatDescriptor.Type.Default: if (length != 1) { // This shouldn't happen. Debug.Assert(false); length = 1; } po.Opcode = opNames.GetDefineData(length); int operand = RawData.GetWord(data, offset, length, false); po.Operand = formatter.FormatHexValue(operand, length * 2); break; case FormatDescriptor.Type.NumericLE: po.Opcode = opNames.GetDefineData(length); operand = RawData.GetWord(data, offset, length, false); po.Operand = FormatNumericOperand(formatter, symbolTable, labelMap, dfd, operand, length, FormatNumericOpFlags.None); break; case FormatDescriptor.Type.NumericBE: po.Opcode = opNames.GetDefineBigData(length); operand = RawData.GetWord(data, offset, length, true); po.Operand = FormatNumericOperand(formatter, symbolTable, labelMap, dfd, operand, length, FormatNumericOpFlags.None); break; case FormatDescriptor.Type.Fill: po.Opcode = opNames.Fill; po.Operand = length + "," + formatter.FormatHexValue(data[offset], 2); break; case FormatDescriptor.Type.Dense: { int maxPerLine = MAX_OPERAND_LEN / 2; offset += subIndex * maxPerLine; length -= subIndex * maxPerLine; if (length > maxPerLine) { length = maxPerLine; } po.Opcode = opNames.Dense; po.Operand = formatter.FormatDenseHex(data, offset, length); //List<PseudoOut> outList = new List<PseudoOut>(); //GenerateTextLines(text, "", "", po, outList); //po = outList[subIndex]; } break; case FormatDescriptor.Type.String: // It's hard to do strings in single-line pieces because of prefix lengths, // terminating nulls, DCI polarity, and reverse-order strings. We // really just want to convert the whole thing to a run of chars // and then pull out a chunk. As an optimization we can handle // generic strings (subtype=None) more efficiently, which should solve // the problem of massive strings created by auto-analysis. if (dfd.FormatSubType == FormatDescriptor.SubType.None) { int maxPerLine = MAX_OPERAND_LEN - 2; offset += subIndex * maxPerLine; length -= subIndex * maxPerLine; if (length > maxPerLine) { length = maxPerLine; } char[] ltext = BytesToChars(formatter, opNames, dfd.FormatSubType, data, offset, length, out string lpopcode, out int unused); po.Opcode = lpopcode; po.Operand = "\u201c" + new string(ltext) + "\u201d"; } else { char[] text = BytesToChars(formatter, opNames, dfd.FormatSubType, data, offset, length, out string popcode, out int showHexZeroes); if (showHexZeroes == 1) { po.Opcode = opNames.DefineData1; po.Operand = formatter.FormatHexValue(0, 2); } else if (showHexZeroes == 2) { po.Opcode = opNames.DefineData2; po.Operand = formatter.FormatHexValue(0, 4); } else { Debug.Assert(showHexZeroes == 0); po.Opcode = popcode; List <PseudoOut> outList = new List <PseudoOut>(); GenerateTextLines(text, "\u201c", "\u201d", po, outList); po = outList[subIndex]; } } break; default: Debug.Assert(false); po.Opcode = ".???"; po.Operand = "$" + data[offset].ToString("x2"); break; } return(po); }
/// <summary> /// Format the symbol and adjustment using Merlin expression syntax. /// </summary> private static void FormatNumericSymbolMerlin(Formatter formatter, Symbol sym, Dictionary <string, string> labelMap, FormatDescriptor dfd, int operandValue, int operandLen, FormatNumericOpFlags flags, StringBuilder sb) { // Merlin expressions are compatible with the original 8-bit Merlin. They're // evaluated from left to right, with (almost) no regard for operator precedence. // // The part-selection operators differ from "simple" in two ways: // (1) They always happen last. If FOO=$10f0, "#>FOO+$18" == $11. One of the // few cases where left-to-right evaluation is overridden. // (2) They select words, not bytes. If FOO=$123456, "#>FOO" is $1234. This is // best thought of as a shift operator, rather than byte-selection. For // 8-bit code this doesn't matter. // // This behavior leads to simpler expressions for simple symbol adjustments. string symLabel = sym.Label; if (labelMap != null && labelMap.TryGetValue(symLabel, out string newLabel)) { symLabel = newLabel; } int adjustment; // If we add or subtract an adjustment, it will be done on the full value, which // is then shifted to the appropriate part. So we need to left-shift the operand // value to match. We fill in the low bytes with the contents of the symbol, so // that the adjustment doesn't include unnecessary values. (For example, let // FOO=$10f0, with operand "#>FOO" ($10). We shift the operand to get $1000, then // OR in the low byte to get $10f0, so that when we subtract we get adjustment==0.) int adjOperand, keepLen; if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.Bank) { adjOperand = operandValue << 16 | (int)(sym.Value & 0xff00ffff); keepLen = 3; } else if (dfd.SymbolRef.ValuePart == WeakSymbolRef.Part.High) { adjOperand = (operandValue << 8) | (sym.Value & 0xff); keepLen = 2; } else { adjOperand = operandValue; keepLen = 1; } keepLen = Math.Max(keepLen, operandLen); adjustment = adjOperand - sym.Value; if (keepLen == 1) { adjustment %= 256; // Adjust for aesthetics. The assembler implicitly applies a modulo operation, // so we can use the value closest to zero. if (adjustment > 127) { adjustment = -(256 - adjustment) /*% 256*/; } else if (adjustment < -128) { adjustment = (256 + adjustment) /*% 256*/; } } else if (keepLen == 2) { adjustment %= 65536; if (adjustment > 32767) { adjustment = -(65536 - adjustment) /*% 65536*/; } else if (adjustment < -32768) { adjustment = (65536 + adjustment) /*% 65536*/; } } // Use the label from sym, not dfd's weak ref; might be different if label // comparisons are case-insensitive. switch (dfd.SymbolRef.ValuePart) { case WeakSymbolRef.Part.Unknown: case WeakSymbolRef.Part.Low: // For Merlin, "<" is effectively a no-op. We can put it in for // aesthetics when grabbing the low byte of a 16-bit value. if ((operandLen == 1) && sym.Value > 0xff) { sb.Append('<'); } sb.Append(symLabel); break; case WeakSymbolRef.Part.High: sb.Append('>'); sb.Append(symLabel); break; case WeakSymbolRef.Part.Bank: sb.Append('^'); sb.Append(symLabel); break; default: Debug.Assert(false, "bad part"); sb.Append("???"); break; } sb.Append(formatter.FormatAdjustment(adjustment)); }
/// <summary> /// Loads platform symbols. /// </summary> /// <param name="fileIdent">Relative pathname of file to open.</param> /// <param name="projectDir">Full path to project directory.</param> /// <param name="report">Report of warnings and errors.</param> /// <returns>True on success (no errors), false on failure.</returns> public bool LoadFromFile(string fileIdent, string projectDir, out FileLoadReport report) { // These files shouldn't be enormous. Do it the easy way. report = new FileLoadReport(fileIdent); ExternalFile ef = ExternalFile.CreateFromIdent(fileIdent); if (ef == null) { report.Add(FileLoadItem.Type.Error, CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + fileIdent); return(false); } string pathName = ef.GetPathName(projectDir); if (pathName == null) { report.Add(FileLoadItem.Type.Error, Res.Strings.ERR_BAD_IDENT + ": " + fileIdent); return(false); } string[] lines; try { lines = File.ReadAllLines(pathName); } catch (IOException ioe) { Debug.WriteLine("Platform symbol load failed: " + ioe); report.Add(FileLoadItem.Type.Error, CommonUtil.Properties.Resources.ERR_FILE_NOT_FOUND + ": " + pathName); return(false); } string tag = string.Empty; int lineNum = 0; foreach (string line in lines) { lineNum++; // first line is line 1, says Vim and VisualStudio if (string.IsNullOrEmpty(line) || line[0] == ';') { // ignore } else if (line[0] == '*') { if (line.StartsWith(TAG_CMD)) { tag = ParseTag(line); } else { // Do something clever with *SYNOPSIS? Debug.WriteLine("CMD: " + line); } } else { MatchCollection matches = sNameValueRegex.Matches(line); if (matches.Count == 1) { //Debug.WriteLine("GOT '" + matches[0].Groups[1] + "' " + // matches[0].Groups[2] + " '" + matches[0].Groups[3] + "'"); string label = matches[0].Groups[1].Value; bool isConst = (matches[0].Groups[2].Value[0] == '='); string badParseMsg; int value, numBase; bool parseOk; if (isConst) { // Allow various numeric options, and preserve the value. parseOk = Asm65.Number.TryParseInt(matches[0].Groups[3].Value, out value, out numBase); badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_NUMERIC_CONSTANT; } else { // Allow things like "05/1000". Always hex. numBase = 16; parseOk = Asm65.Address.ParseAddress(matches[0].Groups[3].Value, (1 << 24) - 1, out value); badParseMsg = CommonUtil.Properties.Resources.ERR_INVALID_ADDRESS; } if (!parseOk) { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, badParseMsg); } else { string comment = matches[0].Groups[4].Value; if (comment.Length > 0) { // remove ';' comment = comment.Substring(1); } FormatDescriptor.SubType subType = FormatDescriptor.GetSubTypeForBase(numBase); DefSymbol symDef = new DefSymbol(label, value, Symbol.Source.Platform, isConst ? Symbol.Type.Constant : Symbol.Type.ExternalAddr, subType, comment, tag); if (mSymbols.ContainsKey(label)) { // This is very easy to do -- just define the same symbol twice // in the same file. We don't really need to do anything about // it though. Debug.WriteLine("NOTE: stomping previous definition of " + label); } mSymbols[label] = symDef; } } else { report.Add(lineNum, FileLoadItem.NO_COLUMN, FileLoadItem.Type.Warning, CommonUtil.Properties.Resources.ERR_SYNTAX); } } } return(!report.HasErrors); }