From 0860a00a541b5b23610ba11f31b07f75d2223129 Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 15 Jul 2020 17:43:08 -0700 Subject: [PATCH] SGEC update, part 2 (of 2) Expand SGEC to include long comments and notes. These are serialized in JavaScript form. SGEC now accepts addresses and relative position deltas. Exported content uses addresses, and can be configured for deltas. --- CommonWPF/Helper.cs | 17 +++ SourceGen/MainController.cs | 14 ++- SourceGen/ProjectFile.cs | 13 +- SourceGen/Sgec.cs | 234 +++++++++++++++++++++++++++++++----- 4 files changed, 233 insertions(+), 45 deletions(-) diff --git a/CommonWPF/Helper.cs b/CommonWPF/Helper.cs index a278b70..94a131f 100644 --- a/CommonWPF/Helper.cs +++ b/CommonWPF/Helper.cs @@ -78,5 +78,22 @@ namespace CommonWPF { return fmt.Width; } + + /// + /// Converts a System.Windows.Media.Color value into 32-bit ARGB. + /// + public static int ColorToInt(Color color) { + return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B; + } + + /// + /// Creates a System.Windows.Media.Color value from 32-bit ARGB. + /// + /// + /// + public static Color ColorFromInt(int colorInt) { + return Color.FromArgb((byte)(colorInt >> 24), (byte)(colorInt >> 16), + (byte)(colorInt >> 8), (byte)colorInt); + } } } diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs index f2be80e..ce4e88e 100644 --- a/SourceGen/MainController.cs +++ b/SourceGen/MainController.cs @@ -4387,7 +4387,19 @@ namespace SourceGen { return; } string sgecPathName = Path.GetFullPath(fileDlg.FileName); - if (!Sgec.ExportToFile(sgecPathName, mProject, out string detailMsg)) { + + MessageBoxResult res = MessageBox.Show("Use relative offsets?", "Question", + MessageBoxButton.YesNoCancel, MessageBoxImage.Question); + bool resMode; + if (res == MessageBoxResult.Cancel) { + return; + } else if (res == MessageBoxResult.Yes) { + resMode = true; + } else { + resMode = false; + } + + if (!Sgec.ExportToFile(sgecPathName, mProject, resMode, out string detailMsg)) { MessageBox.Show("Failed: " + detailMsg); } else { MessageBox.Show("Success: " + detailMsg); diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index a3efe64..243d9de 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -273,7 +273,7 @@ namespace SourceGen { Text = mlc.Text; BoxMode = mlc.BoxMode; MaxWidth = mlc.MaxWidth; - BackgroundColor = ColorToInt(mlc.BackgroundColor); + BackgroundColor = CommonWPF.Helper.ColorToInt(mlc.BackgroundColor); } } public class SerSymbol { @@ -730,7 +730,7 @@ namespace SourceGen { continue; } proj.Notes[intKey] = new MultiLineComment(kvp.Value.Text, - ColorFromInt(kvp.Value.BackgroundColor)); + CommonWPF.Helper.ColorFromInt(kvp.Value.BackgroundColor)); } // Deserialize user-defined labels. @@ -1261,14 +1261,5 @@ namespace SourceGen { return false; } } - - private static int ColorToInt(Color color) { - return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B; - } - - private static Color ColorFromInt(int colorInt) { - return Color.FromArgb((byte)(colorInt >> 24), (byte)(colorInt >> 16), - (byte)(colorInt >> 8), (byte)colorInt); - } } } diff --git a/SourceGen/Sgec.cs b/SourceGen/Sgec.cs index ef84c82..d02d053 100644 --- a/SourceGen/Sgec.cs +++ b/SourceGen/Sgec.cs @@ -18,6 +18,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; +using System.Text.RegularExpressions; +using System.Web.Script.Serialization; + +using CommonUtil; namespace SourceGen { /// @@ -30,6 +34,56 @@ namespace SourceGen { public static class Sgec { public const string SGEC_EXT = "_sgec.txt"; + // Commands. + private const string SET_COMMENT = "set-comment"; + private const string SET_LONG_COMMENT = "set-long-comment"; + private const string SET_NOTE = "set-note"; + + // Regular expression used for parsing lines. + private const string LINE_PATTERN = @"^([A-Za-z-]+)\s([^:]+):(.+)$"; + private static Regex sLineRegex = new Regex(LINE_PATTERN); + private const int GROUP_CMD = 1; + private const int GROUP_POS = 2; + private const int GROUP_VALUE = 3; + + private static bool OUTPUT_ADDR = true; // addr vs. offset + + // Lifted from ProjectFile. + public class SerMultiLineComment { + // NOTE: Text must be CRLF at line breaks. + public string Text { get; set; } + public bool BoxMode { get; set; } + public int MaxWidth { get; set; } + public int BackgroundColor { get; set; } + + public SerMultiLineComment() { } + public SerMultiLineComment(MultiLineComment mlc) { + Text = mlc.Text; + BoxMode = mlc.BoxMode; + MaxWidth = mlc.MaxWidth; + BackgroundColor = CommonWPF.Helper.ColorToInt(mlc.BackgroundColor); + } + } + + /// + /// Generates a position string, which may be a file offset, an address, or a + /// position delta. + /// + private static string PositionStr(int offset, int prevOffset, AddressMap addrMap, + bool relMode) { + if (prevOffset < 0 || !relMode) { + // hex offset or address + if (OUTPUT_ADDR) { + return '$' + addrMap.OffsetToAddress(offset).ToString("x4"); + } else { + return '+' + offset.ToString("x6"); + } + } else { + // decimal delta + return '>' + (offset - prevOffset).ToString(); + } + } + /// /// Exports comments in SGEC format. /// @@ -37,19 +91,43 @@ namespace SourceGen { /// Project object. /// Details on failure or success. /// True on success. - public static bool ExportToFile(string pathName, DisasmProject proj, out string detailMsg) { - int numComments = 0; + public static bool ExportToFile(string pathName, DisasmProject proj, bool relMode, + out string detailMsg) { + int numItems = 0; + JavaScriptSerializer ser = new JavaScriptSerializer(); + + int prevOffset = -1; using (StreamWriter sw = new StreamWriter(pathName, false, new UTF8Encoding(false))) { for (int offset = 0; offset < proj.FileDataLength; offset++) { if (!string.IsNullOrEmpty(proj.Comments[offset])) { - sw.WriteLine("set-comment +" + offset.ToString("x6") + ':' + + sw.WriteLine(SET_COMMENT + " " + + PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' + proj.Comments[offset]); - numComments++; + prevOffset = offset; + numItems++; + } + if (proj.LongComments.TryGetValue(offset, out MultiLineComment lc)) { + SerMultiLineComment serCom = new SerMultiLineComment(lc); + string cereal = ser.Serialize(serCom); + sw.WriteLine(SET_LONG_COMMENT + " " + + PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' + + cereal); + prevOffset = offset; + numItems++; + } + if (proj.Notes.TryGetValue(offset, out MultiLineComment nt)) { + SerMultiLineComment serCom = new SerMultiLineComment(nt); + string cereal = ser.Serialize(serCom); + sw.WriteLine(SET_NOTE + " " + + PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' + + cereal); + prevOffset = offset; + numItems++; } } } - detailMsg = "Exported " + numComments + " comments."; + detailMsg = "exported " + numItems + " items."; return true; } @@ -72,49 +150,139 @@ namespace SourceGen { return false; } - List changed = new List(lines.Length); + JavaScriptSerializer ser = new JavaScriptSerializer(); - string setComment = "set-comment +"; + int lineNum = 0; + int prevOffset = -1; foreach (string line in lines) { - if (!line.StartsWith(setComment)) { - Debug.WriteLine("Ignoring " + line); + lineNum++; // first line is 1 + if (string.IsNullOrEmpty(line) || line[0] == '#') { + // ignore continue; } - - int offset; - try { - offset = Convert.ToInt32(line.Substring(setComment.Length, 6), 16); - } catch (Exception ex) { - Debug.WriteLine("Failed on " + line); - detailMsg = ex.Message; + MatchCollection matches = sLineRegex.Matches(line); + if (matches.Count != 1) { + detailMsg = "Line " + lineNum + ": unable to parse into tokens"; return false; } - if (changed.Contains(offset)) { - Debug.WriteLine("Skipping repeated entry +" + offset.ToString("X6")); - continue; + string posStr = matches[0].Groups[GROUP_POS].Value; + int offset; + if (posStr[0] == '+') { + // offset + if (!Asm65.Number.TryParseIntHex(posStr.Substring(1), out offset)) { + detailMsg = "Line " + lineNum + ": unable to parse offset '" + + posStr + "'"; + return false; + } + } else if (posStr[0] == '$') { + // address + if (!Asm65.Address.ParseAddress(posStr, (1 << 24) - 1, out int addr)) { + detailMsg = "Line " + lineNum + ": unable to parse address '" + + posStr + "'"; + return false; + } + offset = proj.AddrMap.AddressToOffset(0, addr); + } else if (posStr[0] == '>') { + // relative offset + if (prevOffset < 0) { + detailMsg = "Line " + lineNum + ": first address/offset cannot be relative"; + return false; + } + if (!Asm65.Number.TryParseInt(posStr.Substring(1), out int delta, out int _)) { + detailMsg = "Line " + lineNum + ": unable to parse delta"; + return false; + } + offset = prevOffset + delta; + } else { + detailMsg = "Line " + lineNum + ": unknown position type '" + posStr[0] + "'"; + return false; } - string oldComment = proj.Comments[offset]; - string newComment = line.Substring(setComment.Length + 7); - if (oldComment == newComment) { - // no change - continue; - } - if (!string.IsNullOrEmpty(oldComment)) { - // overwriting existing entry - Debug.WriteLine("Replacing comment +" + offset.ToString("x6") + - " '" + oldComment + "'"); + prevOffset = offset; + + string cmdStr = matches[0].Groups[GROUP_CMD].Value; + string valueStr = matches[0].Groups[GROUP_VALUE].Value; + switch (cmdStr) { + case SET_COMMENT: { + string oldComment = proj.Comments[offset]; + string newComment = valueStr; + if (oldComment == newComment) { + // no change + break; + } + if (!string.IsNullOrEmpty(oldComment)) { + // overwriting existing entry; make a note + Debug.WriteLine("Replacing comment +" + offset.ToString("x6") + + " '" + oldComment + "'"); + } + UndoableChange uc = UndoableChange.CreateCommentChange(offset, + oldComment, newComment); + cs.Add(uc); + } + break; + case SET_LONG_COMMENT: { + if (!DeserializeMlc(ser, valueStr, false, + out MultiLineComment newComment)) { + detailMsg = "Line " + lineNum + ": failed to deserialize value"; + return false; + } + proj.LongComments.TryGetValue(offset, out MultiLineComment oldComment); + if (oldComment == newComment) { + // no change + break; + } + UndoableChange uc = UndoableChange.CreateLongCommentChange(offset, + oldComment, newComment); + cs.Add(uc); + } + break; + case SET_NOTE: { + if (!DeserializeMlc(ser, valueStr, true, + out MultiLineComment newNote)) { + detailMsg = "Line " + lineNum + ": failed to deserialize value"; + return false; + } + proj.Notes.TryGetValue(offset, out MultiLineComment oldNote); + if (oldNote == newNote) { + // no change + break; + } + UndoableChange uc = UndoableChange.CreateNoteChange(offset, + oldNote, newNote); + cs.Add(uc); + + } + break; + default: + detailMsg = "Line " + lineNum + ": unknown command '" + cmdStr + "'"; + return false; } - UndoableChange uc = UndoableChange.CreateCommentChange(offset, - oldComment, newComment); - cs.Add(uc); - changed.Add(offset); } detailMsg = "applied " + cs.Count + " changes."; return true; } + + private static bool DeserializeMlc(JavaScriptSerializer ser, string cereal, bool isNote, + out MultiLineComment mlc) { + mlc = null; + SerMultiLineComment smlc; + try { + smlc = ser.Deserialize(cereal); + } catch (Exception ex) { + Debug.WriteLine("Deserialization failed: " + ex.Message); + return false; + } + + if (isNote) { + mlc = new MultiLineComment(smlc.Text, + CommonWPF.Helper.ColorFromInt(smlc.BackgroundColor)); + } else { + mlc = new MultiLineComment(smlc.Text, smlc.BoxMode, smlc.MaxWidth); + } + return true; + } } }