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.
This commit is contained in:
Andy McFadden 2020-07-15 17:43:08 -07:00
parent c5d764d11f
commit 0860a00a54
4 changed files with 233 additions and 45 deletions

View File

@ -78,5 +78,22 @@ namespace CommonWPF {
return fmt.Width;
}
/// <summary>
/// Converts a System.Windows.Media.Color value into 32-bit ARGB.
/// </summary>
public static int ColorToInt(Color color) {
return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
}
/// <summary>
/// Creates a System.Windows.Media.Color value from 32-bit ARGB.
/// </summary>
/// <param name="colorInt"></param>
/// <returns></returns>
public static Color ColorFromInt(int colorInt) {
return Color.FromArgb((byte)(colorInt >> 24), (byte)(colorInt >> 16),
(byte)(colorInt >> 8), (byte)colorInt);
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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 {
/// <summary>
@ -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);
}
}
/// <summary>
/// Generates a position string, which may be a file offset, an address, or a
/// position delta.
/// </summary>
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();
}
}
/// <summary>
/// Exports comments in SGEC format.
/// </summary>
@ -37,19 +91,43 @@ namespace SourceGen {
/// <param name="proj">Project object.</param>
/// <param name="detailMsg">Details on failure or success.</param>
/// <returns>True on success.</returns>
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<int> changed = new List<int>(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<SerMultiLineComment>(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;
}
}
}