/*
* Copyright 2020 faddenSoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
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 {
///
/// SourceGen Edit Commands implementation.
///
///
/// This is an "experimental feature", meaning it's not in its final form and may be
/// lacking in features or error checking.
///
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 IsFancy { 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;
IsFancy = mlc.IsFancy;
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.
///
/// File to write to.
/// Project object.
/// Details on failure or success.
/// True on success.
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 (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++;
}
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 (!string.IsNullOrEmpty(proj.Comments[offset])) {
sw.WriteLine(SET_COMMENT + " " +
PositionStr(offset, prevOffset, proj.AddrMap, relMode) + ':' +
proj.Comments[offset]);
prevOffset = offset;
numItems++;
}
}
}
detailMsg = "exported " + numItems + " items.";
return true;
}
///
/// Import comments in SGEC format.
///
/// File to read from.
/// Project object.
/// Change set that will hold changes.
/// Failure detail, or null on success.
/// True on success.
public static bool ImportFromFile(string pathName, DisasmProject proj, ChangeSet cs,
out string detailMsg) {
string[] lines;
try {
lines = File.ReadAllLines(pathName);
} catch (IOException ex) {
// not expecting this to happen
detailMsg = ex.Message;
return false;
}
JavaScriptSerializer ser = new JavaScriptSerializer();
int lineNum = 0;
int prevOffset = -1;
foreach (string line in lines) {
lineNum++; // first line is 1
if (string.IsNullOrEmpty(line) || line[0] == '#') {
// ignore
continue;
}
MatchCollection matches = sLineRegex.Matches(line);
if (matches.Count != 1) {
detailMsg = "Line " + lineNum + ": unable to parse into tokens";
return false;
}
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;
}
prevOffset = offset;
if (!proj.GetAnattrib(offset).IsStart) {
// This causes problems when we try to do a LineListGen update, because
// we specifically request it to do the modified offset, which happens to
// be in the middle of an instruction, and it gets very confused.
detailMsg = "Line " + lineNum + ": attempt to modify middle of instr/data item";
return false;
}
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;
}
}
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.IsFancy, smlc.BoxMode, smlc.MaxWidth);
}
return true;
}
}
}