-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
label importer: improve label importer
- importer logic is larger enough that this deserved a little more love. break it out into a few classes - better support for BSNES+ formats (the one I am using was a little different but pretty compatible with the previous version) - slightly better error handling
- Loading branch information
1 parent
6cdfa21
commit 73223cf
Showing
8 changed files
with
242 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
using System.Globalization; | ||
using System.Text.RegularExpressions; | ||
using Diz.Core.Interfaces; | ||
using Diz.Core.model; | ||
using Diz.Core.util; | ||
using Diz.Import.bsnes; | ||
|
||
namespace Diz.Import; | ||
|
||
public abstract class LabelImporter | ||
{ | ||
// after import, if there was an error, this will be the line# of what it was. | ||
// if -1, we parsed the entire file. | ||
public int LastErrorLineNumber { get; private set; } = -1; | ||
|
||
// we don't modify labels in the open project directly, instead we read them | ||
// into here and only return this on success. | ||
private readonly Dictionary<int, IAnnotationLabel> newLabels = new(); | ||
|
||
public virtual Dictionary<int, IAnnotationLabel> ReadLabelsFromFile(string importFilename) | ||
{ | ||
newLabels.Clear(); | ||
LastErrorLineNumber = 0; | ||
|
||
var lineIndex = 0; | ||
foreach (var line in Util.ReadLines(importFilename)) | ||
{ | ||
LastErrorLineNumber = lineIndex + 1; | ||
ParseLine(line); | ||
lineIndex++; | ||
} | ||
|
||
if (lineIndex == 0) | ||
throw new InvalidDataException("No lines in file, can't import."); | ||
|
||
LastErrorLineNumber = -1; | ||
return newLabels; | ||
} | ||
|
||
private void ParseLine(string line) | ||
{ | ||
var labelFound = TryParseLabelFromLine(line); | ||
if (labelFound == null) | ||
return; | ||
|
||
var (label, labelAddress) = labelFound.Value; | ||
TryImportLabel(label, labelAddress); | ||
} | ||
|
||
private void TryImportLabel(IAnnotationLabel label, string labelAddress) | ||
{ | ||
var validLabelChars = new Regex(@"^([a-zA-Z0-9_\-]*)$"); | ||
if (!validLabelChars.Match(label.Name).Success) | ||
throw new InvalidDataException("invalid label name: " + label.Name); | ||
|
||
var address = int.Parse(labelAddress, NumberStyles.HexNumber, null); | ||
if (!newLabels.ContainsKey(address)) | ||
{ | ||
newLabels.Add(address, label); | ||
} | ||
else | ||
{ | ||
// Update empty label properties instead of overwriting the entire object | ||
// if there are multiple definitions (like from BSNES or handmade CSV) | ||
var thisLabel = newLabels[address]; | ||
|
||
if (thisLabel.Name.IsEmpty()) | ||
thisLabel.Name = label.Name; | ||
|
||
if (thisLabel.Comment.IsEmpty()) | ||
thisLabel.Comment = label.Comment; | ||
} | ||
} | ||
|
||
protected abstract (IAnnotationLabel label, string labelAddress)? TryParseLabelFromLine(string line); | ||
} | ||
|
||
public static class LabelImporterUtils | ||
{ | ||
// exception handling/line# stuff needs a little rework, messy. | ||
public static void ImportLabelsFromCsv(this ILabelProvider labelProvider, string importFilename, bool replaceAll, out int errLine) | ||
{ | ||
// could probably do this part more elegantly | ||
errLine = 0; | ||
LabelImporter? importer = null; | ||
if (BsnesSymbolLabelImporter.IsFileCompatible(importFilename)) | ||
{ | ||
importer = new BsnesSymbolLabelImporter(); | ||
} | ||
else if (LabelImporterCsv.IsFileCompatible(importFilename)) | ||
{ | ||
importer = new LabelImporterCsv(); | ||
} | ||
|
||
if (importer == null) | ||
{ | ||
throw new InvalidDataException($"No importer was found that can import a file named:\n'{importFilename}'"); | ||
} | ||
|
||
var labelsFromFile = importer.ReadLabelsFromFile(importFilename); | ||
if (importer.LastErrorLineNumber != -1) | ||
{ | ||
errLine = importer.LastErrorLineNumber; | ||
throw new InvalidDataException( | ||
$"Error importing file:\n'{importFilename}'\nNear line#: {importer.LastErrorLineNumber}"); | ||
} | ||
|
||
if (replaceAll) | ||
labelProvider.DeleteAllLabels(); | ||
|
||
labelProvider.AppendLabels(labelsFromFile); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using Diz.Core.Interfaces; | ||
using Diz.Core.model; | ||
using Diz.Core.util; | ||
|
||
namespace Diz.Import; | ||
|
||
public class LabelImporterCsv : LabelImporter | ||
{ | ||
public static bool IsFileCompatible(string importFilename) => | ||
importFilename.ToLower().EndsWith(".csv"); | ||
|
||
protected override (IAnnotationLabel label, string labelAddress)? TryParseLabelFromLine(string line) | ||
{ | ||
// TODO: replace with something better. this is kind of a risky/fragile way to parse CSV lines. | ||
// it won't deal with weirdness in the comments, quotes, etc. | ||
Util.SplitOnFirstComma(line, out var labelAddress, out var remainder); | ||
Util.SplitOnFirstComma(remainder, out var labelName, out var labelComment); | ||
var label = new Label | ||
{ | ||
Name = labelName.Trim(), | ||
Comment = labelComment | ||
}; | ||
return (label, labelAddress); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using Diz.Core.Interfaces; | ||
using Diz.Core.model; | ||
|
||
namespace Diz.Import.bsnes; | ||
|
||
// there's a few different flavors of .sym.cpu files. | ||
// one is here: https://github.com/BenjaminSchulte/fma-snes65816/blob/master/docs/symbols.adoc | ||
// another is from the BSNES+ debugger, which is slightly different. try and support both here if we can, or, split out the parser if needed. | ||
|
||
public class BsnesSymbolLabelImporter : LabelImporter | ||
{ | ||
private string currentBsnesSection = ""; | ||
|
||
public override Dictionary<int, IAnnotationLabel> ReadLabelsFromFile(string importFilename) | ||
{ | ||
currentBsnesSection = ""; | ||
return base.ReadLabelsFromFile(importFilename); | ||
} | ||
|
||
public static bool IsFileCompatible(string importFilename) | ||
{ | ||
// Coming in from BSNES symbol map if it begins with the header | ||
return importFilename.ToLower().EndsWith(".cpu.sym"); | ||
|
||
// here's another way to check if the file contents match. | ||
// this signature can be present (but isn't always) present in some BSNES versions (it's not in BSNES+) | ||
// the above filename extension check is probably sufficient for all of it though | ||
// lines.Length > 0 && lines[0].StartsWith("#SNES65816"); | ||
} | ||
|
||
protected override (IAnnotationLabel label, string labelAddress)? TryParseLabelFromLine(string line) | ||
{ | ||
if (ShouldSkipLineBecauseCommentOrWhitespace(line)) | ||
return null; | ||
|
||
// did we enter a new section? if so, note it, and move to next line | ||
if (TryParseBsnesSection(line)) | ||
return null; | ||
|
||
switch (currentBsnesSection) | ||
{ | ||
case "[LABELS]": | ||
case "[SYMBOL]": | ||
{ | ||
var symbols = line.Trim().Split(' '); | ||
var labelAddress = ParseSnesAddress(symbols[0]); | ||
var label = new Label | ||
{ | ||
Name = symbols[1].Replace(".", "_") // Replace dots which are valid in BSNES | ||
}; | ||
return (label, labelAddress); | ||
} | ||
case "[COMMENT]": | ||
{ | ||
var comments = line.Trim().Split(' ', 2); | ||
var labelAddress = ParseSnesAddress(comments[0]); | ||
var label = new Label | ||
{ | ||
Comment = comments[1].Replace("\"", "") // Remove quotes | ||
}; | ||
return (label, labelAddress); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private bool TryParseBsnesSection(string line) | ||
{ | ||
// BSNES symbol files are multiple INI sections like "[symbol]" | ||
// we only care about a few of them for Diztinguish | ||
// if we hit a section header, consume it, keep going | ||
if (!line.StartsWith('[') || !line.EndsWith(']')) | ||
return false; | ||
|
||
currentBsnesSection = line.ToUpper(); | ||
return true; | ||
} | ||
|
||
private static string ParseSnesAddress(string symbols) => | ||
symbols.Replace(":", "").ToUpper(); | ||
|
||
private static bool ShouldSkipLineBecauseCommentOrWhitespace(string line) => | ||
line.Trim().Length == 0 || line.StartsWith('#') || line.StartsWith(';'); | ||
} |
Oops, something went wrong.