516 lines
19 KiB
C#
516 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.IO;
|
|
|
|
namespace ScriptTool
|
|
{
|
|
public class Compiler : ICompiler
|
|
{
|
|
private static int[] virtualWidths;
|
|
private static int[] renderWidths;
|
|
private static int[] virtualWidthsSaturn;
|
|
private static int[] renderWidthsSaturn;
|
|
|
|
public IEnumerable<IControlCode> ControlCodes { get; set; }
|
|
public Dictionary<string, int> AddressMap { get; set; }
|
|
public Func<byte[], int, bool> ControlCodePredicate { get; set; }
|
|
|
|
static Compiler()
|
|
{
|
|
byte[] widths = Asset.ReadAllBytes("m2-widths-main.bin");
|
|
byte[] saturnWidths = Asset.ReadAllBytes("m2-widths-saturn.bin");
|
|
virtualWidths = new int[widths.Length / 2];
|
|
renderWidths = new int[widths.Length / 2];
|
|
virtualWidthsSaturn = new int[saturnWidths.Length / 2];
|
|
renderWidthsSaturn = new int[saturnWidths.Length / 2];
|
|
|
|
for (int i = 0; i < widths.Length; i += 2)
|
|
{
|
|
virtualWidths[i / 2] = widths[i];
|
|
renderWidths[i / 2] = widths[i + 1];
|
|
virtualWidthsSaturn[i / 2] = saturnWidths[i];
|
|
renderWidthsSaturn[i / 2] = saturnWidths[i + 1];
|
|
}
|
|
}
|
|
|
|
public Compiler(IEnumerable<IControlCode> controlCodes,
|
|
Func<byte[], int, bool> controlCodePredicate)
|
|
{
|
|
ControlCodes = controlCodes;
|
|
ControlCodePredicate = controlCodePredicate;
|
|
|
|
AddressMap = new Dictionary<string, int>();
|
|
}
|
|
|
|
public static bool IsHexByte(string str)
|
|
{
|
|
try
|
|
{
|
|
Convert.ToByte(str, 16);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private byte GetByte(char c, IDictionary<byte, string> charLookup)
|
|
{
|
|
return charLookup.First(kv => kv.Value[0] == c).Key; // lazy
|
|
}
|
|
|
|
public void ScanString(string str, ref int referenceAddress, IDictionary<byte, string> charLookup, bool scanCodesOnly)
|
|
{
|
|
ISet<IControlCode> codes;
|
|
IList<string> references;
|
|
ScanString(str, ref referenceAddress, charLookup, scanCodesOnly, out references, out codes);
|
|
}
|
|
|
|
public void ScanString(string str, IDictionary<byte, string> charLookup, bool scanCodesOnly,
|
|
out IList<string> references)
|
|
{
|
|
int temp = 0;
|
|
ISet<IControlCode> codes;
|
|
ScanString(str, ref temp, charLookup, scanCodesOnly, out references, out codes);
|
|
}
|
|
|
|
public void ScanString(string str, IDictionary<byte, string> charLookup, bool scanCodesOnly,
|
|
out ISet<IControlCode> codes)
|
|
{
|
|
int temp = 0;
|
|
IList<string> references;
|
|
ScanString(str, ref temp, charLookup, scanCodesOnly, out references, out codes);
|
|
}
|
|
|
|
public void ScanString(string str, IDictionary<byte, string> charLookup, bool scanCodesOnly,
|
|
out IList<string> references, out ISet<IControlCode> controlCodes)
|
|
{
|
|
int temp = 0;
|
|
ScanString(str, ref temp, charLookup, scanCodesOnly, out references, out controlCodes);
|
|
}
|
|
|
|
public void ScanString(string str, ref int referenceAddress, IDictionary<byte, string> charLookup, bool scanCodesOnly,
|
|
out IList<string> references, out ISet<IControlCode> controlCodes)
|
|
{
|
|
references = new List<string>();
|
|
controlCodes = new HashSet<IControlCode>();
|
|
|
|
for (int i = 0; i < str.Length; )
|
|
{
|
|
if (str[i] == '[')
|
|
{
|
|
if (str.IndexOf(']', i + 1) == -1)
|
|
throw new Exception("Opening bracket has no matching closing bracket: position " + i);
|
|
|
|
string[] codeStrings = str.Substring(i + 1, str.IndexOf(']', i + 1) - i - 1)
|
|
.Split(' ');
|
|
|
|
IControlCode code = ControlCodes.FirstOrDefault(c => c.IsMatch(codeStrings));
|
|
if (!controlCodes.Contains(code))
|
|
{
|
|
controlCodes.Add(code);
|
|
}
|
|
|
|
foreach (var codeString in codeStrings)
|
|
{
|
|
if (codeString[0] == '_')
|
|
{
|
|
if (codeString[codeString.Length - 1] != '_')
|
|
throw new Exception("Reference has no closing underscore: position " + i);
|
|
|
|
if (codeString.Length <= 2)
|
|
throw new Exception("Reference is empty: position " + i);
|
|
|
|
if (!scanCodesOnly)
|
|
referenceAddress += 4;
|
|
|
|
references.Add(codeString.Substring(1, codeString.Length - 2));
|
|
}
|
|
else if (IsHexByte(codeString))
|
|
{
|
|
if (!scanCodesOnly)
|
|
referenceAddress++;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception(String.Format(
|
|
"Encountered invalid code string at position {0}: {1}", i, codeString));
|
|
}
|
|
}
|
|
|
|
i = str.IndexOf(']', i + 1) + 1;
|
|
}
|
|
else if (str[i] == ']')
|
|
{
|
|
throw new Exception("Closing bracket has no matching opening bracket: position " + i);
|
|
}
|
|
else if (str[i] == '^')
|
|
{
|
|
if (str.IndexOf('^', i + 1) == -1)
|
|
throw new Exception("Label has no matching closing caret: position " + i);
|
|
|
|
string label = str.Substring(i + 1, str.IndexOf('^', i + 1) - i - 1);
|
|
|
|
if (AddressMap.ContainsKey(label))
|
|
throw new Exception("Label " + label + " already defined: position " + i);
|
|
|
|
if (!scanCodesOnly)
|
|
AddressMap.Add(label, referenceAddress);
|
|
|
|
i = str.IndexOf('^', i + 1) + 1;
|
|
}
|
|
else
|
|
{
|
|
if (!(str[i] == '\r') && !(str[i] == '\n'))
|
|
{
|
|
if (!scanCodesOnly)
|
|
{
|
|
GetByte(str[i], charLookup); // just check if it's valid
|
|
referenceAddress++;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CompileString(string str, IList<byte> buffer, ref int referenceAddress, IDictionary<byte, string> charLookup)
|
|
{
|
|
CompileString(str, buffer, ref referenceAddress, charLookup, -1);
|
|
}
|
|
|
|
public void CompileString(string str, IList<byte> buffer, ref int referenceAddress, IDictionary<byte, string> charLookup, int padLength)
|
|
{
|
|
int previousBufferSize = buffer.Count;
|
|
|
|
for (int i = 0; i < str.Length; )
|
|
{
|
|
if (str[i] == '[')
|
|
{
|
|
if (str.IndexOf(']', i + 1) == -1)
|
|
throw new Exception("Opening bracket has no matching closing bracket: position " + i);
|
|
|
|
string[] codeStrings = str.Substring(i + 1, str.IndexOf(']', i + 1) - i - 1)
|
|
.Split(' ');
|
|
|
|
// Match the code
|
|
IControlCode code = ControlCodes.FirstOrDefault(c => c.IsMatch(codeStrings));
|
|
|
|
if (code == null)
|
|
{
|
|
// Direct copy
|
|
for (int j = 0; j < codeStrings.Length; j++)
|
|
{
|
|
if (!IsHexByte(codeStrings[j]))
|
|
throw new Exception("Code string for unrecognized control code block must be a byte literal: position " + i);
|
|
|
|
byte value = byte.Parse(codeStrings[j], System.Globalization.NumberStyles.HexNumber);
|
|
if (buffer != null)
|
|
buffer.Add(value);
|
|
referenceAddress++;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
// Validate
|
|
if (!code.IsValid(codeStrings))
|
|
throw new Exception("Invalid control code: position " + i);
|
|
|
|
// Parse
|
|
code.Compile(codeStrings, buffer, ref referenceAddress, AddressMap);
|
|
}
|
|
|
|
i = str.IndexOf(']', i + 1) + 1;
|
|
}
|
|
else if (str[i] == ']')
|
|
{
|
|
throw new Exception("Closing bracket has no matching opening bracket: position " + i);
|
|
}
|
|
else if (str[i] == '^')
|
|
{
|
|
if (str.IndexOf('^', i + 1) == -1)
|
|
throw new Exception("Label has no matching closing caret: position " + i);
|
|
|
|
i = str.IndexOf('^', i + 1) + 1;
|
|
}
|
|
else
|
|
{
|
|
if (!(str[i] == '\r') && !(str[i] == '\n'))
|
|
{
|
|
byte value = GetByte(str[i], charLookup);
|
|
|
|
if (buffer != null)
|
|
buffer.Add(value);
|
|
|
|
referenceAddress++;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// Pad the remaining bytes
|
|
if (padLength != -1)
|
|
{
|
|
int bytesWritten = buffer.Count - previousBufferSize;
|
|
|
|
if (bytesWritten > padLength)
|
|
throw new Exception("Exceeded pad length: wrote " + bytesWritten +
|
|
" bytes, but the pad length is " + padLength + " bytes");
|
|
|
|
for (int i = bytesWritten; i < padLength; i++)
|
|
{
|
|
if (buffer != null)
|
|
buffer.Add(0);
|
|
|
|
referenceAddress++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public string StripText(string str)
|
|
{
|
|
if (str == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
for (int i = 0; i < str.Length; )
|
|
{
|
|
if (str[i] == '[')
|
|
{
|
|
if (str.IndexOf(']', i + 1) == -1)
|
|
throw new Exception("Opening bracket has no matching closing bracket: position " + i);
|
|
|
|
sb.Append(str.Substring(i, str.IndexOf(']', i + 1) - i + 1));
|
|
|
|
i = str.IndexOf(']', i + 1) + 1;
|
|
}
|
|
else if (str[i] == ']')
|
|
{
|
|
throw new Exception("Closing bracket has no matching opening bracket: position " + i);
|
|
}
|
|
else if (str[i] == '^')
|
|
{
|
|
if (str.IndexOf('^', i + 1) == -1)
|
|
throw new Exception("Label has no matching closing caret: position " + i);
|
|
|
|
sb.Append(str.Substring(i, str.IndexOf('^', i + 1) - i + 1));
|
|
|
|
i = str.IndexOf('^', i + 1) + 1;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public IList<string> FormatPreviewM12(string str, out IList<int> widths, IDictionary<byte, string> charLookup)
|
|
{
|
|
var sb = new StringBuilder();
|
|
widths = new List<int>();
|
|
int currentWidth = 0;
|
|
bool useSaturnWidths = false;
|
|
|
|
var strings = new List<string>();
|
|
|
|
for (int i = 0; i < str.Length; )
|
|
{
|
|
if (str[i] == '[')
|
|
{
|
|
if (str.IndexOf(']', i + 1) == -1)
|
|
throw new Exception("Opening bracket has no matching closing bracket: position " + i);
|
|
|
|
string[] codeStrings = str.Substring(i + 1, str.IndexOf(']', i + 1) - i - 1)
|
|
.Split(' ');
|
|
|
|
M12ControlCode code = (M12ControlCode)ControlCodes.FirstOrDefault(c => c.IsMatch(codeStrings));
|
|
|
|
foreach (var codeString in codeStrings)
|
|
{
|
|
if (codeString[0] == '_')
|
|
{
|
|
if (codeString[codeString.Length - 1] != '_')
|
|
throw new Exception("Reference has no closing underscore: position " + i);
|
|
|
|
if (codeString.Length <= 2)
|
|
throw new Exception("Reference is empty: position " + i);
|
|
}
|
|
else if (!IsHexByte(codeString))
|
|
{
|
|
throw new Exception(String.Format(
|
|
"Encountered invalid code string at position {0}: {1}", i, codeString));
|
|
}
|
|
}
|
|
|
|
i = str.IndexOf(']', i + 1) + 1;
|
|
|
|
if (code == null)
|
|
{
|
|
// Not matched to anything -- check if it's a valid character sequence
|
|
foreach (var codeString in codeStrings)
|
|
{
|
|
if (!IsHexByte(codeString))
|
|
{
|
|
sb.Append("[INVALID]");
|
|
}
|
|
else
|
|
{
|
|
byte b = Convert.ToByte(codeString, 16);
|
|
if (!charLookup.ContainsKey(b))
|
|
{
|
|
sb.Append("[INVALID]");
|
|
}
|
|
else
|
|
{
|
|
sb.Append(charLookup[b]);
|
|
currentWidth += virtualWidths[b - 0x50];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (code.Identifier)
|
|
{
|
|
case 0xC:
|
|
case 0xD:
|
|
case 0xE:
|
|
case 0xF:
|
|
case 0x10:
|
|
case 0x11:
|
|
case 0x12:
|
|
case 0x15:
|
|
case 0x2D:
|
|
case 0x9F:
|
|
case 0xAD:
|
|
// Name code
|
|
sb.Append("[NAME]");
|
|
currentWidth += 60;
|
|
break;
|
|
|
|
case 0x1A:
|
|
// Name (60/96) or item (80/??) or number (36/72).
|
|
sb.Append("[NAME]");
|
|
currentWidth += 80;
|
|
break;
|
|
|
|
case 0x0:
|
|
case 0x1:
|
|
case 0x2:
|
|
case 0x3:
|
|
// Line break
|
|
strings.Add(sb.ToString());
|
|
sb.Clear();
|
|
widths.Add(currentWidth);
|
|
currentWidth = 0;
|
|
break;
|
|
|
|
case 0x20:
|
|
sb.Append("[SMAAAASH!!]");
|
|
currentWidth += 72;
|
|
break;
|
|
|
|
case 0x21:
|
|
sb.Append("[YOU WON!]");
|
|
currentWidth += 72;
|
|
break;
|
|
|
|
case 0x23:
|
|
case 0x63:
|
|
case 0xB7:
|
|
sb.Append("[MONEY]");
|
|
currentWidth += 36;
|
|
break;
|
|
|
|
case 0x24:
|
|
case 0x25:
|
|
case 0x26:
|
|
case 0x27:
|
|
case 0x28:
|
|
case 0x29:
|
|
case 0x2A:
|
|
case 0x2B:
|
|
sb.Append("[STAT]");
|
|
currentWidth += 18;
|
|
break;
|
|
|
|
case 0x1F:
|
|
sb.Append("_");
|
|
currentWidth += 10;
|
|
break;
|
|
|
|
case 0x7B:
|
|
useSaturnWidths = true;
|
|
break;
|
|
|
|
case 0x7C:
|
|
useSaturnWidths = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (str[i] == ']')
|
|
{
|
|
throw new Exception("Closing bracket has no matching opening bracket: position " + i);
|
|
}
|
|
else if (str[i] == '^')
|
|
{
|
|
if (str.IndexOf('^', i + 1) == -1)
|
|
throw new Exception("Label has no matching closing caret: position " + i);
|
|
|
|
string label = str.Substring(i + 1, str.IndexOf('^', i + 1) - i - 1);
|
|
|
|
i = str.IndexOf('^', i + 1) + 1;
|
|
}
|
|
else
|
|
{
|
|
if (!(str[i] == '\r') && !(str[i] == '\n'))
|
|
{
|
|
sb.Append(str[i]);
|
|
int v = GetByte(str[i], charLookup) - 0x50;
|
|
currentWidth += useSaturnWidths ? virtualWidthsSaturn[v] : virtualWidths[v];
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (sb.Length > 0)
|
|
{
|
|
strings.Add(sb.ToString());
|
|
widths.Add(currentWidth);
|
|
}
|
|
|
|
return strings;
|
|
}
|
|
|
|
public IControlCode GetLastControlCode(string str)
|
|
{
|
|
if (str.Length < 2)
|
|
return null;
|
|
|
|
if (!(str[str.Length - 1] == ']'))
|
|
return null;
|
|
|
|
int lastOpenBracket = str.LastIndexOf('[');
|
|
|
|
if (lastOpenBracket < str.LastIndexOf(']', str.Length - 2))
|
|
return null;
|
|
|
|
var codeStrings = str.Substring(lastOpenBracket + 1, str.Length - lastOpenBracket - 2)
|
|
.Split(' ');
|
|
|
|
return ControlCodes.FirstOrDefault(c => c.IsMatch(codeStrings));
|
|
}
|
|
}
|
|
}
|