diff --git a/.gitignore b/.gitignore
index 23616f6..68f5431 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
-################################################################################
-# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
-################################################################################
-ColorzCore/obj
-ColorzCore/.vs
+obj/
+bin/
+.vs/
*.cache
diff --git a/ColorzCore/App.config b/ColorzCore/App.config
deleted file mode 100644
index d740e88..0000000
--- a/ColorzCore/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ColorzCore/ColorzCore.Framework.csproj b/ColorzCore/ColorzCore.Framework.csproj
new file mode 100644
index 0000000..fc5ed33
--- /dev/null
+++ b/ColorzCore/ColorzCore.Framework.csproj
@@ -0,0 +1,10 @@
+
+
+ net48
+ Exe
+ annotations
+ ColorzCore
+ 9
+ bin/Framework
+
+
diff --git a/ColorzCore/ColorzCore.csproj b/ColorzCore/ColorzCore.csproj
index 842e13f..11a245f 100644
--- a/ColorzCore/ColorzCore.csproj
+++ b/ColorzCore/ColorzCore.csproj
@@ -1,124 +1,9 @@
-
-
-
+
- Debug
- AnyCPU
- {B98F7CCF-9CAA-406E-88D7-2040FA99F631}
+ net6.0
Exe
- ColorzCore
- ColorzCore
- v4.5.2
- 512
- true
+ enable
+ 9
+ bin/Core
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- bin\32bit\
- x86
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ColorzCore/ColorzCore.sln b/ColorzCore/ColorzCore.sln
index f0a2e0f..805d6ec 100644
--- a/ColorzCore/ColorzCore.sln
+++ b/ColorzCore/ColorzCore.sln
@@ -1,9 +1,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26430.6
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34714.143
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorzCore", "ColorzCore.csproj", "{B98F7CCF-9CAA-406E-88D7-2040FA99F631}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorzCore", "ColorzCore.csproj", "{B98F7CCF-9CAA-406E-88D7-2040FA99F631}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorzCore.Framework", "ColorzCore.Framework.csproj", "{33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -18,6 +20,12 @@ Global
{B98F7CCF-9CAA-406E-88D7-2040FA99F631}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B98F7CCF-9CAA-406E-88D7-2040FA99F631}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B98F7CCF-9CAA-406E-88D7-2040FA99F631}.Release|Any CPU.Build.0 = Release|Any CPU
+ {33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}.32bit|Any CPU.ActiveCfg = Debug|Any CPU
+ {33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}.32bit|Any CPU.Build.0 = Debug|Any CPU
+ {33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {33DC9CB0-82AB-47EC-9D7F-AD9FD5776BCF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ColorzCore/DataTypes/CaseInsensitiveString.cs b/ColorzCore/DataTypes/CaseInsensitiveString.cs
deleted file mode 100644
index 8fd8ade..0000000
--- a/ColorzCore/DataTypes/CaseInsensitiveString.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ColorzCore.DataTypes
-{
- class CaseInsensitiveString
- {
- private string data;
- public string String {
- get { return data; }
- set
- {
- data = value.ToUpper();
- }
- }
- public CaseInsensitiveString(string input)
- {
- String = input;
- }
- public override int GetHashCode()
- {
- return String.GetHashCode();
- }
- public override bool Equals(object obj)
- {
- return String == obj.ToString();
- }
- public override string ToString()
- {
- return String;
- }
- }
-}
diff --git a/ColorzCore/DataTypes/Either.cs b/ColorzCore/DataTypes/Either.cs
index 5d4896f..77c2476 100644
--- a/ColorzCore/DataTypes/Either.cs
+++ b/ColorzCore/DataTypes/Either.cs
@@ -14,9 +14,9 @@ public interface Either
bool IsRight { get; }
Left GetLeft { get; }
Right GetRight { get; }
- void Case(TAction LAction, TAction RAction);
+ void Case(Action LAction, Action RAction);
}
- public class Left : Either
+ public class Left : Either
{
public Left(L val)
{
@@ -27,7 +27,7 @@ public Left(L val)
public bool IsRight { get { return false; } }
public L GetLeft { get; }
public R GetRight { get { throw new WrongEitherException(); } }
- public void Case(TAction LAction, TAction RAction) { LAction(GetLeft); }
+ public void Case(Action LAction, Action RAction) { LAction(GetLeft); }
}
public class Right : Either
{
@@ -40,7 +40,7 @@ public Right(R val)
public bool IsRight { get { return true; } }
public L GetLeft { get { throw new WrongEitherException(); } }
public R GetRight { get; }
- public void Case(TAction LAction, TAction RAction) { RAction(GetRight); }
+ public void Case(Action LAction, Action RAction) { RAction(GetRight); }
}
class WrongEitherException : Exception { }
}
diff --git a/ColorzCore/DataTypes/Extension.cs b/ColorzCore/DataTypes/Extension.cs
index abb0c0b..41d6848 100644
--- a/ColorzCore/DataTypes/Extension.cs
+++ b/ColorzCore/DataTypes/Extension.cs
@@ -50,6 +50,7 @@ public static int ToInt(this string numString)
}
}
public static Dictionary> AddTo(this Dictionary> self, K key, V val)
+ where K: notnull
{
if (self.ContainsKey(key))
self[key].Add(val);
@@ -58,8 +59,8 @@ public static Dictionary> AddTo(this Dictionary> se
self[key] = new List { val };
}
return self;
- }
-
+ }
+
public static void SetBits(this byte[] array, int bitOffset, int bitSize, int value)
{
array.SetBits(0, bitOffset, bitSize, value);
@@ -67,10 +68,10 @@ public static void SetBits(this byte[] array, int bitOffset, int bitSize, int va
public static void SetBits(this byte[] array, int byteOffset, int bitOffset, int bitSize, int value)
{
- if (bitOffset >= 8)
+ if (bitOffset >= 8)
{
array.SetBits(byteOffset + bitOffset / 8, bitOffset % 8, bitSize, value);
- return;
+ return;
}
long bytes = 0;
@@ -95,23 +96,23 @@ public static void SetBits(this byte[] array, int byteOffset, int bitOffset, int
array[byteOffset + i] = (byte)(bytes >> i * 8);
}
- public static void SetBits(this byte[] array, int bitOffset, int bitSize, byte[] data)
- {
- array.SetBits(0, bitOffset, bitSize, data);
+ public static void SetBits(this byte[] array, int bitOffset, int bitSize, byte[] data)
+ {
+ array.SetBits(0, bitOffset, bitSize, data);
}
- public static void SetBits(this byte[] array, int byteOffset, int bitOffset, int bitSize, byte[] data)
- {
- if (bitOffset >= 8)
+ public static void SetBits(this byte[] array, int byteOffset, int bitOffset, int bitSize, byte[] data)
+ {
+ if (bitOffset >= 8)
{
array.SetBits(byteOffset + bitOffset / 8, bitOffset % 8, bitSize, data);
- return;
+ return;
}
int byteSize = (bitOffset + bitSize + 7) / 8;
- if (bitOffset == 0)
- {
+ if (bitOffset == 0)
+ {
for (int i = 0; i < byteSize - 1; ++i)
array[byteOffset + i] = data[i];
@@ -119,24 +120,24 @@ public static void SetBits(this byte[] array, int byteOffset, int bitOffset, int
uint endMask = (uint)((1 << shift) - 1);
array[byteOffset + byteSize - 1] &= (byte)~endMask;
- array[byteOffset + byteSize - 1] |= (byte)(data[byteSize - 1] & endMask);
+ array[byteOffset + byteSize - 1] |= (byte)(data[byteSize - 1] & endMask);
}
else
- {
- for (int i = 0; i < byteSize; ++i)
- {
- int mask = ((1 << Math.Min(bitSize - i * 8, 8)) - 1) << bitOffset;
-
- byte loMask = (byte)mask;
- byte hiMask = (byte)(mask >> 8);
-
- array[byteOffset + i] &= (byte)~loMask;
- array[byteOffset + i] |= (byte)((data[i] << bitOffset) & loMask);
-
- array[byteOffset + i + 1] &= (byte)~hiMask;
- array[byteOffset + i + 1] |= (byte)((data[i] >> (8 - bitOffset)) & hiMask);
+ {
+ for (int i = 0; i < byteSize; ++i)
+ {
+ int mask = ((1 << Math.Min(bitSize - i * 8, 8)) - 1) << bitOffset;
+
+ byte loMask = (byte)mask;
+ byte hiMask = (byte)(mask >> 8);
+
+ array[byteOffset + i] &= (byte)~loMask;
+ array[byteOffset + i] |= (byte)((data[i] << bitOffset) & loMask);
+
+ array[byteOffset + i + 1] &= (byte)~hiMask;
+ array[byteOffset + i + 1] |= (byte)((data[i] >> (8 - bitOffset)) & hiMask);
}
- }
+ }
}
}
}
diff --git a/ColorzCore/DataTypes/ImmutableStack.cs b/ColorzCore/DataTypes/ImmutableStack.cs
index 51e5aef..ea4a2bb 100644
--- a/ColorzCore/DataTypes/ImmutableStack.cs
+++ b/ColorzCore/DataTypes/ImmutableStack.cs
@@ -9,42 +9,32 @@ namespace ColorzCore.DataTypes
{
public class ImmutableStack : IEnumerable
{
- private Maybe>> member;
- int? count;
+ private readonly Tuple>? member;
+ private int? count;
public ImmutableStack(T elem, ImmutableStack tail)
- { member = new Just>>(new Tuple>(elem, tail)); }
- private ImmutableStack()
{
- member = new Nothing>>();
- count = null;
+ member = new Tuple>(elem, tail);
}
- private static ImmutableStack emptyList = new ImmutableStack();
- public static ImmutableStack Nil { get { return emptyList;} }
-
- public bool IsEmpty { get { return member.IsNothing; } }
- public T Head { get { return member.FromJust.Item1; } }
- public ImmutableStack Tail { get { return member.FromJust.Item2; } }
- public int Count { get
- {
- if (count.HasValue)
- return count.Value;
- else
- return (count = Tail.Count + 1).Value;
- } }
- /*
- public bool Contains(T toLookFor)
+ private ImmutableStack()
{
- bool acc = false;
- for(ImmutableStack temp = this; !acc && !temp.IsEmpty; temp = temp.Tail) acc |= temp.Head.Equals(toLookFor);
- return acc;
+ member = null;
+ count = 0;
}
- */
+
+ public static ImmutableStack Nil { get; } = new ImmutableStack();
+
+ public bool IsEmpty => member == null;
+ public T Head => member!.Item1;
+ public ImmutableStack Tail => member!.Item2;
+ public int Count => count ?? (count = Tail.Count + 1).Value;
+
public IEnumerator GetEnumerator()
{
ImmutableStack temp = this;
- while(!temp.IsEmpty)
+
+ while (!temp.IsEmpty)
{
yield return temp.Head;
temp = temp.Tail;
@@ -54,6 +44,7 @@ public IEnumerator GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
{
ImmutableStack temp = this;
+
while (!temp.IsEmpty)
{
yield return temp.Head;
@@ -63,7 +54,7 @@ IEnumerator IEnumerable.GetEnumerator()
public static ImmutableStack FromEnumerable(IEnumerable content)
{
- return content.Reverse().Aggregate(Nil, (ImmutableStack acc, T elem) => new ImmutableStack(elem, acc));
+ return content.Reverse().Aggregate(Nil, (acc, elem) => new ImmutableStack(elem, acc));
}
}
}
diff --git a/ColorzCore/DataTypes/Location.cs b/ColorzCore/DataTypes/Location.cs
index 7699204..4ea0e52 100644
--- a/ColorzCore/DataTypes/Location.cs
+++ b/ColorzCore/DataTypes/Location.cs
@@ -9,13 +9,20 @@ namespace ColorzCore.DataTypes
public struct Location
{
public string file;
- public int lineNum, colNum;
+ public int line, column;
+ public MacroLocation? macroLocation;
- public Location(string fileName, int lineNum, int colNum) : this()
+ public Location(string fileName, int lineNum, int colNum, MacroLocation? macro = null) : this()
{
file = fileName;
- this.lineNum = lineNum;
- this.colNum = colNum;
+ line = lineNum;
+ column = colNum;
+ macroLocation = macro;
}
+
+ public readonly Location OffsetBy(int columns) => new Location(file, line, column + columns, macroLocation);
+ public readonly Location MacroClone(MacroLocation macro) => new Location(file, line, column, macro);
+
+ public override readonly string ToString() => $"{file}:{line}:{column}";
}
}
diff --git a/ColorzCore/DataTypes/MacroLocation.cs b/ColorzCore/DataTypes/MacroLocation.cs
new file mode 100644
index 0000000..fd50152
--- /dev/null
+++ b/ColorzCore/DataTypes/MacroLocation.cs
@@ -0,0 +1,14 @@
+namespace ColorzCore.DataTypes
+{
+ public class MacroLocation
+ {
+ public string MacroName { get; }
+ public Location Location { get; }
+
+ public MacroLocation(string macroName, Location location)
+ {
+ MacroName = macroName;
+ Location = location;
+ }
+ }
+}
diff --git a/ColorzCore/DataTypes/Maybe.cs b/ColorzCore/DataTypes/Maybe.cs
deleted file mode 100644
index 130583c..0000000
--- a/ColorzCore/DataTypes/Maybe.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace ColorzCore.DataTypes
-{
- public delegate R UnaryFunction(T val);
- public delegate R RConst();
- public delegate void TAction(T val);
- public delegate void NullaryAction();
- public delegate Maybe MaybeAction(T val);
-
-#pragma warning disable IDE1006 // Naming Styles
- public interface Maybe
-#pragma warning restore IDE1006 // Naming Styles
- {
- bool IsNothing { get; }
- T FromJust { get; }
- Maybe Fmap(UnaryFunction f);
- Maybe Bind(MaybeAction f);
- R IfJust(UnaryFunction just, RConst nothing);
- void IfJust(TAction just, NullaryAction nothing = null);
- }
- public class Just : Maybe
- {
- public bool IsNothing { get { return false; } }
- public T FromJust { get; }
-
- public Just(T val)
- {
- FromJust = val;
- }
-
- public Maybe Fmap(UnaryFunction f)
- {
- return new Just(f(FromJust));
- }
- public Maybe Bind(MaybeAction f)
- {
- return f(FromJust);
- }
- public R IfJust(UnaryFunction just, RConst nothing)
- {
- return just(FromJust);
- }
- public void IfJust(TAction just, NullaryAction nothing)
- {
- just(FromJust);
- }
- }
- public class Nothing : Maybe
- {
- public bool IsNothing { get { return true; } }
- public T FromJust { get { throw new MaybeException(); } }
-
- public Nothing() { }
-
- public Maybe Fmap(UnaryFunction f)
- {
- return new Nothing();
- }
- public Maybe Bind(MaybeAction f)
- {
- return new Nothing();
- }
- public R IfJust(UnaryFunction just, RConst nothing)
- {
- return nothing();
- }
- public void IfJust(TAction just, NullaryAction nothing)
- {
- nothing?.Invoke();
- }
- }
-
- public class MaybeException : Exception { }
-}
diff --git a/ColorzCore/DataTypes/MergeableGenerator.cs b/ColorzCore/DataTypes/MergeableGenerator.cs
index a483226..ae7fe97 100644
--- a/ColorzCore/DataTypes/MergeableGenerator.cs
+++ b/ColorzCore/DataTypes/MergeableGenerator.cs
@@ -7,22 +7,24 @@
namespace ColorzCore.DataTypes
{
- public class MergeableGenerator: IEnumerable
+ public class MergeableGenerator : IEnumerable
{
- private Stack> myEnums;
+ private readonly Stack> myEnums;
public bool EOS { get; private set; }
+
public MergeableGenerator(IEnumerable baseEnum)
{
myEnums = new Stack>();
myEnums.Push(baseEnum.GetEnumerator());
}
- public T Current { get { return myEnums.Peek().Current; } }
+ public T Current => myEnums.Peek().Current;
+
public bool MoveNext()
{
- if(!myEnums.Peek().MoveNext())
+ if (!myEnums.Peek().MoveNext())
{
- if(myEnums.Count > 1)
+ if (myEnums.Count > 1)
{
myEnums.Pop();
return true;
@@ -38,43 +40,55 @@ public bool MoveNext()
return true;
}
}
+
public void PrependEnumerator(IEnumerator nextEnum)
{
if (EOS)
+ {
myEnums.Pop();
+ }
+
myEnums.Push(nextEnum);
Prime();
}
+
public void PutBack(T elem)
{
- this.PrependEnumerator(new List { elem }.GetEnumerator());
+ PrependEnumerator(new List { elem }.GetEnumerator());
}
+
private bool Prime()
{
if (!myEnums.Peek().MoveNext())
{
if (myEnums.Count == 1)
EOS = true;
- else
+ else
myEnums.Pop();
}
else
+ {
EOS = false;
+ }
+
return !EOS;
}
+
IEnumerator IEnumerable.GetEnumerator()
{
- while(!EOS) {
- yield return this.Current;
- this.MoveNext();
+ while (!EOS)
+ {
+ yield return Current;
+ MoveNext();
}
}
+
public IEnumerator GetEnumerator()
{
while (!EOS)
{
- yield return this.Current;
- this.MoveNext();
+ yield return Current;
+ MoveNext();
}
}
}
diff --git a/ColorzCore/DataTypes/NullableExtensions.cs b/ColorzCore/DataTypes/NullableExtensions.cs
new file mode 100644
index 0000000..1a0fef4
--- /dev/null
+++ b/ColorzCore/DataTypes/NullableExtensions.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ColorzCore.DataTypes
+{
+ public static class NullableExtensions
+ {
+ public static R? Fmap(this T? self, Func f)
+ where T : class
+ {
+ return self != null ? f(self) : default;
+ }
+
+ public static R IfJust(this T? self, Func just, Func nothing)
+ where T : class
+ {
+ if (self != null)
+ {
+ return just(self);
+ }
+ else
+ {
+ return nothing();
+ }
+ }
+
+ public static R IfJust(this T? self, Func just, Func nothing)
+ where T : struct
+ {
+ if (self.HasValue)
+ {
+ return just(self.Value);
+ }
+ else
+ {
+ return nothing();
+ }
+ }
+
+ public static void IfJust(this T? self, Action just, Action? nothing = null)
+ where T : class
+ {
+ if (self != null)
+ {
+ just(self);
+ }
+ else
+ {
+ nothing?.Invoke();
+ }
+ }
+
+ public static void IfJust(this T? self, Action just, Action? nothing = null)
+ where T : struct
+ {
+ if (self.HasValue)
+ {
+ just(self.Value);
+ }
+ else
+ {
+ nothing?.Invoke();
+ }
+ }
+ }
+}
diff --git a/ColorzCore/EA Standard library/Convo Helpers.txt b/ColorzCore/EA Standard library/Convo Helpers.txt
deleted file mode 100644
index fa836a1..0000000
--- a/ColorzCore/EA Standard library/Convo Helpers.txt
+++ /dev/null
@@ -1,36 +0,0 @@
-
-//Show text with background and return to map
-#ifdef _FE6_
-#define Text(text) "TEX1 text; REMA"
-#define Text(background,text) "FADI 0x10; HIDEMAP; BACG background; FADU 0x10; SHOWMAP; TEX1 text; REMA"
-#endif
-
-#ifdef _FE7_
-#define Text(text) "TEX1 text; REMA"
-#define Text(background,text) "FADI 0x10; HIDEMAP; BACG background; FADU 0x10; SHOWMAP; TEX1 text; REMA"
-#define ClearBrownBox "_ASM0x42 0x83181"
-#endif
-
-#ifdef _FE8_
-#define Text(text) "TEXTSTART; TEXTSHOW text; TEXTEND; REMA"
-#define Text(background,text) "_SETVAL 2 background; _SETVAL 3 text; CALL $9EE310"
-#define SetBackground(background) "SVAL 2 background; CALL $9EE2E8" //EVBIT 0x8 = fade in?
-#define ClearBackground "CALL 0x9EE2C4"
-#define ClearBackgroundSmooth "FADI 0x10; ClearBackground"
-//#define SetBackground(background) "SETVAL 0x2 background; EVBIT_CHECK 0x8; REMOVEPORTRAITS; BACG 0xFFFF 0x0 0x0"
-#define CenterTutorialTextBox "_SETVAL 0xB 0xFFFFFFFF"
-#define FlashWhite "FAWI 0x20; STAL 0x10; FAWU 0x20"
-#define FlashBlack "FADI 0x20; STAL 0x10; FADU 0x20"
-#define ShowCG(BGIndex) "SETVAL 0x2 BGIndex; REMOVEPORTRAITS; BACG 0xFFFF;"
-#endif
-
-
-//Smooth changing to CG
-#ifdef _FE7_
-#define ChangeToCutScene(cutscene) "FADICG 0x10; HIDEMAP; SHCG cutscene; FADUCG 0x10; SHOWMAP"
-#endif
-
-//Shows text on a scroll
-#ifdef _FE7_
-#define ScrollText(textID) "TEX6 7 [0,0] textID; _ASM0x42 $83181; _0x89"
-#endif
diff --git a/ColorzCore/EA Standard library/FE8 Definitions.txt b/ColorzCore/EA Standard library/FE8 Definitions.txt
deleted file mode 100644
index 01faeac..0000000
--- a/ColorzCore/EA Standard library/FE8 Definitions.txt
+++ /dev/null
@@ -1,476 +0,0 @@
-//Characters
-#define Eirika 0x01
-#define Seth 0x02
-#define Gilliam 0x03
-#define Franz 0x04
-#define Moulder 0x05
-#define Vanessa 0x06
-#define Ross 0x07
-#define Neimi 0x08
-#define Colm 0x09
-#define Garcia 0x0A
-#define Innes 0x0B
-#define Lute 0x0C
-#define Natasha 0x0D
-#define Cormag 0x0E
-#define Ephraim 0x0F
-#define Forde 0x10
-#define Kyle 0x11
-#define Amelia 0x12
-#define Artur 0x13
-#define Gerik 0x14
-#define Tethys 0x15
-#define Marisa 0x16
-#define Saleh 0x17
-#define Ewan 0x18
-#define LArachel 0x19
-#define Dozla 0x1A
-#define Rennac 0x1C
-#define Duessel 0x1D
-#define Myrrh 0x1E
-#define Knoll 0x1F
-#define Joshua 0x20
-#define Syrene 0x21
-#define Tana 0x22
-#define LyonCC 0x23
-#define OrsonCC 0x24
-#define GlenCC 0x25
-#define SelenaCC 0x26
-#define ValterCC 0x27
-#define RievCC 0x28
-#define CaellachCC 0x29
-#define FadoCC 0x2A
-#define IsmaireCC 0x2B
-#define HaydenCC 0x2C
-#define LyonSummon 0x3B
-#define KnollSummon 0x3E
-#define EvanSummon 0x3F
-#define Lyon_Ch17 0x40
-#define Morva_Ch20 0x41
-#define Orson_Ch5x 0x42
-#define Valter_Ch15_ 0x43
-#define Valter_Ch15 0x43
-#define Selena_Ch10B_and_13B 0x44
-#define Valter_Prologue 0x45
-#define Breguet 0x46
-#define Bone 0x47
-#define Bazba 0x48
-#define Entombed_boss 0x49
-#define Saar 0x4A
-#define Novala 0x4B
-#define Murray 0x4C
-#define Tirado 0x4D
-#define Binks 0x4E
-#define Pablo 0x4F
-#define Macdaire 0x50
-#define Maelduin_boss 0x50
-#define Aias 0x51
-#define Carlyle 0x52
-#define Caellach 0x53
-#define Pablo_2 0x54
-#define Gorgon_boss 0x56
-#define Riev 0x57
-#define Gheb 0x5A
-#define Beran 0x5B
-#define Cyclops_boss 0x5C
-#define Wight_boss 0x5D
-#define Deathgoyle_boss 0x5E
-#define Bandit 0x66
-#define ONeill 0x68
-#define Glen 0x69
-#define Zonta 0x6A
-#define Vigarde 0x6B
-#define Lyon_Final 0x6C
-#define Orson_Boss 0x6D
-#define Fomortiis 0xBE
-#define Fado 0xC5
-#define Hayden 0xC7
-#define Mansel 0xC8
-#define Klimt 0xC9
-#define Dara 0xCA
-#define Ismaire 0xCB
-#define PegasusMessenger 0xCC
-#define RiverFolkChild_F 0xF4
-#define RiverFolk_F 0xF5
-#define RiverFolk 0xF6
-#define RiverFolk_F_2 0xF7
-#define RiverFolkChild 0xF8
-#define RenaisCivilianChild_F 0xF9
-#define RenaisCivilian 0xFA
-#define RenaisCivilian_F 0xFB
-#define OldCivilian 0xFC
-#define Wall 0xFE
-#define Snag 0xFF
-
-//Classes
-#define EphraimLord 0x01
-#define EirikaLord 0x02
-#define EphraimMasterLord 0x03
-#define EirikaMasterLord 0x04
-#define Cavalier 0x05
-#define Cavalier_F 0x06
-#define Paladin 0x07
-#define Paladin_F 0x08
-#define Knight 0x09
-#define Knight_F 0x0A
-#define General 0x0B
-#define General_F 0x0C
-#define Thief 0x0D
-#define Manakete 0x0E
-#define Mercenary 0x0F
-#define Mercenary_F 0x10
-#define Hero 0x11
-#define Hero_F 0x12
-#define Myrmidon 0x13
-#define Myrmidon_F 0x14
-#define Swordmaster 0x15
-#define Swordmaster_F 0x16
-#define Assassin 0x17
-#define Assassin_F 0x18
-#define Archer 0x19
-#define Archer_F 0x1A
-#define Sniper 0x1B
-#define Sniper_F 0x1C
-#define Ranger 0x1D
-#define Ranger_F 0x1E
-#define WyvernRider 0x1F
-#define WyvernRider_F 0x20
-#define WyvernLord 0x21
-#define WyvernLord_F 0x22
-#define WyvernKnight 0x23
-#define WyvernKnight_F 0x24
-#define Mage 0x25
-#define Mage_F 0x26
-#define Sage 0x27
-#define Sage_F 0x28
-#define MageKnight 0x29
-#define MageKnight_F 0x2A
-#define Bishop 0x2B
-#define Bishop_F 0x2C
-#define Shaman 0x2D
-#define Shaman_F 0x2E
-#define Druid 0x2F
-#define Druid_F 0x30
-#define Summoner 0x31
-#define Summoner_F 0x32
-#define Rogue 0x33
-#define GorgonEgg 0x34
-#define GreatKnight 0x35
-#define GreatKnight_F 0x36
-#define Recruit_2 0x37
-#define Journeyman_3 0x38
-#define Pupil_3 0x39
-#define Recruit_3 0x3A
-#define Manakete_2 0x3B
-#define Manakete_2_F 0x3C
-#define Journeyman_1 0x3D
-#define Pupil_1 0x3E
-#define Fighter 0x3F
-#define Warrior 0x40
-#define Brigand 0x41
-#define Pirate 0x42
-#define Berserker 0x43
-#define Monk 0x44
-#define Priest 0x45
-#define Bard 0x46
-#define Recruit_1 0x47
-#define PegasusKnight 0x48
-#define FalcoKnight 0x49
-#define Cleric 0x4A
-#define Troubadour 0x4B
-#define Valkyrie 0x4C
-#define Dancer 0x4D
-#define Soldier 0x4E
-#define Necromancer 0x4F
-#define Fleet 0x50
-#define GhostFighter 0x51
-#define Revenant 0x52
-#define Entombed 0x53
-#define Bonewalker 0x54
-#define Bonewalker_Bow 0x55
-#define Wight 0x56
-#define Wight_Bow 0x57
-#define Bael 0x58
-#define ElderBael 0x59
-#define Cyclops 0x5A
-#define Mauthedoog 0x5B
-#define Gwyllgi 0x5C
-#define Tarvos 0x5D
-#define Maelduin 0x5E
-#define Mogall 0x5F
-#define ArchMogall 0x60
-#define Gorgon 0x61
-#define GorgonEgg_2 0x62
-#define Gargoyle 0x63
-#define Deathgoyle 0x64
-#define DracoZombie 0x65
-#define DemonKing 0x66
-#define ArcheronBallista 0x67
-#define ArcheronIronBallista 0x68
-#define ArcheronKillerBallista 0x69
-#define EmptyBallista 0x6A
-#define EmptyIronBallista 0x6B
-#define EmptyKillerBallista 0x6C
-#define Civilian 0x6D
-#define Civilian_F 0x6E
-#define Civilian_2 0x6F
-#define Civilian_F_2 0x70
-#define Civilian_3 0x71
-#define Civilian_F_3 0x72
-#define Peer 0x73
-#define Queen 0x74
-#define Prince 0x75
-#define Queen_2 0x76
-#define FallenPrince 0x78
-#define Tent 0x79
-#define Pontifex 0x7A
-#define FallenPeer 0x7B
-#define Cyclops_2 0x7C
-#define ElderBael_2 0x7D
-#define Journeyman_2 0x7E
-#define Pupil_2 0x7F
-
-//Items
-#define IronSword 0x01
-#define SlimSword 0x02
-#define SteelSword 0x03
-#define SilverSword 0x04
-#define IronBlade 0x05
-#define SteelBlade 0x06
-#define SilverBlade 0x07
-#define PoisonSword 0x08
-#define Rapier 0x09
-#define ManiKatti 0x0A
-#define BraveSword 0x0B
-#define Shamshir 0x0C
-#define KillingEdge 0x0D
-#define Armourslayer 0x0E
-#define Armorslayer 0x0E
-#define Wyrmslayer 0x0F
-#define LightBrand 0x10
-#define Lightbrand 0x10
-#define Runesword 0x11
-#define Lancereaver 0x12
-#define Longsword 0x13
-#define Zanbato 0x13
-#define Zanbatou 0x13
-#define IronLance 0x14
-#define SlimLance 0x15
-#define SteelLance 0x16
-#define SilverLance 0x17
-#define PoisonLance 0x18
-#define ToxicLance 0x18
-#define ToxinLance 0x18
-#define BraveLance 0x19
-#define KillerLance 0x1A
-#define Horseslayer 0x1B
-#define Ridersbane 0x1B
-#define Horsekiller 0x1B
-#define Javelin 0x1C
-#define Spear 0x1D
-#define Axereaver 0x1E
-#define IronAxe 0x1F
-#define SteelAxe 0x20
-#define SilverAxe 0x21
-#define PoisonAxe 0x22
-#define BraveAxe 0x23
-#define KillerAxe 0x24
-#define Halberd 0x25
-#define Hammer 0x26
-#define DevilAxe 0x27
-#define HandAxe 0x28
-#define Tomahawk 0x29
-#define Swordreaver 0x2A
-#define Swordslayer 0x2B
-#define Hatchet 0x2C
-#define IronBow 0x2D
-#define SteelBow 0x2E
-#define SilverBow 0x2F
-#define PoisonBow 0x30
-#define KillerBow 0x31
-#define BraveBow 0x32
-#define ShortBow 0x33
-#define Longbow 0x34
-#define Ballista 0x35
-#define IronBallista 0x36
-#define KillerBallista 0x37
-#define Fire 0x38
-#define Thunder 0x39
-#define Elfire 0x3A
-#define Bolting 0x3B
-#define Fimbulvetr 0x3C
-#define Forblaze 0x3D
-#define Excalibur 0x3E
-#define Lightning 0x3F
-#define Shine 0x40
-#define Divine 0x41
-#define Purge 0x42
-#define Aura 0x43
-#define Luce 0x44
-#define Flux 0x45
-#define Luna 0x46
-#define Nosferatu 0x47
-#define Eclipse 0x48
-#define Fenrir 0x49
-#define Gleipnir 0x4A
-#define Heal 0x4B
-#define Mend 0x4C
-#define Recover 0x4D
-#define Physic 0x4E
-#define Fortify 0x4F
-#define Restore 0x50
-#define Silence 0x51
-#define Sleep 0x52
-#define Berserk 0x53
-#define Warp 0x54
-#define Rescue 0x55
-#define TorchStaff 0x56
-#define Hammerne 0x57
-#define Unlock 0x58
-#define Barrier 0x59
-#define DragonAxe 0x5A
-#define AngelicRobe 0x5B
-#define EnergyRing 0x5C
-#define SecretBook 0x5D
-#define Speedwings 0x5E
-#define GoddessIcon 0x5F
-#define Dragonshield 0x60
-#define Dracoshield 0x60
-#define Talisman 0x61
-#define Boots 0x62
-#define BodyRing 0x63
-#define HeroCrest 0x64
-#define HerosCrest 0x64
-#define KnightCrest 0x65
-#define KnightsCrest 0x65
-#define OrionBolt 0x66
-#define OrionsBolt 0x66
-#define ElysianWhip 0x67
-#define GuidingRing 0x68
-#define ChestKey 0x69
-#define DoorKey 0x6A
-#define Lockpick 0x6B
-#define Vulnerary 0x6C
-#define Elixir 0x6D
-#define PureWater 0x6E
-#define Antidote 0x6F
-#define Antitoxin 0x6F
-#define Torch 0x70
-#define TorchItem 0x70
-#define DelphiShield 0x71
-#define MemberCard 0x72
-#define SilverCard 0x73
-#define WhiteGem 0x74
-#define BlueGem 0x75
-#define RedGem 0x76
-#define Gold 0x77
-#define Reginleif 0x78
-#define ChestKey_5 0x79
-#define Mine 0x7A
-#define LightRune 0x7B
-#define HoplonShield 0x7C
-#define FillasMight 0x7D
-#define NinissGrace 0x7E
-#define ThorsIre 0x7F
-#define SetsLitany 0x80
-#define ShadowKiller 0x81
-#define Shadowkiller 0x81
-#define BrightLance 0x82
-#define FiendCleaver 0x83
-#define Fiendcleaver 0x83
-#define BeaconBow 0x84
-#define Sieglind 0x85
-#define Sieglinde 0x85
-#define BattleAxe 0x86
-#define Ivaldi 0x87
-#define MasterProof 0x88
-#define MasterSeal 0x88
-#define MetissTome 0x89
-#define HeavenSeal 0x8A
-#define SharpClaw 0x8B
-#define Latona 0x8C
-#define DragonSpear 0x8D
-#define Vidofnir 0x8E
-#define Naglfar 0x8F
-#define WretchedAir 0x90
-#define Audomra 0x91
-#define Audhulma 0x91
-#define Siegmund 0x92
-#define Garm 0x93
-#define Nidhogg 0x94
-#define HeavySpear 0x95
-#define ShortSpear 0x96
-#define ConquerorsProof 0x97
-#define OceanSeal 0x97
-#define MoonBracelet 0x98
-#define LunarBracelet 0x98
-#define SunBracelet 0x99
-#define SolarBracelet 0x99
-#define _1G 0x9A
-#define _5G 0x9B
-#define _10G 0x9C
-#define _50G 0x9D
-#define _100G 0x9E
-#define _3000G 0x9F
-#define _5000G 0xA0
-#define WindSword 0xA1
-#define Vulnerary_2 0xA2
-#define Greennote 0xA3
-#define Rednote 0xA4
-#define Dance 0xA5
-#define Nightmare 0xA6
-#define NightmareStaff 0xA6
-#define DemonShard 0xA7
-#define DemonStone 0xA7
-#define DemonLight 0xA8
-#define Ravager 0xA9
-#define HolyDragonStone 0xAA
-#define DivineDragonStone 0xAA
-#define DragonStone 0xAA
-#define Dragonstone 0xAA
-#define DemonSurge 0xAB
-#define Shadowshot 0xAC
-#define RottenClaw 0xAD
-#define FetidClaw 0xAE
-#define PoisonClaw 0xAF
-#define LongPoisonClaw 0xB0
-#define LethalTalon 0xB0
-#define FireFang 0xB1
-#define HellFang 0xB2
-#define EvilEye 0xB3
-#define BloodyEye 0xB4
-#define CrimsonEye 0xB4
-#define Stone 0xB5
-#define Aircalibur 0xB6
-#define JunaFruit 0xB7
-#define _150G 0xB8
-#define _200G 0xB9
-#define BlackGem 0xBA
-#define GoldGem 0xBB
-
-#define r0 0x0
-#define r1 0x1
-#define r2 0x2
-#define r3 0x3
-#define r4 0x4
-#define r5 0x5
-#define r6 0x6
-#define r7 0x7
-#define r8 0x8
-#define r9 0x9
-#define rA 0xA
-#define r10 0xA
-#define rB 0xB
-#define r11 0xB
-#define rC 0xC
-#define r12 0xC
-#define rD 0xD
-#define r13 0xD
-#define QP 0xD //QueuePointer
-
-#define ActiveUnit (-1) // 0xFFFF
-#define UnitAtCoordsSlotB (-2) // 0xFFFE
-#define UnitInSlot2 (-3) // 0xFFFD
-
-#define CurrentUnit ActiveUnit
-#define UnitAtCoords UnitAtCoordsSlotB
diff --git a/ColorzCore/EADriver.cs b/ColorzCore/EADriver.cs
new file mode 100644
index 0000000..b6a7d86
--- /dev/null
+++ b/ColorzCore/EADriver.cs
@@ -0,0 +1,318 @@
+using ColorzCore.DataTypes;
+using ColorzCore.Interpreter;
+using ColorzCore.Interpreter.Diagnostics;
+using ColorzCore.IO;
+using ColorzCore.Lexer;
+using ColorzCore.Parser;
+using ColorzCore.Parser.AST;
+using ColorzCore.Preprocessor;
+using ColorzCore.Preprocessor.Directives;
+using ColorzCore.Preprocessor.Macros;
+using ColorzCore.Raws;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace ColorzCore
+{
+ // Class to excapsulate all steps in EA script interpretation (lexing -> parsing -> interpretation -> commit).
+ class EADriver
+ {
+ private Dictionary> allRaws;
+ private EAParser myParser;
+ private EAInterpreter myInterpreter;
+ private string iFile;
+ private Stream sin;
+ private Logger Logger { get; }
+ private IOutput output;
+
+ public EADriver(IOutput output, string? game, string? rawsFolder, string rawsExtension, Stream sin, string inFileName, Logger logger)
+ {
+ this.output = output;
+
+ try
+ {
+ allRaws = SelectRaws(game, ListAllRaws(rawsFolder, rawsExtension));
+ }
+ catch (RawReader.RawParseException e)
+ {
+ Location loc = new Location(e.FileName, e.LineNumber, 1);
+
+ logger.Message(Logger.MessageKind.ERROR, loc, "An error occured while parsing raws");
+ logger.Message(Logger.MessageKind.ERROR, loc, e.Message);
+
+ Environment.Exit(-1); // ew?
+ }
+
+ this.sin = sin;
+ Logger = logger;
+ iFile = inFileName;
+
+ myInterpreter = new EAInterpreter(logger);
+
+ ParseConsumerChain parseConsumers = new ParseConsumerChain();
+
+ if (EAOptions.IsWarningEnabled(EAOptions.Warnings.SetSymbolMacros))
+ {
+ parseConsumers.Add(new SetSymbolMacroDetector(logger));
+ }
+
+ // add the interpreter last
+ parseConsumers.Add(myInterpreter);
+
+ StringProcessor stringProcessor = new StringProcessor(logger);
+
+ myParser = new EAParser(logger, allRaws, parseConsumers, myInterpreter.BindIdentifier, stringProcessor);
+
+ myParser.Definitions["__COLORZ_CORE__"] = new Definition();
+
+ myParser.Definitions["__COLORZ_CORE_VERSION__"] = new Definition(
+ new Token(TokenType.NUMBER, new Location("builtin", 0, 0), "20240504"));
+
+ if (game != null)
+ {
+ myParser.Definitions[$"_{game}_"] = new Definition();
+ }
+
+ IncludeFileSearcher includeSearcher = new IncludeFileSearcher();
+ includeSearcher.IncludeDirectories.Add(AppDomain.CurrentDomain.BaseDirectory);
+
+ foreach (string path in EAOptions.IncludePaths)
+ {
+ includeSearcher.IncludeDirectories.Add(path);
+ }
+
+ myParser.DirectiveHandler.Directives["include"] = new IncludeDirective() { FileSearcher = includeSearcher };
+ myParser.DirectiveHandler.Directives["incbin"] = new IncludeBinaryDirective() { FileSearcher = includeSearcher };
+
+ myParser.DirectiveHandler.Directives["inctbl"] = new IncludeEncodingTableDirective(stringProcessor)
+ {
+ FileSearcher = includeSearcher
+ };
+
+ if (EAOptions.IsExtensionEnabled(EAOptions.Extensions.ReadDataMacros) && output is ROM rom)
+ {
+ myParser.Definitions["__has_read_data_macros"] = new Definition();
+
+ myParser.Macros.BuiltInMacros.Add("ReadByteAt", new ReadDataAt(myParser, rom, 1));
+ myParser.Macros.BuiltInMacros.Add("ReadShortAt", new ReadDataAt(myParser, rom, 2));
+ myParser.Macros.BuiltInMacros.Add("ReadWordAt", new ReadDataAt(myParser, rom, 4));
+ }
+ else
+ {
+ BuiltInMacro unsupportedMacro = new ErrorMacro(myParser, "Macro unsupported in this configuration.", i => i == 1);
+
+ myParser.Macros.BuiltInMacros.Add("ReadByteAt", unsupportedMacro);
+ myParser.Macros.BuiltInMacros.Add("ReadShortAt", unsupportedMacro);
+ myParser.Macros.BuiltInMacros.Add("ReadWordAt", unsupportedMacro);
+ }
+
+ if (EAOptions.IsExtensionEnabled(EAOptions.Extensions.IncludeTools))
+ {
+ myParser.Definitions["__has_incext"] = new Definition();
+
+ IncludeFileSearcher toolSearcher = new IncludeFileSearcher { AllowRelativeInclude = false };
+ toolSearcher.IncludeDirectories.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools"));
+
+ foreach (string path in EAOptions.ToolsPaths)
+ {
+ includeSearcher.IncludeDirectories.Add(path);
+ }
+
+ IDirective incextDirective = new IncludeExternalDirective { FileSearcher = toolSearcher };
+ IDirective inctextDirective = new IncludeToolEventDirective { FileSearcher = toolSearcher };
+
+ myParser.DirectiveHandler.Directives["incext"] = incextDirective;
+ myParser.DirectiveHandler.Directives["inctext"] = inctextDirective;
+ myParser.DirectiveHandler.Directives["inctevent"] = inctextDirective;
+ }
+
+ if (EAOptions.IsExtensionEnabled(EAOptions.Extensions.AddToPool))
+ {
+ myParser.Definitions["__has_pool"] = new Definition();
+
+ Pool pool = new Pool();
+
+ myParser.Macros.BuiltInMacros.Add("AddToPool", new AddToPool(pool));
+ myParser.DirectiveHandler.Directives.Add("pool", new PoolDirective(pool));
+ }
+ }
+
+ public bool Interpret()
+ {
+ Tokenizer tokenizer = new Tokenizer();
+
+ ExecTimer.Timer.AddTimingPoint(ExecTimer.KEY_GENERIC);
+
+ foreach ((string name, string body) in EAOptions.PreDefintions)
+ {
+ Location location = new Location("CMD", 0, 1);
+ myParser.ParseAll(tokenizer.TokenizePhrase($"#define {name} \"{body}\"", location));
+ }
+
+ myParser.ParseAll(tokenizer.Tokenize(sin, iFile));
+
+ IList lines = myInterpreter.HandleEndOfInput();
+
+ /* First pass on AST: Identifier resolution.
+ *
+ * Suppose we had the code
+ *
+ * POIN myLabel
+ * myLabel:
+ *
+ * At parse time, myLabel did not exist for the POIN.
+ * It is at this point we want to make sure all references to identifiers are valid, before assembling.
+ */
+ List<(Location, Exception)> evaluationErrors = new List<(Location, Exception)>();
+ foreach (ILineNode line in lines)
+ {
+ try
+ {
+ line.EvaluateExpressions(evaluationErrors, EvaluationPhase.Final);
+ }
+ catch (MacroInvocationNode.MacroException e)
+ {
+ Logger.Error(e.CausedError.MyLocation, "Unexpanded macro.");
+ }
+ }
+
+ foreach ((Location location, Exception e) in evaluationErrors)
+ {
+ if (e is IdentifierNode.UndefinedIdentifierException uie
+ && uie.CausedError.Content.StartsWith(Pool.pooledLabelPrefix, StringComparison.Ordinal))
+ {
+ Logger.Error(location, "Unpooled data (forgot #pool?)");
+ }
+ else
+ {
+ Logger.Error(location, e.Message);
+ }
+ }
+
+ /* Last step: assembly */
+
+ ExecTimer.Timer.AddTimingPoint(ExecTimer.KEY_DATAWRITE);
+
+ if (!Logger.HasErrored)
+ {
+ foreach (ILineNode line in lines)
+ {
+ if (Program.Debug)
+ {
+ Logger.Message(Logger.MessageKind.DEBUG, line.PrettyPrint(0));
+ }
+
+ line.WriteData(output);
+ }
+
+ output.Commit();
+
+ Logger.Output.WriteLine("No errors. Please continue being awesome.");
+ return true;
+ }
+ else
+ {
+ Logger.Output.WriteLine("Errors occurred; no changes written.");
+ return false;
+ }
+ }
+
+ public bool WriteNocashSymbols(TextWriter output)
+ {
+ for (int i = 0; i < myInterpreter.AllScopes.Count; i++)
+ {
+ string manglePrefix = string.Empty;
+
+ switch (i)
+ {
+ case 0:
+ output.WriteLine("; global scope");
+ break;
+ default:
+ output.WriteLine($"; local scope {i}");
+ manglePrefix = $"${i}$";
+ break;
+ }
+
+ Closure scope = myInterpreter.AllScopes[i];
+
+ foreach (KeyValuePair pair in scope.LocalSymbols())
+ {
+ string name = pair.Key;
+ int address = EAInterpreter.ConvertToAddress(pair.Value);
+
+ output.WriteLine($"{address:X8} {manglePrefix}{name}");
+ }
+ }
+
+ return true;
+ }
+
+ private static IEnumerable LoadAllRaws(string rawsFolder, string rawsExtension)
+ {
+ var directoryInfo = new DirectoryInfo(rawsFolder);
+ var files = directoryInfo.GetFiles("*" + rawsExtension, SearchOption.AllDirectories);
+
+ foreach (FileInfo fileInfo in files)
+ {
+ using var fs = new FileStream(fileInfo.FullName, FileMode.Open);
+
+ foreach (var raw in RawReader.ParseAllRaws(fs))
+ yield return raw;
+ }
+ }
+
+ private static IList ListAllRaws(string? rawsFolder, string rawsExtension)
+ {
+ if (rawsFolder != null)
+ {
+ return new List(LoadAllRaws(rawsFolder, rawsExtension));
+ }
+ else
+ {
+ return GetFallbackRaws();
+ }
+ }
+
+ private static IList GetFallbackRaws()
+ {
+ static List CreateParams(int bitSize, bool isPointer)
+ {
+ return new List() { new AtomicParam("Data", 0, bitSize, isPointer) };
+ }
+
+ static Raw CreateRaw(string name, int byteSize, int alignment, bool isPointer)
+ {
+ return new Raw(name, byteSize * 8, 0, alignment, CreateParams(byteSize * 8, isPointer), true);
+ }
+
+ return new List()
+ {
+ CreateRaw("BYTE", 1, 1, false),
+ CreateRaw("SHORT", 2, 2, false),
+ CreateRaw("WORD", 4, 4, false),
+ CreateRaw("POIN", 4, 4, true),
+ CreateRaw("SHORT2", 2, 1, false),
+ CreateRaw("WORD2", 4, 1, false),
+ CreateRaw("POIN2", 4, 1, true),
+ };
+ }
+
+ private static Dictionary> SelectRaws(string? game, IList allRaws)
+ {
+ Dictionary> result = new Dictionary>();
+
+ foreach (Raw raw in allRaws)
+ {
+ if (raw.Game.Count == 0 || (game != null && raw.Game.Contains(game)))
+ {
+ result.AddTo(raw.Name, raw);
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/ColorzCore/EAInterpreter.cs b/ColorzCore/EAInterpreter.cs
deleted file mode 100644
index c01a7d8..0000000
--- a/ColorzCore/EAInterpreter.cs
+++ /dev/null
@@ -1,187 +0,0 @@
-using ColorzCore.DataTypes;
-using ColorzCore.IO;
-using ColorzCore.Lexer;
-using ColorzCore.Parser;
-using ColorzCore.Parser.AST;
-using ColorzCore.Raws;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace ColorzCore
-{
- //Class to excapsulate all steps in EA script interpretation.
- class EAInterpreter
- {
- private Dictionary> allRaws;
- private EAParser myParser;
- private string game, iFile;
- private Stream sin;
- private Log log;
- private IOutput output;
-
- public EAInterpreter(IOutput output, string game, string rawsFolder, string rawsExtension, Stream sin, string inFileName, Log log)
- {
- this.game = game;
- this.output = output;
-
- try
- {
- allRaws = ProcessRaws(game, ListAllRaws(rawsFolder, rawsExtension));
- }
- catch (RawReader.RawParseException e)
- {
- Location loc = new Location
- {
- file = e.FileName,
- lineNum = e.LineNumber,
- colNum = 1
- };
-
- log.Message(Log.MsgKind.ERROR, loc, "An error occured while parsing raws");
- log.Message(Log.MsgKind.ERROR, loc, e.Message);
-
- Environment.Exit(-1); // ew?
- }
-
- this.sin = sin;
- this.log = log;
- iFile = inFileName;
-
- IncludeFileSearcher includeSearcher = new IncludeFileSearcher();
- includeSearcher.IncludeDirectories.Add(AppDomain.CurrentDomain.BaseDirectory);
-
- foreach (string path in EAOptions.Instance.includePaths)
- includeSearcher.IncludeDirectories.Add(path);
-
- IncludeFileSearcher toolSearcher = new IncludeFileSearcher { AllowRelativeInclude = false };
- toolSearcher.IncludeDirectories.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Tools"));
-
- foreach (string path in EAOptions.Instance.toolsPaths)
- includeSearcher.IncludeDirectories.Add(path);
-
- myParser = new EAParser(allRaws, log, new Preprocessor.DirectiveHandler(includeSearcher, toolSearcher));
-
- myParser.Definitions['_' + game + '_'] = new Definition();
- myParser.Definitions["__COLORZ_CORE__"] = new Definition();
- }
-
- public bool Interpret()
- {
- Tokenizer t = new Tokenizer();
-
- ExecTimer.Timer.AddTimingPoint(ExecTimer.KEY_GENERIC);
-
- foreach (Tuple defpair in EAOptions.Instance.defs)
- {
- myParser.ParseAll(t.TokenizeLine("#define " + defpair.Item1 + " " + defpair.Item2, "cmd", 0));
- }
-
- IList lines = new List(myParser.ParseAll(t.Tokenize(sin, iFile)));
-
- /* First pass on AST: Identifier resolution.
- *
- * Suppose we had the code
- *
- * POIN myLabel
- * myLabel:
- *
- * At parse time, myLabel did not exist for the POIN.
- * It is at this point we want to make sure all references to identifiers are valid, before assembling.
- */
- List undefinedIds = new List();
- foreach (ILineNode line in lines)
- {
- try
- {
- line.EvaluateExpressions(undefinedIds);
- } catch (MacroInvocationNode.MacroException e)
- {
- myParser.Error(e.CausedError.MyLocation, "Unexpanded macro.");
- }
- }
-
- foreach (Token errCause in undefinedIds)
- {
- if (errCause.Content.StartsWith(Pool.pooledLabelPrefix, StringComparison.Ordinal))
- {
- myParser.Error(errCause.Location, "Unpooled data (forgot #pool?)");
- }
- else
- {
- myParser.Error(errCause.Location, "Undefined identifier: " + errCause.Content);
- }
- }
-
- /* Last step: assembly */
-
- ExecTimer.Timer.AddTimingPoint(ExecTimer.KEY_DATAWRITE);
-
- if (!log.HasErrored)
- {
- foreach (ILineNode line in lines)
- {
- if (Program.Debug)
- {
- log.Message(Log.MsgKind.DEBUG, line.PrettyPrint(0));
- }
-
- line.WriteData(output);
- }
-
- output.Commit();
-
- log.Output.WriteLine("No errors. Please continue being awesome.");
- return true;
- }
- else
- {
- log.Output.WriteLine("Errors occurred; no changes written.");
- return false;
- }
- }
-
- public bool WriteNocashSymbols(TextWriter output)
- {
- foreach (var label in myParser.GlobalScope.Head.LocalLabels())
- {
- // TODO: more elegant offset to address mapping
- output.WriteLine("{0:X8} {1}", label.Value + 0x8000000, label.Key);
- }
-
- return true;
- }
-
- private static IEnumerable LoadAllRaws(string rawsFolder, string rawsExtension)
- {
- var directoryInfo = new DirectoryInfo(rawsFolder);
- var files = directoryInfo.GetFiles("*" + rawsExtension, SearchOption.AllDirectories);
-
- foreach (FileInfo fileInfo in files)
- {
- using (var fs = new FileStream(fileInfo.FullName, FileMode.Open))
- foreach (var raw in RawReader.ParseAllRaws(fs))
- yield return raw;
- }
- }
-
- private static IList ListAllRaws(string rawsFolder, string rawsExtension)
- {
- return new List(LoadAllRaws(rawsFolder, rawsExtension));
- }
-
- private static Dictionary> ProcessRaws(string game, IList allRaws)
- {
- Dictionary> result = new Dictionary>();
-
- foreach (Raw r in allRaws)
- {
- if (r.Game.Count == 0 || r.Game.Contains(game))
- result.AddTo(r.Name, r);
- }
-
- return result;
- }
- }
-}
diff --git a/ColorzCore/EAOptions.cs b/ColorzCore/EAOptions.cs
index f3d934e..d6d05dd 100644
--- a/ColorzCore/EAOptions.cs
+++ b/ColorzCore/EAOptions.cs
@@ -1,37 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace ColorzCore
{
- class EAOptions
+ public static class EAOptions
{
- public bool werr;
- public bool nowarn, nomess;
- public bool buildTimes;
+ [Flags]
+ public enum Warnings : long
+ {
+ None = 0,
+
+ // warn on non-portable include paths on Windows
+ NonPortablePath = 1,
+
+ // warn on #define on an existing definition
+ ReDefine = 2,
- public bool noColoredLog;
+ // warn on write before ORG
+ // NOTE: currently no way to disable
+ UninitializedOffset = 4,
- public bool nocashSym;
+ // warn on unintuitive macro expansions (#define A 1 + 2 ... BYTE A * 2 )
+ UnintuitiveExpressionMacros = 8,
- public List includePaths = new List();
- public List toolsPaths = new List();
- public List> defs = new List>();
- public static EAOptions Instance { get; } = new EAOptions();
+ // warn on expansion of unguarded expression within macro
+ UnguardedExpressionMacros = 16,
- public int romOffset;
+ // warn on macro expanded into "PUSH ; ORG value ; name : ; POP"
+ // NOTE: currently no way to disable
+ SetSymbolMacros = 32,
- private EAOptions()
+ // warn on use of legacy features that were superceded (such as the String macro)
+ LegacyFeatures = 64,
+
+ Extra = UnguardedExpressionMacros | LegacyFeatures,
+ All = long.MaxValue & ~Extra,
+ }
+
+ [Flags]
+ public enum Extensions : long
{
- werr = false;
- nowarn = false;
- nomess = false;
- noColoredLog = false;
- nocashSym = false;
- buildTimes = false;
- romOffset = 0x8000000;
+ None = 0,
+
+ // enable ReadByteAt and friends
+ ReadDataMacros = 1,
+
+ // enable incext and inctext/inctevent
+ IncludeTools = 2,
+
+ // enable AddToPool and #pool
+ AddToPool = 4,
+
+ Extra = All,
+ All = long.MaxValue,
}
+
+ public static bool WarningsAreErrors { get; set; }
+ public static bool QuietWarnings { get; set; }
+ public static bool QuietMessages { get; set; }
+ public static bool MonochromeLog { get; set; }
+ public static bool BenchmarkBuildTimes { get; set; }
+ public static bool ProduceNocashSym { get; set; }
+
+ // NOTE: currently this is not exposed to users
+ public static bool TranslateBackslashesInPaths { get; set; } = true;
+
+ public static int BaseAddress { get; set; } = 0x8000000;
+ public static int MaximumBinarySize { get; set; } = 0x2000000;
+
+ public static List IncludePaths { get; } = new List();
+ public static List ToolsPaths { get; } = new List();
+ public static List<(string, string)> PreDefintions { get; } = new List<(string, string)>();
+
+ public static Warnings EnabledWarnings { get; set; } = Warnings.All;
+
+ // NOTE: currently this is not exposed to users
+ public static Extensions EnabledExtensions { get; set; } = Extensions.All;
+
+ public static bool IsWarningEnabled(Warnings warning) => EnabledWarnings.HasFlag(warning);
+ public static bool IsExtensionEnabled(Extensions extension) => EnabledExtensions.HasFlag(extension);
}
}
diff --git a/ColorzCore/ExecTimer.cs b/ColorzCore/ExecTimer.cs
index 5c603ad..d8ffbb5 100644
--- a/ColorzCore/ExecTimer.cs
+++ b/ColorzCore/ExecTimer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
namespace ColorzCore
{
@@ -14,11 +15,11 @@ public class ExecTimer
private List> timingPoints;
- private Dictionary times;
- private Dictionary counts;
+ private Dictionary? times;
+ private Dictionary? counts;
+
+ private TimeSpan totalTime = TimeSpan.Zero;
- private TimeSpan totalTime;
-
private ExecTimer()
{
timingPoints = new List>();
@@ -37,8 +38,9 @@ public SortedList SortedTimes
SortedList sortedTimes = new SortedList();
- foreach (KeyValuePair time in this.times)
- sortedTimes.Add(time.Value, time.Key);
+ if (this.times != null)
+ foreach (KeyValuePair time in this.times)
+ sortedTimes.Add(time.Value, time.Key);
return sortedTimes;
}
@@ -51,7 +53,7 @@ public Dictionary Times
if (this.times == null)
ComputeTimes();
- return this.times;
+ return this.times!;
}
}
@@ -62,7 +64,7 @@ public Dictionary Counts
if (this.counts == null)
ComputeTimes();
- return this.counts;
+ return this.counts!;
}
}
@@ -70,7 +72,7 @@ public TimeSpan TotalTime
{
get
{
- if (this.totalTime == null)
+ if (this.totalTime == TimeSpan.Zero)
ComputeTimes();
return this.totalTime;
diff --git a/ColorzCore/IO/ASM.cs b/ColorzCore/IO/ASM.cs
index 4fb8dfb..66f5cc8 100644
--- a/ColorzCore/IO/ASM.cs
+++ b/ColorzCore/IO/ASM.cs
@@ -2,8 +2,7 @@
using System.IO;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using ColorzCore.Interpreter;
namespace ColorzCore.IO
{
@@ -19,21 +18,21 @@ public ASM(StreamWriter asmStream, StreamWriter ldsStream)
private void WriteToASM(int position, byte[] data)
{
- string sectionName = String.Format(".ea_{0:x}", 0x8000000 + position);
- asmStream.WriteLine(".section {0},\"ax\",%progbits", sectionName);
- asmStream.WriteLine(".global {0}", sectionName);
- asmStream.WriteLine("{0}:", sectionName);
+ string sectionName = $".ea_{EAInterpreter.ConvertToAddress(position):x}";
+ asmStream.WriteLine($".section {sectionName},\"ax\",%progbits");
+ asmStream.WriteLine($".global {sectionName}");
+ asmStream.WriteLine($"{sectionName}:");
asmStream.Write("\t.byte ");
foreach (byte value in data)
- asmStream.Write("0x{0:x}, ", value);
+ asmStream.Write($"0x{value:x}, ");
asmStream.WriteLine();
}
private void WriteToLDS(int position)
{
- string sectionName = String.Format(".ea_{0:x}", 0x8000000 + position);
- ldsStream.WriteLine(". = 0x{0:x};", 0x8000000 + position);
- ldsStream.WriteLine("{0} : {{*.o({0})}}", sectionName);
+ string sectionName = $".ea_{EAInterpreter.ConvertToAddress(position):x}";
+ ldsStream.WriteLine($". = 0x{EAInterpreter.ConvertToAddress(position):x};");
+ ldsStream.WriteLine($"{sectionName} : {{*.o({sectionName})}}");
}
public void WriteTo(int position, byte[] data)
diff --git a/ColorzCore/IO/IOUtility.cs b/ColorzCore/IO/IOUtility.cs
index 73c7c78..d7c8fe0 100644
--- a/ColorzCore/IO/IOUtility.cs
+++ b/ColorzCore/IO/IOUtility.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
@@ -22,17 +23,93 @@ public static string UnescapePath(string param)
return sb.Replace("\\ ", " ").Replace("\\\\", "\\").ToString();
}
- public static string GetToolFileName(string name)
- {
- switch (Environment.OSVersion.Platform)
+ public static string GetToolFileName(string name)
+ {
+ return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? $"{name}.exe" : name;
+ }
+
+ // HACK: this is to make Log not print the entire path every time
+ public static string GetPortableBasePathForPrefix(string name)
+ {
+ string? result = Path.GetDirectoryName(name);
+
+ if (string.IsNullOrEmpty(result))
+ {
+ return "";
+ }
+ else
+ {
+ return (result + '/').Replace('\\', '/');
+ }
+ }
+
+ private static readonly char[] pathSeparators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
+ private static readonly char[] invalidFileCharacters = Path.GetInvalidFileNameChars();
+
+ ///
+ /// Convert a user-given path expression to a 'portable' one. That is, it would work on other systems.
+ /// This is used for warning emission from '#include' and '#incbin' directives that refer to non-portable paths
+ ///
+ /// The full real path corresponding to the given expression
+ /// The user given expression
+ /// The portable version of the expression, possibly the same as the input expression
+ public static string GetPortablePathExpression(string fullPath, string pathExpression)
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // this method is only meaningful on Windows, which has case-insensitive paths and '\' path separators.
+ // I believe that on other systems (Linux and friends for sure, probably macOS also?), valid paths are necessarily portable.
+ return pathExpression;
+ }
+
+ if (Path.IsPathRooted(pathExpression))
+ {
+ // HACK: rooted paths (like absolute paths) don't really make sense to make portable anyway
+ // those are most likely generated paths from tools so this doesn't really matter
+
+ return pathExpression;
+ }
+
+ IList inputComponents = pathExpression.Split(pathSeparators).ToList();
+ IList outputComponents = new List(); // will be in reverse
+
+ int upwind = 0;
+
+ foreach (string component in inputComponents.Reverse())
{
- case PlatformID.Win32Windows: // Who knows, maybe someone runs EA on win 95
- case PlatformID.Win32NT:
- return name + ".exe";
-
- default:
- return name;
- }
+ if (component.IndexOfAny(invalidFileCharacters) != -1)
+ {
+ // fallback just in case
+ outputComponents.Add(component);
+ }
+ if (component == "..")
+ {
+ outputComponents.Add(component);
+ upwind++;
+ }
+ else if (component == ".")
+ {
+ outputComponents.Add(component);
+ }
+ else if (upwind > 0)
+ {
+ // this was a "DirName/..", get the real name of this DirName
+ string? correctComponent = Path.GetFileName(Directory.GetFileSystemEntries(fullPath, component).FirstOrDefault());
+ outputComponents.Add(correctComponent ?? component);
+
+ upwind--;
+ }
+ else
+ {
+ string? reducedPath = Path.GetDirectoryName(fullPath);
+ fullPath = string.IsNullOrEmpty(reducedPath) ? "." : reducedPath;
+
+ string? correctComponent = Path.GetFileName(Directory.GetFileSystemEntries(fullPath, component).FirstOrDefault());
+ outputComponents.Add(correctComponent ?? component);
+ }
+ }
+
+ return string.Join("/", outputComponents.Reverse());
}
}
}
diff --git a/ColorzCore/IO/IncludeFileSearcher.cs b/ColorzCore/IO/IncludeFileSearcher.cs
index 4fe5f1b..2195ed3 100644
--- a/ColorzCore/IO/IncludeFileSearcher.cs
+++ b/ColorzCore/IO/IncludeFileSearcher.cs
@@ -10,43 +10,43 @@ public class IncludeFileSearcher
public List IncludeDirectories { get; } = new List();
public bool AllowRelativeInclude { get; set; } = true;
- public Maybe FindFile(string name)
+ public string? FindFile(string name)
{
return FindFile(null, name);
}
- public Maybe FindFile(string cwd, string name)
+ public string? FindFile(string? cwd, string name)
{
// Find the first valid file in the list of possible file paths
foreach (string path in EnumeratePossibleAccessPaths(cwd, name))
{
if (File.Exists(path))
- return new Just(path);
+ return path;
}
- return new Nothing();
+ return null;
}
- public Maybe FindDirectory(string name)
+ public string? FindDirectory(string name)
{
return FindDirectory(null, name);
}
- public Maybe FindDirectory(string cwd, string name)
+ public string? FindDirectory(string? cwd, string name)
{
// Find the first valid directory in the list of possible file paths
foreach (string path in EnumeratePossibleAccessPaths(cwd, name))
{
if (Directory.Exists(path))
- return new Just(path);
+ return path;
}
- return new Nothing();
+ return null;
}
- protected IEnumerable EnumeratePossibleAccessPaths(string cwd, string name)
+ protected IEnumerable EnumeratePossibleAccessPaths(string? cwd, string name)
{
if (AllowRelativeInclude)
{
diff --git a/ColorzCore/IO/Log.cs b/ColorzCore/IO/Log.cs
deleted file mode 100644
index 2cb74af..0000000
--- a/ColorzCore/IO/Log.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using ColorzCore.DataTypes;
-
-namespace ColorzCore.IO
-{
- public class Log
- {
- public enum MsgKind
- {
- ERROR,
- WARNING,
- NOTE,
- MESSAGE,
- DEBUG,
- }
-
- public bool HasErrored { get; private set; } = false;
- public bool WarningsAreErrors { get; set; } = false;
-
- public bool NoColoredTags { get; set; } = false;
-
- public List IgnoredKinds { get; } = new List();
-
- public TextWriter Output { get; set; } = Console.Error;
-
- protected struct LogDisplayConfig
- {
- public string tag;
- public ConsoleColor? tagColor;
- }
-
- protected static readonly Dictionary KIND_DISPLAY_DICT = new Dictionary {
- { MsgKind.ERROR, new LogDisplayConfig { tag = "error", tagColor = ConsoleColor.Red } },
- { MsgKind.WARNING, new LogDisplayConfig { tag = "warning", tagColor = ConsoleColor.Magenta } },
- { MsgKind.NOTE, new LogDisplayConfig { tag = "note", tagColor = null } },
- { MsgKind.MESSAGE, new LogDisplayConfig { tag = "message", tagColor = ConsoleColor.Blue } },
- { MsgKind.DEBUG, new LogDisplayConfig { tag = "debug", tagColor = ConsoleColor.Green } }
- };
-
- public void Message(string message)
- {
- Message(MsgKind.MESSAGE, null, message);
- }
-
- public void Message(MsgKind kind, string message)
- {
- Message(kind, null, message);
- }
-
- public void Message(MsgKind kind, Location? source, string message)
- {
- if (WarningsAreErrors && (kind == MsgKind.WARNING))
- {
- kind = MsgKind.ERROR;
- }
-
- HasErrored |= (kind == MsgKind.ERROR);
-
- if (!IgnoredKinds.Contains(kind))
- {
- if (KIND_DISPLAY_DICT.TryGetValue(kind, out LogDisplayConfig config))
- {
- if (!NoColoredTags && config.tagColor.HasValue)
- Console.ForegroundColor = config.tagColor.Value;
-
- Output.Write("{0}: ", config.tag);
-
- if (!NoColoredTags)
- Console.ResetColor();
-
- if (source.HasValue)
- {
- Output.Write("{0}:{1}:{2}: ", source.Value.file, source.Value.lineNum, source.Value.colNum);
- }
-
- Output.WriteLine(message);
- }
- }
- }
- }
-}
diff --git a/ColorzCore/IO/Logger.cs b/ColorzCore/IO/Logger.cs
new file mode 100644
index 0000000..80d46f9
--- /dev/null
+++ b/ColorzCore/IO/Logger.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using ColorzCore.DataTypes;
+
+namespace ColorzCore.IO
+{
+ public class Logger
+ {
+ public enum MessageKind
+ {
+ ERROR,
+ WARNING,
+ NOTE,
+ MESSAGE,
+ DEBUG,
+ CONTINUE,
+ }
+
+ public bool HasErrored { get; private set; } = false;
+ public bool WarningsAreErrors { get; set; } = false;
+
+ public bool NoColoredTags { get; set; } = false;
+
+ public string LocationBasePath { get; set; } = string.Empty;
+
+ public List IgnoredKinds { get; } = new List();
+
+ public TextWriter Output { get; set; } = Console.Error;
+
+ private MessageKind continueKind = MessageKind.CONTINUE;
+ private int continueLength = 0;
+
+ protected struct LogDisplayConfig
+ {
+ public string tag;
+ public ConsoleColor? tagColor;
+ }
+
+ protected static readonly Dictionary KIND_DISPLAY_DICT = new Dictionary
+ {
+ { MessageKind.ERROR, new LogDisplayConfig { tag = "error", tagColor = ConsoleColor.Red } },
+ { MessageKind.WARNING, new LogDisplayConfig { tag = "warning", tagColor = ConsoleColor.Magenta } },
+ { MessageKind.NOTE, new LogDisplayConfig { tag = "note", tagColor = null } },
+ { MessageKind.MESSAGE, new LogDisplayConfig { tag = "message", tagColor = ConsoleColor.Blue } },
+ { MessageKind.DEBUG, new LogDisplayConfig { tag = "debug", tagColor = ConsoleColor.Green } }
+ };
+
+ public void Message(string message)
+ {
+ Message(MessageKind.MESSAGE, null, message);
+ }
+
+ public void Message(MessageKind kind, string message)
+ {
+ Message(kind, null, message);
+ }
+
+ public void Message(MessageKind kind, Location? source, string message)
+ {
+ if (WarningsAreErrors && (kind == MessageKind.WARNING))
+ {
+ kind = MessageKind.ERROR;
+ }
+
+ HasErrored |= kind == MessageKind.ERROR;
+
+ if (kind == MessageKind.CONTINUE)
+ {
+ if (continueKind != MessageKind.CONTINUE && !IgnoredKinds.Contains(continueKind))
+ {
+ Console.Write("".PadLeft(continueLength));
+ Console.WriteLine(message);
+ }
+ }
+ else if (!IgnoredKinds.Contains(kind))
+ {
+ if (KIND_DISPLAY_DICT.TryGetValue(kind, out LogDisplayConfig config))
+ {
+ if (!NoColoredTags && config.tagColor.HasValue)
+ {
+ Console.ForegroundColor = config.tagColor.Value;
+ }
+
+ Output.Write($"{config.tag}: ");
+ continueLength = config.tag.Length + 2;
+
+ if (!NoColoredTags)
+ Console.ResetColor();
+
+ if (source.HasValue)
+ {
+ string locString = source.Value.ToString();
+
+ if (locString.StartsWith(LocationBasePath))
+ {
+ locString = locString.Substring(LocationBasePath.Length);
+ }
+
+ Output.Write($"{locString}: ");
+ continueLength += locString.Length + 2;
+ }
+
+ Output.WriteLine(message);
+
+ continueKind = kind;
+ }
+ }
+ }
+ }
+}
diff --git a/ColorzCore/IO/LoggerExtensions.cs b/ColorzCore/IO/LoggerExtensions.cs
new file mode 100644
index 0000000..8ec85c4
--- /dev/null
+++ b/ColorzCore/IO/LoggerExtensions.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using ColorzCore.DataTypes;
+
+namespace ColorzCore.IO
+{
+ public static class LoggerExtensions
+ {
+ // shorthand helpers
+
+ public static void Message(this Logger self, Location? location, string message) => self.MessageTrace(Logger.MessageKind.MESSAGE, location, message);
+ public static void Warning(this Logger self, Location? location, string message) => self.MessageTrace(Logger.MessageKind.WARNING, location, message);
+ public static void Error(this Logger self, Location? location, string message) => self.MessageTrace(Logger.MessageKind.ERROR, location, message);
+
+ private static void MessageTrace(this Logger self, Logger.MessageKind kind, Location? location, string message)
+ {
+ if (location is Location myLocation && myLocation.macroLocation != null)
+ {
+ MacroLocation macroLocation = myLocation.macroLocation;
+ self.MessageTrace(kind, macroLocation.Location, message);
+ self.Message(Logger.MessageKind.NOTE, location, $"From inside of macro `{macroLocation.MacroName}`.");
+ }
+ else
+ {
+ string[] messages = message.Split('\n');
+ self.Message(kind, location, messages[0]);
+
+ for (int i = 1; i < messages.Length; i++)
+ {
+ self.Message(Logger.MessageKind.CONTINUE, messages[i]);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorzCore/IO/ROM.cs b/ColorzCore/IO/ROM.cs
index 32b03de..eb81761 100644
--- a/ColorzCore/IO/ROM.cs
+++ b/ColorzCore/IO/ROM.cs
@@ -2,22 +2,22 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace ColorzCore.IO
{
- class ROM : IOutput
+ public class ROM : IOutput
{
- private BufferedStream myStream;
- private byte[] myData;
+ private readonly BufferedStream myStream;
+ private readonly byte[] myData;
private int size;
- public ROM(Stream myROM)
+ public byte this[int offset] => myData[offset];
+
+ public ROM(Stream myROM, int maximumSize)
{
myStream = new BufferedStream(myROM);
- myData = new byte[0x2000000];
- size = myStream.Read(myData, 0, 0x2000000);
+ myData = new byte[maximumSize];
+ size = myStream.Read(myData, 0, maximumSize);
myStream.Position = 0;
}
diff --git a/ColorzCore/IO/TblEncoding.cs b/ColorzCore/IO/TblEncoding.cs
new file mode 100644
index 0000000..68541b8
--- /dev/null
+++ b/ColorzCore/IO/TblEncoding.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace ColorzCore.IO
+{
+ public class TblEncoding
+ {
+ // https://datacrystal.romhacking.net/wiki/Text_Table
+
+ private class EncodingTableNode
+ {
+ public byte[]? encoding;
+ public IDictionary nextTable = new Dictionary();
+ }
+
+ private readonly IDictionary rootTable;
+
+ private TblEncoding()
+ {
+ rootTable = new Dictionary();
+ }
+
+ public byte[] ConvertToBytes(string inputString)
+ {
+ return ConvertToBytes(inputString, 0, inputString.Length);
+ }
+
+ public byte[] ConvertToBytes(string inputString, int start, int length)
+ {
+ using MemoryStream memoryStream = new MemoryStream();
+ using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
+
+ int offset = start;
+ int end = start + length;
+
+ while (offset < end)
+ {
+ // find longest match by following tree branches and keeping track of last encodable match
+
+ byte[]? currentMatch = null;
+ int currentMatchLength = 0;
+
+ IDictionary table = rootTable;
+
+ for (int j = 0; offset + j < end; j++)
+ {
+ if (!table.TryGetValue(inputString[offset + j], out EncodingTableNode? node))
+ {
+ break;
+ }
+
+ table = node.nextTable;
+
+ if (node.encoding != null)
+ {
+ currentMatch = node.encoding;
+ currentMatchLength = j + 1;
+ }
+ }
+
+ if (currentMatch == null)
+ {
+ // TODO: better exception?
+ throw new Exception($"Could not encode character: '{inputString[offset]}'");
+ }
+ else
+ {
+ binaryWriter.Write(currentMatch);
+ offset += currentMatchLength;
+ }
+ }
+
+ return memoryStream.ToArray();
+ }
+
+ public void MapSequence(string sequence, byte[] bytes)
+ {
+ if (!rootTable.TryGetValue(sequence[0], out EncodingTableNode? mapping))
+ {
+ mapping = rootTable[sequence[0]] = new EncodingTableNode();
+ }
+
+ // Find node corresponding to character sequence
+ for (int i = 1; i < sequence.Length; i++)
+ {
+ IDictionary thisTable = mapping.nextTable;
+
+ if (!thisTable.TryGetValue(sequence[i], out mapping))
+ {
+ mapping = thisTable[sequence[i]] = new EncodingTableNode();
+ }
+ }
+
+ mapping.encoding = bytes;
+ }
+
+ // TODO: this could be elsewhere
+ private static byte[] HexToBytes(string hex, int offset, int length)
+ {
+ byte[] result = new byte[length / 2];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ // this could be optimized...
+ result[i] = Convert.ToByte(hex.Substring(offset + i * 2, 2), 16);
+ }
+
+ return result;
+ }
+
+ public static TblEncoding FromTextReader(TextReader reader)
+ {
+ TblEncoding result = new TblEncoding();
+ string? line;
+
+ char[] equalForSplit = new char[] { '=' };
+
+ while ((line = reader.ReadLine()) != null)
+ {
+ string[] parts;
+
+ // this is never null but might be white space
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ line = line.TrimStart(null);
+
+ switch (line[0])
+ {
+ case '*':
+ result.MapSequence("\n", HexToBytes(line, 1, line.Length - 1));
+ break;
+
+ case '/':
+ // What is this?
+ // not very well documented
+ break;
+
+ default:
+ parts = line.Split(equalForSplit, 2);
+ result.MapSequence(parts[1], HexToBytes(parts[0], 0, parts[0].Length));
+ break;
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/ColorzCore/Interpreter/AtomVisitor.cs b/ColorzCore/Interpreter/AtomVisitor.cs
new file mode 100644
index 0000000..023c73c
--- /dev/null
+++ b/ColorzCore/Interpreter/AtomVisitor.cs
@@ -0,0 +1,32 @@
+
+using ColorzCore.Parser.AST;
+
+namespace ColorzCore.Interpreter
+{
+ public abstract class AtomVisitor
+ {
+ protected void Visit(IAtomNode node)
+ {
+ switch (node)
+ {
+ case OperatorNode operatorNode:
+ VisitNode(operatorNode);
+ break;
+ case UnaryOperatorNode unaryOperatorNode:
+ VisitNode(unaryOperatorNode);
+ break;
+ case IdentifierNode identifierNode:
+ VisitNode(identifierNode);
+ break;
+ case NumberNode numberNode:
+ VisitNode(numberNode);
+ break;
+ }
+ }
+
+ protected abstract void VisitNode(UnaryOperatorNode node);
+ protected abstract void VisitNode(IdentifierNode node);
+ protected abstract void VisitNode(NumberNode node);
+ protected abstract void VisitNode(OperatorNode node);
+ }
+}
diff --git a/ColorzCore/Interpreter/BaseClosure.cs b/ColorzCore/Interpreter/BaseClosure.cs
new file mode 100644
index 0000000..5fbe44c
--- /dev/null
+++ b/ColorzCore/Interpreter/BaseClosure.cs
@@ -0,0 +1,14 @@
+namespace ColorzCore.Interpreter
+{
+ class BaseClosure : Closure
+ {
+ public override bool HasLocalSymbol(string label)
+ {
+ return label.ToUpperInvariant() switch
+ {
+ "CURRENTOFFSET" or "__LINE__" or "__FILE__" => true,
+ _ => base.HasLocalSymbol(label),
+ };
+ }
+ }
+}
diff --git a/ColorzCore/Interpreter/Closure.cs b/ColorzCore/Interpreter/Closure.cs
new file mode 100644
index 0000000..c2aa981
--- /dev/null
+++ b/ColorzCore/Interpreter/Closure.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using ColorzCore.Parser.AST;
+
+namespace ColorzCore.Interpreter
+{
+ public class Closure
+ {
+ private Dictionary Symbols { get; }
+ private Dictionary NonComputedSymbols { get; }
+
+ public Closure()
+ {
+ Symbols = new Dictionary();
+ NonComputedSymbols = new Dictionary();
+ }
+
+ public virtual bool HasLocalSymbol(string label)
+ {
+ return Symbols.ContainsKey(label) || NonComputedSymbols.ContainsKey(label);
+ }
+
+ // HACK: having evaluationPhase being passed here is a bit suspect.
+ public virtual int GetSymbol(string label, EvaluationPhase evaluationPhase)
+ {
+ if (Symbols.TryGetValue(label, out int value))
+ {
+ return value;
+ }
+
+ // Try to evaluate assigned symbol
+
+ IAtomNode node = NonComputedSymbols[label];
+ NonComputedSymbols.Remove(label);
+
+ return node.TryEvaluate(e =>
+ {
+ NonComputedSymbols.Add(label, node);
+ throw new SymbolComputeException(label, node, e);
+ }, evaluationPhase)!.Value;
+ }
+
+ public void AddSymbol(string label, int value) => Symbols[label] = value;
+ public void AddSymbol(string label, IAtomNode node) => NonComputedSymbols[label] = node.Simplify(e => { }, EvaluationPhase.Early);
+
+ public IEnumerable> LocalSymbols()
+ {
+ foreach (KeyValuePair label in Symbols)
+ {
+ yield return label;
+ }
+ }
+
+ public class SymbolComputeException : Exception
+ {
+ public string SymbolName { get; }
+ public IAtomNode Expression { get; }
+
+ public SymbolComputeException(string name, IAtomNode node, Exception e)
+ : base($"Couldn't evaluate value of symbol `{name}` ({e.Message})")
+ {
+ SymbolName = name;
+ Expression = node;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorzCore/Interpreter/Diagnostics/DiagnosticsHelpers.cs b/ColorzCore/Interpreter/Diagnostics/DiagnosticsHelpers.cs
new file mode 100644
index 0000000..5d50daa
--- /dev/null
+++ b/ColorzCore/Interpreter/Diagnostics/DiagnosticsHelpers.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using ColorzCore.DataTypes;
+using ColorzCore.Lexer;
+using ColorzCore.Parser;
+using ColorzCore.Parser.AST;
+
+namespace ColorzCore.Interpreter.Diagnostics
+{
+ public static class DiagnosticsHelpers
+ {
+ // Gets string representation of expression with emphasis on locations for which the predicate is true
+ public static string GetEmphasizedExpression(IAtomNode node, Func emphasisPredicate)
+ {
+ return new EmphasisExpressionPrinter(emphasisPredicate).PrintExpression(node);
+ }
+
+ /*
+ // Print expression (unused)
+ public static string PrettyPrintExpression(IAtomNode node)
+ {
+ return GetEmphasizedExpression(node, _ => false);
+ }
+ */
+
+ // visits operators that aren't around parenthesises or brackets
+ public static void VisitUnguardedOperators(IList tokens, Action action)
+ {
+ if (tokens.Count > 1)
+ {
+ int paren = 0;
+ int bracket = 0;
+
+ foreach (Token token in tokens)
+ {
+ switch (token.Type)
+ {
+ case TokenType.OPEN_PAREN:
+ paren++;
+ break;
+
+ case TokenType.CLOSE_PAREN:
+ paren--;
+ break;
+
+ case TokenType.OPEN_BRACKET:
+ bracket++;
+ break;
+
+ case TokenType.CLOSE_BRACKET:
+ bracket--;
+ break;
+
+ default:
+ if (paren == 0 && bracket == 0 && AtomParser.IsInfixOperator(token))
+ {
+ action.Invoke(token);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // helper for DoesOperationSpanMultipleMacrosUnintuitively
+ private static IAtomNode GetLeftmostInnerNode(IAtomNode node)
+ {
+ if (node is OperatorNode operatorNode)
+ {
+ return GetLeftmostInnerNode(operatorNode.Left);
+ }
+
+ return node;
+ }
+
+ // helper for DoesOperationSpanMultipleMacrosUnintuitively
+ private static IAtomNode GetRightmostInnerNode(IAtomNode node)
+ {
+ if (node is OperatorNode operatorNode)
+ {
+ return GetRightmostInnerNode(operatorNode.Right);
+ }
+
+ if (node is UnaryOperatorNode unaryOperatorNode)
+ {
+ return GetRightmostInnerNode(unaryOperatorNode.Inner);
+ }
+
+ return node;
+ }
+
+ // returns true if node spans multiple macros unintuitively
+ public static bool DoesOperationSpanMultipleMacrosUnintuitively(OperatorNode operatorNode)
+ {
+ /* The condition for this diagnostic are as follows:
+ * 1. The operator node is from the same macro expansion as the closest node on either side
+ * 2. The operator node is not from the same macro expansion as the operator token on that same side */
+
+ MacroLocation? macroLocation = operatorNode.MyLocation.macroLocation;
+
+ IAtomNode left = operatorNode.Left;
+ IAtomNode right = operatorNode.Right;
+
+ if (left is OperatorNode leftNode)
+ {
+ if (macroLocation == GetRightmostInnerNode(left).MyLocation.macroLocation)
+ {
+ if (macroLocation != leftNode.OperatorToken.Location.macroLocation)
+ {
+ return true;
+ }
+ }
+ }
+
+ if (right is OperatorNode rightNode)
+ {
+ if (macroLocation == GetLeftmostInnerNode(right).MyLocation.macroLocation)
+ {
+ if (macroLocation != rightNode.OperatorToken.Location.macroLocation)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static string PrettyParamType(ParamType paramType) => paramType switch
+ {
+ ParamType.ATOM => "Atom",
+ ParamType.LIST => "List",
+ ParamType.STRING => "String",
+ ParamType.MACRO => "Macro",
+ _ => "",
+ };
+
+ // absolute as in "not relative to the value of a symbol"
+ // NOTE: currently unused (I considered using this for SetSymbol detection)
+ public static bool IsAbsoluteAtom(IAtomNode node) => node switch
+ {
+ IdentifierNode => false,
+
+ OperatorNode operatorNode => operatorNode.OperatorToken.Type switch
+ {
+ // A + B is not absolute if either one is relative, but not both
+ TokenType.ADD_OP => IsAbsoluteAtom(operatorNode.Left) == IsAbsoluteAtom(operatorNode.Right),
+
+ // A - B is not absolute if A is relative and not B
+ TokenType.SUB_OP => IsAbsoluteAtom(operatorNode.Left) || !IsAbsoluteAtom(operatorNode.Right),
+
+ // A ?? B is not absolute if A and B aren't absolute
+ TokenType.UNDEFINED_COALESCE_OP => IsAbsoluteAtom(operatorNode.Left) || IsAbsoluteAtom(operatorNode.Right),
+
+ _ => true,
+ },
+
+ _ => true,
+ };
+
+ public static bool IsSubtractionOfCurrentOffset(IAtomNode node)
+ {
+ if (node is OperatorNode operatorNode && operatorNode.OperatorToken.Type == TokenType.SUB_OP)
+ {
+ // the "CURRENTOFFSET" node is a number node whose source token is an identifier
+ if (operatorNode.Right is NumberNode numberNode)
+ {
+ Token token = numberNode.SourceToken;
+ return token.Type == TokenType.IDENTIFIER && token.Content.ToUpperInvariant() == "CURRENTOFFSET";
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/ColorzCore/Interpreter/Diagnostics/EmphasisExpressionPrinter.cs b/ColorzCore/Interpreter/Diagnostics/EmphasisExpressionPrinter.cs
new file mode 100644
index 0000000..bb4a23d
--- /dev/null
+++ b/ColorzCore/Interpreter/Diagnostics/EmphasisExpressionPrinter.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+using ColorzCore.DataTypes;
+using ColorzCore.Parser.AST;
+
+namespace ColorzCore.Interpreter.Diagnostics
+{
+ // Helper class for printing expressions (IAtomNode) with some bits emphasized
+ public class EmphasisExpressionPrinter : AtomVisitor
+ {
+ readonly StringBuilder stringBuilder = new StringBuilder();
+ readonly StringBuilder underlineBuilder = new StringBuilder();
+
+ readonly Func emphasisPredicate;
+ bool wasEmphasized = false;
+
+ public EmphasisExpressionPrinter(Func predicate)
+ {
+ emphasisPredicate = predicate;
+ // targetMacroLocation = macroLocation;
+ }
+
+ public string PrintExpression(IAtomNode expression)
+ {
+ stringBuilder.Clear();
+ underlineBuilder.Clear();
+
+ Visit(expression);
+
+ return $"{stringBuilder}\n{underlineBuilder}";
+ }
+
+ private void AppendString(bool strong, string value)
+ {
+ // HACK: on Windows, ANSI terminal escape codes don't (always?) work
+ if (strong && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !EAOptions.MonochromeLog)
+ {
+ stringBuilder.Append("\x1B[1;37m");
+ stringBuilder.Append(value);
+ stringBuilder.Append("\x1B[0m");
+ }
+ else
+ {
+ stringBuilder.Append(value);
+ }
+
+ underlineBuilder.Append(strong ? '~' : ' ', value.Length);
+
+ wasEmphasized = strong;
+ }
+
+ protected override void VisitNode(OperatorNode node)
+ {
+ AppendString(emphasisPredicate(node.Left.MyLocation), "(");
+
+ Visit(node.Left);
+
+ AppendString(emphasisPredicate(node.OperatorToken.Location), $" {node.OperatorString} ");
+
+ Visit(node.Right);
+
+ AppendString(wasEmphasized, ")");
+ }
+
+ protected override void VisitNode(UnaryOperatorNode node)
+ {
+ AppendString(emphasisPredicate(node.OperatorToken.Location), node.OperatorString);
+
+ Visit(node.Inner);
+ }
+
+ protected override void VisitNode(IdentifierNode node)
+ {
+ AppendString(emphasisPredicate(node.MyLocation), node.IdentifierToken.Content);
+ }
+
+ protected override void VisitNode(NumberNode node)
+ {
+ AppendString(emphasisPredicate(node.MyLocation), node.PrettyPrint());
+ }
+ }
+}
diff --git a/ColorzCore/Interpreter/Diagnostics/SetSymbolMacroDetector.cs b/ColorzCore/Interpreter/Diagnostics/SetSymbolMacroDetector.cs
new file mode 100644
index 0000000..30bf2e7
--- /dev/null
+++ b/ColorzCore/Interpreter/Diagnostics/SetSymbolMacroDetector.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using ColorzCore.DataTypes;
+using ColorzCore.IO;
+using ColorzCore.Parser;
+using ColorzCore.Parser.AST;
+using ColorzCore.Raws;
+
+namespace ColorzCore.Interpreter.Diagnostics
+{
+ public class SetSymbolMacroDetector : IParseConsumer
+ {
+ public Logger Logger { get; }
+
+ private enum State
+ {
+ AwaitingPush,
+ AwaitingOrgAbsolute,
+ AwaitingLabel,
+ AwaitingPop,
+ AwaitingEndOfMacro,
+ }
+
+ State suspectState = State.AwaitingPush;
+ Location? suspectLocation = null;
+
+ public SetSymbolMacroDetector(Logger logger)
+ {
+ Logger = logger;
+ suspectState = State.AwaitingPush;
+ suspectLocation = null;
+ }
+
+ private void OnSequenceBroken(Location location)
+ {
+ if (suspectState == State.AwaitingEndOfMacro && location.macroLocation != suspectLocation?.macroLocation)
+ {
+ Logger.Warning(suspectLocation,
+ "This looks like the expansion of a \"SetSymbol\" macro.\n"
+ + "SetSymbol macros are macros defined as `PUSH ; ORG value ; name : ; POP`.\n"
+ + "Because of changes to the behavior of labels, their use may introduce bugs.\n"
+ + "Consider using symbol assignment instead by writing `name := value`.");
+ }
+
+ suspectState = State.AwaitingPush;
+ suspectLocation = null;
+ }
+
+ public void OnPushStatement(Location location)
+ {
+ OnSequenceBroken(location);
+
+ suspectState = State.AwaitingOrgAbsolute;
+ suspectLocation = location;
+ }
+
+ public void OnOrgStatement(Location location, IAtomNode offsetNode)
+ {
+ if (suspectState == State.AwaitingOrgAbsolute && location.macroLocation == suspectLocation?.macroLocation)
+ {
+ suspectState = State.AwaitingLabel;
+ }
+ else
+ {
+ OnSequenceBroken(location);
+ }
+ }
+
+ public void OnLabel(Location location, string name)
+ {
+ if (suspectState == State.AwaitingLabel && location.macroLocation == suspectLocation?.macroLocation)
+ {
+ suspectState = State.AwaitingPop;
+ }
+ else
+ {
+ OnSequenceBroken(location);
+ }
+ }
+
+ public void OnPopStatement(Location location)
+ {
+ if (suspectState == State.AwaitingPop && location.macroLocation == suspectLocation?.macroLocation)
+ {
+ suspectState = State.AwaitingEndOfMacro;
+ }
+ else
+ {
+ OnSequenceBroken(location);
+ }
+ }
+
+ public void OnOpenScope(Location location) => OnSequenceBroken(location);
+
+ public void OnCloseScope(Location location) => OnSequenceBroken(location);
+
+ public void OnRawStatement(Location location, Raw raw, IList parameters) => OnSequenceBroken(location);
+
+ public void OnAssertStatement(Location location, IAtomNode node) => OnSequenceBroken(location);
+
+ public void OnProtectStatement(Location location, IAtomNode beginAtom, IAtomNode? endAtom) => OnSequenceBroken(location);
+
+ public void OnAlignStatement(Location location, IAtomNode alignNode, IAtomNode? offsetNode) => OnSequenceBroken(location);
+
+ public void OnFillStatement(Location location, IAtomNode amountNode, IAtomNode? valueNode) => OnSequenceBroken(location);
+
+ public void OnSymbolAssignment(Location location, string name, IAtomNode atom) => OnSequenceBroken(location);
+
+ public void OnData(Location location, byte[] data) => OnSequenceBroken(location);
+ }
+}
diff --git a/ColorzCore/Interpreter/EAInterpreter.cs b/ColorzCore/Interpreter/EAInterpreter.cs
new file mode 100644
index 0000000..f132421
--- /dev/null
+++ b/ColorzCore/Interpreter/EAInterpreter.cs
@@ -0,0 +1,496 @@
+using System.Collections.Generic;
+using System.Linq;
+using ColorzCore.DataTypes;
+using ColorzCore.IO;
+using ColorzCore.Lexer;
+using ColorzCore.Parser;
+using ColorzCore.Parser.AST;
+using ColorzCore.Interpreter.Diagnostics;
+using ColorzCore.Raws;
+
+namespace ColorzCore.Interpreter
+{
+ public class EAInterpreter : IParseConsumer
+ {
+ public int CurrentOffset => currentOffset;
+
+ public IList AllScopes { get; }
+
+ public ImmutableStack GlobalScope { get; }
+ public ImmutableStack CurrentScope { get; set; }
+
+ private readonly Stack<(int, bool)> pastOffsets; // currentOffset, offsetInitialized
+ private readonly IList<(int, int, Location)> protectedRegions;
+
+ private bool diagnosedOverflow; // false until first overflow diagnostic.
+ private bool offsetInitialized; // false until first ORG, used for diagnostics
+ private int currentOffset;
+
+ public Logger Logger { get; }
+
+ public EAInterpreter(Logger logger)
+ {
+ Closure headScope = new BaseClosure();
+ AllScopes = new List() { headScope };
+ GlobalScope = new ImmutableStack(headScope, ImmutableStack.Nil);
+ CurrentScope = GlobalScope;
+ pastOffsets = new Stack<(int, bool)>();
+ protectedRegions = new List<(int, int, Location)>();
+ currentOffset = 0;
+ diagnosedOverflow = false;
+ offsetInitialized = false;
+ Logger = logger;
+ }
+
+ // TODO: these next two functions should probably be moved into their own module
+
+ public static int ConvertToAddress(int value)
+ {
+ if (value >= 0 && value < EAOptions.MaximumBinarySize)
+ {
+ value += EAOptions.BaseAddress;
+ }
+
+ return value;
+ }
+
+ public static int ConvertToOffset(int value)
+ {
+ if (value >= EAOptions.BaseAddress && value <= EAOptions.BaseAddress + EAOptions.MaximumBinarySize)
+ {
+ value -= EAOptions.BaseAddress;
+ }
+
+ return value;
+ }
+
+ public IAtomNode BindIdentifier(Token identifierToken)
+ {
+ return identifierToken.Content.ToUpperInvariant() switch
+ {
+ "CURRENTOFFSET" => new NumberNode(identifierToken, EAOptions.BaseAddress + CurrentOffset),
+ _ => new IdentifierNode(identifierToken, CurrentScope),
+ };
+ }
+
+ // Helper method for statement handlers
+ private int? EvaluteAtom(IAtomNode node)
+ {
+ return node.TryEvaluate(e => Logger.Error(node.MyLocation, e.Message), EvaluationPhase.Immediate);
+ }
+
+ private IList LineNodes { get; } = new List();
+
+ public IList HandleEndOfInput()
+ {
+ if (CurrentScope != GlobalScope)
+ {
+ Logger.Error(null, "Reached end of input with an open local scope.");
+ }
+
+ return LineNodes;
+ }
+
+ #region AST Handlers
+
+ public void OnOpenScope(Location _)
+ {
+ Closure newClosure = new Closure();
+
+ AllScopes.Add(newClosure);
+ CurrentScope = new ImmutableStack(newClosure, CurrentScope);
+ }
+
+ public void OnCloseScope(Location location)
+ {
+ if (CurrentScope != GlobalScope)
+ {
+ CurrentScope = CurrentScope.Tail;
+ }
+ else
+ {
+ Logger.Error(location, "Attempted to close local scope without being within one.");
+ }
+ }
+
+ public void OnRawStatement(Location location, Raw raw, IList parameters)
+ {
+ RawNode node = new RawNode(raw, CurrentOffset, parameters);
+
+ if ((CurrentOffset % node.Raw.Alignment) != 0)
+ {
+ Logger.Error(location,
+ $"Bad alignment for raw {node.Raw.Name}: offset ({CurrentOffset:X8}) needs to be {node.Raw.Alignment}-aligned.");
+ }
+ else
+ {
+ // TODO: more efficient spacewise to just have contiguous writing and not an offset with every line?
+ CheckWriteBytes(location, node.Size);
+ LineNodes.Add(node);
+ }
+ }
+
+ public void OnOrgStatement(Location location, IAtomNode offsetNode)
+ {
+ if (EvaluteAtom(offsetNode) is int offset)
+ {
+ offset = ConvertToOffset(offset);
+
+ if (!TrySetCurrentOffset(offset))
+ {
+ Logger.Error(location, $"Invalid offset: 0x{offset:X}");
+ }
+ else
+ {
+ diagnosedOverflow = false;
+ offsetInitialized = true;
+ }
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ }
+ }
+
+ public void OnPushStatement(Location _)
+ {
+ pastOffsets.Push((CurrentOffset, offsetInitialized));
+ }
+
+ public void OnPopStatement(Location location)
+ {
+ if (pastOffsets.Count == 0)
+ {
+ Logger.Error(location, "POP without matching PUSH.");
+ }
+ else
+ {
+ (currentOffset, offsetInitialized) = pastOffsets.Pop();
+ }
+ }
+
+ public void OnAssertStatement(Location location, IAtomNode node)
+ {
+ // helper for distinguishing boolean expressions and other expressions
+ // TODO: move elsewhere perhaps
+ static bool IsBooleanResultHelper(IAtomNode node)
+ {
+ return node switch
+ {
+ UnaryOperatorNode uon => uon.OperatorToken.Type switch
+ {
+ TokenType.LOGNOT_OP => true,
+ _ => false,
+ },
+
+ OperatorNode on => on.OperatorToken.Type switch
+ {
+ TokenType.LOGAND_OP => true,
+ TokenType.LOGOR_OP => true,
+ TokenType.COMPARE_EQ => true,
+ TokenType.COMPARE_NE => true,
+ TokenType.COMPARE_GT => true,
+ TokenType.COMPARE_GE => true,
+ TokenType.COMPARE_LE => true,
+ TokenType.COMPARE_LT => true,
+ _ => false,
+ },
+
+ _ => false,
+ };
+ }
+
+ bool isBoolean = IsBooleanResultHelper(node);
+ bool isSubtractionOfCurrentOffset = DiagnosticsHelpers.IsSubtractionOfCurrentOffset(node);
+
+ if (EvaluteAtom(node) is int result)
+ {
+ if (isBoolean && result == 0)
+ {
+ Logger.Error(location, "Assertion failed");
+ }
+ else if (!isBoolean && result < 0)
+ {
+ /* users may do something like ASSERT UpperBound - CURRENTOFFSET
+ * If UpperBound is an offset rather than an address, this will now certainly fail.
+ * as CURRENTOFFSET was changed to be expanded into an address rather than a ROM offset.
+ * we do not want this to break too hard so we try to emit a warning rather than an error. */
+
+ if (isSubtractionOfCurrentOffset && result + EAOptions.BaseAddress >= 0)
+ {
+ Logger.Warning(location,
+ $"Assertion would fail with value {result} if CURRENTOFFSET is treated as an address.\n"
+ + "ColorzCore was recently changed to work in terms of addresses rather that ROM offsets.\n"
+ + "Consider changing the left operand to an address, or if you need to keep compatibility,\n"
+ + "You can also bitwise AND CURRENTOFFSET to keep it within the desired bounds.");
+ }
+ else
+ {
+ Logger.Error(location, $"Assertion failed with value {result}.");
+ }
+ }
+ }
+ else
+ {
+ Logger.Error(node.MyLocation, "Failed to evaluate ASSERT expression.");
+ }
+ }
+
+ public void OnProtectStatement(Location location, IAtomNode beginAtom, IAtomNode? endAtom)
+ {
+ if (EvaluteAtom(beginAtom) is int beginValue)
+ {
+ beginValue = ConvertToOffset(beginValue);
+
+ int length = 4;
+
+ if (endAtom != null)
+ {
+ if (EvaluteAtom(endAtom) is int endValue)
+ {
+ endValue = ConvertToOffset(endValue);
+
+ length = endValue - beginValue;
+
+ switch (length)
+ {
+ case < 0:
+ Logger.Error(location, $"Invalid PROTECT region: end address ({endValue:X8}) is before start address ({beginValue:X8}).");
+ return;
+
+ case 0:
+ // NOTE: does this need to be an error?
+ Logger.Error(location, $"Empty PROTECT region: end address is equal to start address ({beginValue:X8}).");
+ return;
+ }
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ return;
+ }
+ }
+
+ protectedRegions.Add((beginValue, length, location));
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ }
+ }
+
+ public void OnAlignStatement(Location location, IAtomNode alignNode, IAtomNode? offsetNode)
+ {
+ if (EvaluteAtom(alignNode) is int alignValue)
+ {
+ if (alignValue > 0)
+ {
+ int alignOffset = 0;
+
+ if (offsetNode != null)
+ {
+ if (EvaluteAtom(offsetNode) is int rawOffset)
+ {
+ if (rawOffset >= 0)
+ {
+ alignOffset = ConvertToOffset(rawOffset) % alignValue;
+ }
+ else
+ {
+ Logger.Error(location, $"ALIGN offset cannot be negative (got {rawOffset})");
+ return;
+ }
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ return;
+ }
+ }
+
+ if (CurrentOffset % alignValue != alignOffset)
+ {
+ int skip = alignValue - (CurrentOffset + alignValue - alignOffset) % alignValue;
+
+ if (!TrySetCurrentOffset(CurrentOffset + skip) && !diagnosedOverflow)
+ {
+ Logger.Error(location, $"Trying to jump past end of binary (CURRENTOFFSET would surpass {EAOptions.MaximumBinarySize:X})");
+ diagnosedOverflow = true;
+ }
+ }
+ }
+ else
+ {
+ Logger.Error(location, $"Invalid ALIGN value (got {alignValue}).");
+ }
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ }
+ }
+
+ public void OnFillStatement(Location location, IAtomNode amountNode, IAtomNode? valueNode)
+ {
+ if (EvaluteAtom(amountNode) is int amount)
+ {
+ if (amount > 0)
+ {
+ int fillValue = 0;
+
+ if (valueNode != null)
+ {
+ if (EvaluteAtom(valueNode) is int rawValue)
+ {
+ fillValue = rawValue;
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ return;
+ }
+ }
+
+ var data = new byte[amount];
+
+ for (int i = 0; i < amount; ++i)
+ {
+ data[i] = (byte)fillValue;
+ }
+
+ var node = new DataNode(CurrentOffset, data);
+
+ CheckWriteBytes(location, amount);
+ LineNodes.Add(node);
+ }
+ else
+ {
+ Logger.Error(location, $"Invalid FILL amount (got {amount}).");
+ }
+ }
+ else
+ {
+ // EvaluateAtom already printed an error message
+ }
+ }
+
+ public void OnSymbolAssignment(Location location, string name, IAtomNode atom)
+ {
+ if (atom.TryEvaluate(_ => { }, EvaluationPhase.Early) is int value)
+ {
+ TryDefineSymbol(location, name, value);
+ }
+ else
+ {
+ TryDefineSymbol(location, name, atom);
+ }
+ }
+
+ public void OnLabel(Location location, string name)
+ {
+ TryDefineSymbol(location, name, ConvertToAddress(CurrentOffset));
+ }
+
+ public void OnData(Location location, byte[] data)
+ {
+ DataNode node = new DataNode(CurrentOffset, data);
+ CheckWriteBytes(location, node.Size);
+ LineNodes.Add(node);
+ }
+
+ #endregion
+
+ public bool IsValidLabelName(string name)
+ {
+ // TODO: this could be where checks for CURRENTOFFSET, __LINE__ and __FILE__ are?
+ return true; // !IsReservedName(name);
+ }
+
+ public void TryDefineSymbol(Location location, string name, int value)
+ {
+ if (CurrentScope.Head.HasLocalSymbol(name))
+ {
+ Logger.Warning(location, $"Symbol already in scope, ignoring: {name}");
+ }
+ else if (!IsValidLabelName(name))
+ {
+ // NOTE: IsValidLabelName returns true always. This is dead code
+ Logger.Error(location, $"Invalid symbol name {name}.");
+ }
+ else
+ {
+ CurrentScope.Head.AddSymbol(name, value);
+ }
+ }
+
+ public void TryDefineSymbol(Location location, string name, IAtomNode expression)
+ {
+ if (CurrentScope.Head.HasLocalSymbol(name))
+ {
+ Logger.Warning(location, $"Symbol already in scope, ignoring: {name}");
+ }
+ else if (!IsValidLabelName(name))
+ {
+ // NOTE: IsValidLabelName returns true always. This is dead code
+ Logger.Error(location, $"Invalid symbol name {name}.");
+ }
+ else
+ {
+ CurrentScope.Head.AddSymbol(name, expression);
+ }
+ }
+
+ // Return value: Location where protection occurred. Nothing if location was not protected.
+ public Location? IsProtected(int offset, int length)
+ {
+ offset = ConvertToOffset(offset);
+
+ foreach ((int protectedOffset, int protectedLength, Location location) in protectedRegions)
+ {
+ /* They intersect if the last offset in the given region is after the start of this one
+ * and the first offset in the given region is before the last of this one. */
+
+ if (offset + length > protectedOffset && offset < protectedOffset + protectedLength)
+ {
+ return location;
+ }
+ }
+
+ return null;
+ }
+
+ public void CheckWriteBytes(Location location, int length)
+ {
+ if (!offsetInitialized && EAOptions.IsWarningEnabled(EAOptions.Warnings.UninitializedOffset))
+ {
+ Logger.Warning(location, "Writing before initializing offset. You may be breaking the ROM! (use `ORG offset` to set write offset).");
+ }
+
+ if (IsProtected(CurrentOffset, length) is Location prot)
+ {
+ Logger.Error(location, $"Trying to write data to area protected by {prot}");
+ }
+
+ if (!TrySetCurrentOffset(CurrentOffset + length) && !diagnosedOverflow)
+ {
+ Logger.Error(location, $"Trying to write past end of binary (CURRENTOFFSET would surpass {EAOptions.MaximumBinarySize:X})");
+ diagnosedOverflow = true;
+ }
+ }
+
+ private bool TrySetCurrentOffset(int value)
+ {
+ if (value < 0 || value > EAOptions.MaximumBinarySize)
+ {
+ return false;
+ }
+ else
+ {
+ currentOffset = value;
+ diagnosedOverflow = false;
+ offsetInitialized = true;
+ return true;
+ }
+ }
+ }
+}
diff --git a/ColorzCore/Interpreter/EvaluationPhase.cs b/ColorzCore/Interpreter/EvaluationPhase.cs
new file mode 100644
index 0000000..3bda589
--- /dev/null
+++ b/ColorzCore/Interpreter/EvaluationPhase.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace ColorzCore.Interpreter
+{
+ public enum EvaluationPhase
+ {
+ // Early-evaluation: we are not done with parsing and obtaining a value now would just be an optimization
+ Early,
+
+ // Final-evaluation: we are done with parsing and need to freeze this value now
+ Final,
+
+ // Immediate-evaluation: we are not done with parsing but need this value now
+ Immediate,
+ }
+}
diff --git a/ColorzCore/Interpreter/StringProcessor.cs b/ColorzCore/Interpreter/StringProcessor.cs
new file mode 100644
index 0000000..cdaf706
--- /dev/null
+++ b/ColorzCore/Interpreter/StringProcessor.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Text.RegularExpressions;
+using ColorzCore.DataTypes;
+using ColorzCore.Interpreter.Diagnostics;
+using ColorzCore.IO;
+using ColorzCore.Lexer;
+using ColorzCore.Parser;
+using ColorzCore.Parser.AST;
+
+namespace ColorzCore.Interpreter
+{
+ // String handling the various processing of strings (encoding, formatting)
+ public class StringProcessor
+ {
+ public IDictionary TableEncodings { get; } = new Dictionary();
+ public Logger Logger { get; }
+
+ public StringProcessor(Logger logger)
+ {
+ Logger = logger;
+ }
+
+ public byte[] EncodeString(Location locationForLogger, string inputString, string? encodingName)
+ {
+ encodingName ??= "UTF-8";
+
+ switch (encodingName.ToUpperInvariant())
+ {
+ case "UTF-8":
+ case "UTF8":
+ try
+ {
+ return Encoding.UTF8.GetBytes(inputString);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(locationForLogger,
+ $"Failed to encode string '{inputString}': {e.Message}.");
+ return Array.Empty();
+ }
+ }
+
+ if (TableEncodings.TryGetValue(encodingName, out TblEncoding? encoding))
+ {
+ try
+ {
+ return encoding.ConvertToBytes(inputString);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(locationForLogger,
+ $"Failed to encode string '{inputString}': {e.Message}.");
+ }
+ }
+ else
+ {
+ Logger.Error(locationForLogger,
+ $"Unknown encoding: '{encodingName}'.");
+ }
+
+ return Array.Empty();
+ }
+
+ private static readonly Regex formatItemRegex = new Regex(@"\{(?[^:}]+)(?:\:(?[^:}]*))?\}");
+
+ string UserFormatStringError(Location loc, string message, string details)
+ {
+ Logger.Error(loc, $"An error occurred while expanding format string ({message}).");
+ return $"<{message}: {details}>";
+ }
+
+ public string ExpandUserFormatString(Location location, EAParser parser, string stringValue)
+ {
+ return formatItemRegex.Replace(stringValue, match =>
+ {
+ string expr = match.Groups["expr"].Value!;
+ string? format = match.Groups["format"].Value;
+
+ return ExpandFormatItem(parser, location.OffsetBy(match.Index), expr, format);
+ });
+ }
+
+ public string ExpandFormatItem(EAParser parser, Location location, string expr, string? format = null)
+ {
+ MergeableGenerator tokens = new MergeableGenerator(
+ Tokenizer.TokenizeLine($"{expr} \n", location));
+
+ tokens.MoveNext();
+
+ IParamNode? node = parser.ParseParam(tokens);
+
+ if (node == null || tokens.Current.Type != TokenType.NEWLINE)
+ {
+ return UserFormatStringError(location, "bad expression", $"'{expr}'");
+ }
+
+ switch (node)
+ {
+ case IAtomNode atom:
+ if (atom.TryEvaluate(e => Logger.Error(atom.MyLocation, e.Message),
+ EvaluationPhase.Immediate) is int value)
+ {
+ try
+ {
+ // TODO: do we need to raise an error when result == format?
+ // this happens (I think) when a custom format specifier with no substitution
+ return value.ToString(format, CultureInfo.InvariantCulture);
+ }
+ catch (FormatException e)
+ {
+ return UserFormatStringError(node.MyLocation,
+ "bad format specifier", $"'{format}' ({e.Message})");
+ }
+ }
+ else
+ {
+ return UserFormatStringError(node.MyLocation,
+ "failed to evaluate expression", $"'{expr}'");
+ }
+
+ case StringNode stringNode:
+ if (!string.IsNullOrEmpty(format))
+ {
+ return UserFormatStringError(node.MyLocation,
+ "string items cannot specify format", $"'{format}'");
+ }
+ else
+ {
+ return stringNode.Value;
+ }
+
+ default:
+ return UserFormatStringError(node.MyLocation,
+ "invalid format item type", $"'{DiagnosticsHelpers.PrettyParamType(node.Type)}'");
+ }
+ }
+ }
+}
diff --git a/ColorzCore/Lexer/Token.cs b/ColorzCore/Lexer/Token.cs
index ba40c15..61eaa90 100644
--- a/ColorzCore/Lexer/Token.cs
+++ b/ColorzCore/Lexer/Token.cs
@@ -1,39 +1,41 @@
using ColorzCore.DataTypes;
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace ColorzCore.Lexer
{
public class Token
{
- public Location Location { get; }
-
public TokenType Type { get; }
- public string FileName { get { return Location.file; } }
- public int LineNumber { get { return Location.lineNum; } }
- public int ColumnNumber { get { return Location.colNum; } }
public string Content { get; }
+ public Location Location { get; }
- public Token(TokenType type, string fileName, int lineNum, int colNum, string original = "")
- {
- Type = type;
- Location = new Location(fileName, lineNum, colNum+1);
- Content = original;
- }
+ public string FileName => Location.file;
+ public int LineNumber => Location.line;
+ public int ColumnNumber => Location.column;
- public Token(TokenType type, Location newLoc, string content)
+ public Token(TokenType type, Location location, string content = "")
{
Type = type;
- this.Location = newLoc;
+ Location = location;
Content = content;
}
- public override string ToString()
+ public override string ToString() => $"{Location}, {Type}: {Content}";
+
+ public Token MacroClone(MacroLocation macroLocation) => new Token(Type, Location.MacroClone(macroLocation), Content);
+
+ // used for __LINE__ and __FILE__
+ public Location GetSourceLocation()
{
- return String.Format("File {4}, Line {0}, Column {1}, {2}: {3}", LineNumber, ColumnNumber, Type, Content, FileName);
+ Location location = Location;
+
+ while (location.macroLocation != null)
+ {
+ location = location.macroLocation.Location;
+ }
+
+ return location;
}
}
}
diff --git a/ColorzCore/Lexer/TokenType.cs b/ColorzCore/Lexer/TokenType.cs
index 96e8c4d..8a0911e 100644
--- a/ColorzCore/Lexer/TokenType.cs
+++ b/ColorzCore/Lexer/TokenType.cs
@@ -25,9 +25,21 @@ public enum TokenType
LSHIFT_OP,
RSHIFT_OP,
SIGNED_RSHIFT_OP,
+ UNDEFINED_COALESCE_OP,
+ NOT_OP,
AND_OP,
XOR_OP,
OR_OP,
+ LOGNOT_OP,
+ LOGAND_OP,
+ LOGOR_OP,
+ COMPARE_EQ,
+ COMPARE_NE,
+ COMPARE_LT,
+ COMPARE_LE,
+ COMPARE_GT,
+ COMPARE_GE,
+ ASSIGN,
NUMBER,
OPEN_BRACKET,
CLOSE_BRACKET,
diff --git a/ColorzCore/Lexer/Tokenizer.cs b/ColorzCore/Lexer/Tokenizer.cs
index 8e465f3..f780d28 100644
--- a/ColorzCore/Lexer/Tokenizer.cs
+++ b/ColorzCore/Lexer/Tokenizer.cs
@@ -5,6 +5,7 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using ColorzCore.DataTypes;
using ColorzCore.IO;
namespace ColorzCore.Lexer
@@ -14,17 +15,17 @@ class Tokenizer
public const int MAX_ID_LENGTH = 64;
public static readonly Regex numRegex = new Regex("\\G([01]+b|0x[\\da-fA-F]+|\\$[\\da-fA-F]+|\\d+)");
public static readonly Regex idRegex = new Regex("\\G([a-zA-Z_][a-zA-Z0-9_]*)");
- public static readonly Regex stringRegex = new Regex("\\G(([^\\\"]|\\\\\\\")*)"); //"\\G(([^\\\\\\\"]|\\\\[rnt\\\\\\\"])*)");
- public static readonly Regex winPathnameRegex = new Regex(String.Format("\\G([^ \\{0}]|\\ |\\\\)+", Process(Path.GetInvalidPathChars())));
+ public static readonly Regex stringRegex = new Regex(@"\G(([^\""]|\\\"")*)"); //"\\G(([^\\\\\\\"]|\\\\[rnt\\\\\\\"])*)");
+ public static readonly Regex winPathnameRegex = new Regex(string.Format("\\G([^ \\{0}]|\\ |\\\\)+", Process(Path.GetInvalidPathChars())));
public static readonly Regex preprocDirectiveRegex = new Regex("\\G(#[a-zA-Z_][a-zA-Z0-9_]*)");
public static readonly Regex wordRegex = new Regex("\\G([^\\s]+)");
private static string Process(char[] chars)
{
StringBuilder sb = new StringBuilder();
- foreach(char c in chars)
+ foreach (char c in chars)
{
- switch(c)
+ switch (c)
{
case '.':
case '\\':
@@ -57,8 +58,8 @@ public Tokenizer()
{
multilineCommentNesting = 0;
}
-
- public IEnumerable TokenizePhrase(string line, string fileName, int lineNum, int startOffs, int endOffs, int offset = 0)
+
+ public IEnumerable TokenizePhrase(string line, int startOffs, int endOffs, Location location)
{
bool afterInclude = false, afterDirective = false, afterWhitespace = false;
@@ -73,7 +74,8 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
multilineCommentNesting -= 1;
curCol += 2;
continue;
- } else if (nextChar == '/' && curCol + 1 < endOffs && line[curCol + 1] == '*')
+ }
+ else if (nextChar == '/' && curCol + 1 < endOffs && line[curCol + 1] == '*')
{
multilineCommentNesting += 1;
curCol += 2;
@@ -85,7 +87,7 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
continue;
}
}
- if (Char.IsWhiteSpace(nextChar) && nextChar != '\n')
+ if (char.IsWhiteSpace(nextChar) && nextChar != '\n')
{
curCol++;
afterWhitespace = true;
@@ -95,37 +97,44 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
switch (nextChar)
{
case ';':
- yield return new Token(TokenType.SEMICOLON, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.SEMICOLON, location.OffsetBy(curCol));
break;
case ':':
- yield return new Token(TokenType.COLON, fileName, lineNum, curCol+offset);
+ if (curCol + 1 < endOffs && line[curCol + 1] == '=') // ':='
+ {
+ yield return new Token(TokenType.ASSIGN, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+
+ yield return new Token(TokenType.COLON, location.OffsetBy(curCol));
break;
case '{':
- yield return new Token(TokenType.OPEN_BRACE, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.OPEN_BRACE, location.OffsetBy(curCol));
break;
case '}':
- yield return new Token(TokenType.CLOSE_BRACE, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.CLOSE_BRACE, location.OffsetBy(curCol));
break;
case '[':
- yield return new Token(TokenType.OPEN_BRACKET, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.OPEN_BRACKET, location.OffsetBy(curCol));
break;
case ']':
- yield return new Token(TokenType.CLOSE_BRACKET, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.CLOSE_BRACKET, location.OffsetBy(curCol));
break;
case '(':
- yield return new Token(TokenType.OPEN_PAREN, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.OPEN_PAREN, location.OffsetBy(curCol));
break;
case ')':
- yield return new Token(TokenType.CLOSE_PAREN, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.CLOSE_PAREN, location.OffsetBy(curCol));
break;
case '*':
- yield return new Token(TokenType.MUL_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.MUL_OP, location.OffsetBy(curCol));
break;
case '%':
- yield return new Token(TokenType.MOD_OP, fileName, lineNum, curCol + offset);
+ yield return new Token(TokenType.MOD_OP, location.OffsetBy(curCol));
break;
case ',':
- yield return new Token(TokenType.COMMA, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.COMMA, location.OffsetBy(curCol));
break;
case '/':
if (curCol + 1 < endOffs && line[curCol + 1] == '/')
@@ -141,11 +150,11 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
}
else
{
- yield return new Token(TokenType.DIV_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.DIV_OP, location.OffsetBy(curCol));
}
break;
case '+':
- yield return new Token(TokenType.ADD_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.ADD_OP, location.OffsetBy(curCol));
break;
case '-':
if (afterWhitespace && afterDirective)
@@ -154,45 +163,65 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
if (wsDelimited.Success)
{
string match = wsDelimited.Value;
- yield return new Token(TokenType.STRING, fileName, lineNum, curCol, IOUtility.UnescapePath(match));
+ yield return new Token(TokenType.STRING, location.OffsetBy(curCol), IOUtility.UnescapePath(match));
curCol += match.Length;
continue;
}
}
- yield return new Token(TokenType.SUB_OP, fileName, lineNum, curCol + offset);
+ yield return new Token(TokenType.SUB_OP, location.OffsetBy(curCol));
break;
case '&':
- yield return new Token(TokenType.AND_OP, fileName, lineNum, curCol+offset);
+ if (curCol + 1 < endOffs && line[curCol + 1] == '&')
+ {
+ yield return new Token(TokenType.LOGAND_OP, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+
+ yield return new Token(TokenType.AND_OP, location.OffsetBy(curCol));
break;
case '^':
- yield return new Token(TokenType.XOR_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.XOR_OP, location.OffsetBy(curCol));
break;
case '|':
- yield return new Token(TokenType.OR_OP, fileName, lineNum, curCol+offset);
+ if (curCol + 1 < endOffs && line[curCol + 1] == '|')
+ {
+ yield return new Token(TokenType.LOGOR_OP, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+
+ yield return new Token(TokenType.OR_OP, location.OffsetBy(curCol));
break;
case '\"':
{
curCol++;
Match quoteInterior = stringRegex.Match(line, curCol, endOffs - curCol);
string match = quoteInterior.Value;
- yield return new Token(TokenType.STRING, fileName, lineNum, curCol, /*IOUtility.UnescapeString(*/match/*)*/);
+ yield return new Token(TokenType.STRING, location.OffsetBy(curCol), /*IOUtility.UnescapeString(*/match/*)*/);
curCol += match.Length;
if (curCol == endOffs || line[curCol] != '\"')
{
- yield return new Token(TokenType.ERROR, fileName, lineNum, curCol, "Unclosed string.");
+ yield return new Token(TokenType.ERROR, location.OffsetBy(curCol), "Unclosed string.");
}
break;
}
case '<':
if (curCol + 1 < endOffs && line[curCol + 1] == '<')
{
- yield return new Token(TokenType.LSHIFT_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.LSHIFT_OP, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+ else if (curCol + 1 < endOffs && line[curCol + 1] == '=')
+ {
+ yield return new Token(TokenType.COMPARE_LE, location.OffsetBy(curCol));
curCol++;
break;
}
else
{
- yield return new Token(TokenType.ERROR, fileName, lineNum, curCol, "<");
+ yield return new Token(TokenType.COMPARE_LT, location.OffsetBy(curCol));
break;
}
case '>':
@@ -200,23 +229,68 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
{
if (curCol + 2 < endOffs && line[curCol + 2] == '>')
{
- yield return new Token(TokenType.SIGNED_RSHIFT_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.SIGNED_RSHIFT_OP, location.OffsetBy(curCol));
curCol += 2;
}
else
{
- yield return new Token(TokenType.RSHIFT_OP, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.RSHIFT_OP, location.OffsetBy(curCol));
curCol++;
}
break;
}
+ else if (curCol + 1 < endOffs && line[curCol + 1] == '=')
+ {
+ yield return new Token(TokenType.COMPARE_GE, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+ else
+ {
+ yield return new Token(TokenType.COMPARE_GT, location.OffsetBy(curCol));
+ break;
+ }
+ case '=':
+ if (curCol + 1 < endOffs && line[curCol + 1] == '=')
+ {
+ yield return new Token(TokenType.COMPARE_EQ, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+ else
+ {
+ yield return new Token(TokenType.ERROR, location.OffsetBy(curCol), "=");
+ break;
+ }
+ case '!':
+ if (curCol + 1 < endOffs && line[curCol + 1] == '=')
+ {
+ yield return new Token(TokenType.COMPARE_NE, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
else
{
- yield return new Token(TokenType.ERROR, fileName, lineNum, curCol, ">");
+ yield return new Token(TokenType.LOGNOT_OP, location.OffsetBy(curCol));
+ break;
+ }
+ case '~':
+ yield return new Token(TokenType.NOT_OP, location.OffsetBy(curCol));
+ break;
+ case '?':
+ if (curCol + 1 < endOffs && line[curCol + 1] == '?')
+ {
+ yield return new Token(TokenType.UNDEFINED_COALESCE_OP, location.OffsetBy(curCol));
+ curCol++;
+ break;
+ }
+ else
+ {
+ yield return new Token(TokenType.ERROR, location.OffsetBy(curCol), "?");
break;
}
case '\n':
- yield return new Token(TokenType.NEWLINE, fileName, lineNum, curCol+offset);
+ yield return new Token(TokenType.NEWLINE, location.OffsetBy(curCol));
break;
default:
if (afterInclude)
@@ -225,7 +299,7 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
if (winPath.Success)
{
string match = winPath.Value;
- yield return new Token(TokenType.STRING, fileName, lineNum, curCol, IOUtility.UnescapePath(match));
+ yield return new Token(TokenType.STRING, location.OffsetBy(curCol), IOUtility.UnescapePath(match));
curCol += match.Length;
afterInclude = false;
continue;
@@ -242,14 +316,14 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
int idCol = curCol;
curCol += match.Length;
if (curCol < endOffs && line[curCol] == '(')
- yield return new Token(TokenType.MAYBE_MACRO, fileName, lineNum, idCol, match);
- else
- yield return new Token(TokenType.IDENTIFIER, fileName, lineNum, idCol, match);
- if (curCol < endOffs && (Char.IsLetterOrDigit(line[curCol]) | line[curCol] == '_'))
+ yield return new Token(TokenType.MAYBE_MACRO, location.OffsetBy(idCol), match);
+ else
+ yield return new Token(TokenType.IDENTIFIER, location.OffsetBy(idCol), match);
+ if (curCol < endOffs && (char.IsLetterOrDigit(line[curCol]) | line[curCol] == '_'))
{
Match idMatch2 = new Regex("[a-zA-Z0-9_]+").Match(line, curCol, endOffs - curCol);
match = idMatch2.Value;
- yield return new Token(TokenType.ERROR, fileName, lineNum, curCol, String.Format("Identifier longer than {0} characters.", MAX_ID_LENGTH));
+ yield return new Token(TokenType.ERROR, location.OffsetBy(curCol), $"Identifier longer than {MAX_ID_LENGTH} characters.");
curCol += match.Length;
}
continue;
@@ -259,9 +333,9 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
{
string match = numMatch.Value;
//Verify that next token isn't start of an identifier
- if (curCol + match.Length >= endOffs || (!Char.IsLetter(line[curCol + match.Length]) && line[curCol + match.Length] != '_'))
+ if (curCol + match.Length >= endOffs || (!char.IsLetter(line[curCol + match.Length]) && line[curCol + match.Length] != '_'))
{
- yield return new Token(TokenType.NUMBER, fileName, lineNum, curCol, match.TrimEnd());
+ yield return new Token(TokenType.NUMBER, location.OffsetBy(curCol), match.TrimEnd());
curCol += match.Length;
continue;
}
@@ -270,7 +344,7 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
if (directiveMatch.Success)
{
string match = directiveMatch.Value;
- yield return new Token(TokenType.PREPROCESSOR_DIRECTIVE, fileName, lineNum, curCol, match);
+ yield return new Token(TokenType.PREPROCESSOR_DIRECTIVE, location.OffsetBy(curCol), match);
curCol += match.Length;
if (match.Substring(1).Equals("include") || match.Substring(1).Equals("incbin"))
{
@@ -281,7 +355,7 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
}
}
string restOfWord = new Regex("\\G\\S+").Match(line, curCol, endOffs - curCol).Value;
- yield return new Token(TokenType.ERROR, fileName, lineNum, curCol, restOfWord);
+ yield return new Token(TokenType.ERROR, location.OffsetBy(curCol), restOfWord);
curCol += restOfWord.Length;
continue;
}
@@ -290,9 +364,15 @@ public IEnumerable TokenizePhrase(string line, string fileName, int lineN
afterWhitespace = false;
}
}
- public IEnumerable TokenizeLine(string line, string fileName, int lineNum, int offset = 0)
+
+ public IEnumerable TokenizePhrase(string line, Location location)
+ {
+ return TokenizePhrase(line, 0, line.Length, location);
+ }
+
+ public static IEnumerable TokenizeLine(string line, Location location)
{
- return TokenizePhrase(line, fileName, lineNum, 0, line.Length, offset);
+ return new Tokenizer().TokenizePhrase(line, 0, line.Length, location);
}
/***
@@ -305,29 +385,35 @@ public IEnumerable Tokenize(Stream input, string fileName)
int curLine = 1;
while (!sin.EndOfStream)
{
- string line = sin.ReadLine();
-
+ string line = sin.ReadLine()!;
+
//allow escaping newlines
- while (line.Length > 0 && line.Substring(line.Length-1) == "\\")
+ while (line.Length > 0 && line[line.Length - 1] == '\\')
{
curLine++;
line = line.Substring(0, line.Length - 1) + " " + sin.ReadLine();
}
-
- foreach (Token t in TokenizeLine(line, fileName, curLine))
+
+ Location location = new Location(fileName, curLine, 1);
+ foreach (Token t in TokenizePhrase(line, location))
{
yield return t;
}
- yield return new Token(TokenType.NEWLINE, fileName, curLine, line.Length);
+ yield return new Token(TokenType.NEWLINE, location.OffsetBy(line.Length));
curLine++;
}
}
- public IEnumerable Tokenize(FileStream fs)
+ public IEnumerable TokenizeFile(FileStream fs, string filename)
{
- foreach (Token t in Tokenize(fs, fs.Name))
+ foreach (Token t in Tokenize(fs, filename))
yield return t;
fs.Close();
}
+
+ public IEnumerable Tokenize(FileStream fs)
+ {
+ return TokenizeFile(fs, fs.Name);
+ }
}
}
diff --git a/ColorzCore/Parser/AST/AtomNodeKernel.cs b/ColorzCore/Parser/AST/AtomNodeKernel.cs
index 8998bb6..deff126 100644
--- a/ColorzCore/Parser/AST/AtomNodeKernel.cs
+++ b/ColorzCore/Parser/AST/AtomNodeKernel.cs
@@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using ColorzCore.DataTypes;
+using ColorzCore.Interpreter;
using ColorzCore.Lexer;
namespace ColorzCore.Parser.AST
@@ -12,27 +11,16 @@ public abstract class AtomNodeKernel : IAtomNode
{
public abstract int Precedence { get; }
- public ParamType Type { get { return ParamType.ATOM; } }
-
- public virtual Maybe GetIdentifier()
- {
- return new Nothing