IFPSTools.NET/IFPSAsmLib/Parser.cs
zc b85cd7770a lib: allow record element names to be specified
asm: allow record element names to be specified
asm: fix some errors being thrown by the wrong parser element
asm: add light obfuscation, when enabled all exported functions will be renamed to "A"
2022-12-09 16:01:32 +00:00

1004 lines
46 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
namespace IFPSAsmLib
{
internal enum ParserSeparatorType : byte
{
StartBody,
NextBody,
EndBody,
NextOrEndBody,
NextOrStartBody,
Unknown
}
internal static class ParserExtensions
{
private static bool Matches(this char input, ParserSeparatorType type)
{
switch (type)
{
case ParserSeparatorType.StartBody:
return input == Constants.START_BODY;
case ParserSeparatorType.NextBody:
return input == Constants.NEXT_BODY;
case ParserSeparatorType.EndBody:
return input == Constants.END_BODY;
case ParserSeparatorType.NextOrEndBody:
return input == Constants.NEXT_BODY || input == Constants.END_BODY;
case ParserSeparatorType.NextOrStartBody:
return input == Constants.NEXT_BODY || input == Constants.START_BODY;
default:
return false;
}
}
private static ParserSeparatorType GetSeparator(this char input)
{
if (input == Constants.START_BODY) return ParserSeparatorType.StartBody;
if (input == Constants.NEXT_BODY) return ParserSeparatorType.NextBody;
if (input == Constants.END_BODY) return ParserSeparatorType.EndBody;
return ParserSeparatorType.Unknown;
}
private static void ThrowEof(ParserLocation location)
{
throw new InvalidDataException(string.Format("Unexpected end of file [:{0},{1}]", location.Line, location.Column));
}
internal static string ToLiteral(this string input)
{
StringBuilder literal = new StringBuilder(input.Length + 2);
literal.Append(Constants.STRING_CHAR);
foreach (var c in input)
{
switch (c)
{
case '\"': literal.Append("\\\""); break;
case '\\': literal.Append(@"\\"); break;
case '\0': literal.Append(@"\0"); break;
case '\a': literal.Append(@"\a"); break;
case '\b': literal.Append(@"\b"); break;
case '\f': literal.Append(@"\f"); break;
case '\n': literal.Append(@"\n"); break;
case '\r': literal.Append(@"\r"); break;
case '\t': literal.Append(@"\t"); break;
case '\v': literal.Append(@"\v"); break;
default:
// ASCII printable character
if ((c >= 0x20 && c <= 0x7e) || !char.IsControl(c))
{
literal.Append(c);
// As UTF16 escaped character
}
else if (c < 0x100)
{
literal.Append(@"\x");
literal.Append(((int)c).ToString("x2"));
}
else
{
literal.Append(@"\u");
literal.Append(((int)c).ToString("x4"));
}
break;
}
}
literal.Append(Constants.STRING_CHAR);
return literal.ToString();
}
internal static bool AdvanceToNextLine(this ReadOnlySpan<char> str, ref ParserLocation location)
{
while (str[location.Offset] != '\n')
{
location.AdvanceOffset();
if (location.Offset >= str.Length) return false;
}
location.AdvanceOffset();
location = location.AdvanceLine();
return true;
}
internal static bool AdvanceWhiteSpace(this ReadOnlySpan<char> str, ref ParserLocation location, bool disallowNewLine)
{
if (location.Offset >= str.Length) return false;
while (char.IsWhiteSpace(str[location.Offset]) || str[location.Offset] == Constants.COMMENT_START)
{
var offset = location.Offset;
if (disallowNewLine && (str[offset] == '\r' || str[offset] == '\n' || str[offset] == Constants.COMMENT_START)) {
throw new InvalidDataException(string.Format("Unexpected new line [:{0},{1}]", location.Line, location.Column));
}
if (!disallowNewLine && str[offset] == Constants.COMMENT_START) break;
location.AdvanceOffset();
if (str[offset] == '\n')
{
location = location.AdvanceLine();
}
if (location.Offset >= str.Length)
{
if (!disallowNewLine) return false;
ThrowEof(location);
}
}
return true;
}
internal static bool AdvanceWhiteSpaceUntilNewLine(this ReadOnlySpan<char> str, ref ParserLocation location)
{
while (char.IsWhiteSpace(str[location.Offset]) || str[location.Offset] == Constants.COMMENT_START)
{
var offset = location.Offset;
if (str[offset] == Constants.COMMENT_START) return true;
location.AdvanceOffset();
if (str[offset] == '\r' || str[offset] == '\n')
{
if (str[offset] == '\n')
{
location = location.AdvanceLine();
}
return true;
}
if (location.Offset >= str.Length)
ThrowEof(location);
}
return false;
}
private static bool IsValidNibble(this char input)
{
return
(input >= '0' && input <= '9') ||
(input >= 'a' && input <= 'f') ||
(input >= 'A' && input <= 'F')
;
}
internal static ParserEntity GetEntity(this ReadOnlySpan<char> str, ref ParserLocation location, ParserSeparatorType? separatorType = null, bool optional = true)
{
var origLoc = location.Clone();
var start = location.Offset;
var length = 0;
bool inQuotes = false;
byte escapingCount = 0;
byte escapingState = 0;
for (var chr = str[location.Offset]; inQuotes || !char.IsWhiteSpace(chr); location.AdvanceOffset(), length++)
{
if (location.Offset >= str.Length) ThrowEof(location);
chr = str[location.Offset];
// allow for escaped data in quotes
if (inQuotes)
{
if (escapingState == 1)
{
switch (chr)
{
case '\"':
case '\\':
case '0':
case 'a':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
escapingCount = escapingState = 0;
continue;
case 'x': // 2 hexits
escapingCount += 2;
break;
case 'u': // 4 hexits
escapingCount += 4;
break;
default:
throw new InvalidDataException(string.Format("Invalid escape character '{0}' [:{1},{2}]", chr, location.Line, location.Column));
}
escapingState++;
}
else if (escapingState == (escapingCount - 1))
{
escapingCount = escapingState = 0;
}
else if (escapingState == 0)
{
if (chr == '\\')
{
escapingState = escapingCount = 1;
}
else if (chr == '\"')
{
inQuotes = false;
}
}
else
{
if (!chr.IsValidNibble())
throw new InvalidDataException(string.Format("Invalid hex digit '{0}' [:{1},{2}]", chr, location.Line, location.Column));
escapingState++;
}
continue;
} else if (chr == '\"')
{
inQuotes = true;
continue;
}
if (!separatorType.HasValue) continue;
if (chr.Matches(separatorType.Value)) break;
}
if (char.IsWhiteSpace(str[start + length - 1])) length--;
// Allow for whitespace before the separator, do not allow cr or lf.
var found = ParserSeparatorType.Unknown;
if (separatorType.HasValue)
{
for (var chr = str[location.Offset]; char.IsWhiteSpace(chr) && chr != '\r' && chr != '\n'; location.AdvanceOffset())
{
// no operation
}
// Separator must be at this point, if not optional.
found = str[location.Offset].GetSeparator();
var matches = str[location.Offset].Matches(separatorType.Value);
if (!optional && !matches)
{
throw new InvalidDataException(string.Format("After {0}: expected '{1}', got '{2}' {3}", str.Slice(start, length).ToString(), separatorType.Value, found, location));
}
if (matches) location.AdvanceOffset();
}
return new ParserEntity(str.Slice(start, length), origLoc, found);
}
internal static void EnsureBodyEmpty(this ReadOnlySpan<char> str, ref ParserLocation location)
{
if (location.Offset >= str.Length) ThrowEof(location);
if (str[location.Offset] != Constants.END_BODY) throw new InvalidDataException(string.Format("Expected empty declaration body {0}", location));
location.AdvanceOffset();
}
}
internal class ParserLocation
{
internal int Offset;
internal int Line;
internal int Column;
internal ParserLocation Clone()
{
return new ParserLocation()
{
Offset = Offset,
Line = Line,
Column = Column
};
}
internal ParserLocation AdvanceLine()
{
return new ParserLocation()
{
Offset = Offset,
Line = Line + 1,
Column = 0
};
}
internal void AdvanceOffset()
{
Offset++;
Column++;
}
public override string ToString()
{
return string.Format("[:{0},{1}]", Line, Column);
}
internal string ToStringWithOffset(int offset)
{
return string.Format("[:{0},{1}]", Line, Column + offset);
}
}
internal readonly ref struct ParserEntity
{
internal readonly ReadOnlySpan<char> Value;
internal readonly ParserLocation Location;
internal readonly ParserSeparatorType? SeparatorType;
internal bool FoundSeparator => SeparatorType.HasValue;
internal ParserEntity(ReadOnlySpan<char> val, ParserLocation loc, ParserSeparatorType type)
{
Value = val;
Location = loc;
SeparatorType = null;
if (type != ParserSeparatorType.Unknown) SeparatorType = type;
}
public override string ToString()
{
return Value.ToString();
}
internal string ToStringForError()
{
return string.Format("{0} {1}", Value.ToString().ToLiteral(), Location);
}
internal string ToStringForError(int offset)
{
return string.Format("{0} {1}", Value.ToString().ToLiteral(), Location.ToStringWithOffset(offset));
}
internal void ThrowInvalid()
{
throw new InvalidDataException(string.Format("Invalid token {0}", ToStringForError()));
}
internal bool Equals(string str)
{
if (Value.Length != str.Length) return false;
for (int i = 0; i < Value.Length; i++)
{
if (Value[i] != str[i]) return false;
}
return true;
}
internal void ExpectToken(string token)
{
if (!Equals(token)) ThrowInvalid();
}
internal void ExpectValidName()
{
var indexOf = Value.IndexOfAny(Constants.STRING_CHAR, Constants.START_BODY, Constants.END_BODY);
if (indexOf == -1) return;
ThrowInvalid();
}
internal void ExpectString()
{
if (Value.Length < 2) ThrowInvalid();
if (Value[0] != Constants.STRING_CHAR) ThrowInvalid();
if (Value[Value.Length - 1] != Constants.STRING_CHAR) ThrowInvalid();
}
internal void ExpectSeparatorType(ParserSeparatorType type)
{
var actualType = SeparatorType.HasValue ? SeparatorType.Value : ParserSeparatorType.Unknown;
if (type == ParserSeparatorType.NextOrEndBody)
{
if (actualType == ParserSeparatorType.NextBody || actualType == ParserSeparatorType.EndBody) type = actualType;
}
if (type == ParserSeparatorType.NextOrStartBody)
{
if (actualType == ParserSeparatorType.NextBody || actualType == ParserSeparatorType.StartBody) type = actualType;
}
if (type != actualType)
throw new InvalidDataException(string.Format("After {0}: expected '{1}', got '{2}' {3}", Value.ToString(), type, actualType, Location));
}
}
public enum ElementParentType : byte
{
Unknown,
Obfuscate,
FileVersion,
EntryPoint,
Attribute,
Type,
GlobalVariable,
Function,
Alias,
Define,
Instruction,
Label,
}
public class ParserElement
{
/// <summary>
/// Value of this element
/// </summary>
public readonly string Value;
/// <summary>
/// The next element on this line; null if not present.
/// </summary>
public ParserElement Next { get; internal set; } = null;
/// <summary>
/// The next child element (elements inside brackets); null if not present.
/// </summary>
public ParserElement NextChild { get; internal set; } = null;
/// <summary>
/// Line number of this element
/// </summary>
public readonly int Line;
/// <summary>
/// Column index of this element
/// </summary>
public readonly int Column;
/// <summary>
/// Parent element type
/// </summary>
public ElementParentType ParentType;
internal ParserElement(ParserEntity entity, ElementParentType type)
{
Value = entity.ToString();
Line = entity.Location.Line;
Column = entity.Location.Column;
ParentType = type;
}
public IEnumerable<ParserElement> Children
{
get
{
for (var next = NextChild; next != null; next = next.NextChild) yield return next;
}
}
public IEnumerable<ParserElement> Tokens
{
get
{
yield return this;
for (var next = Next; next != null; next = next.Next) yield return next;
}
}
internal string ToStringForError()
{
return string.Format("{0} [:{1},{2}]", Value.ToLiteral(), Line, Column);
}
internal void ThrowInvalid()
{
ThrowInvalid("Invalid token");
}
internal void ThrowInvalid(string error)
{
throw new InvalidDataException(string.Format("{0} {1}", error, ToStringForError()));
}
internal void ExpectValidName()
{
var indexOf = Value.AsSpan().IndexOfAny(Constants.STRING_CHAR, Constants.START_BODY, Constants.END_BODY);
if (indexOf == -1) return;
ThrowInvalid();
}
internal void ExpectString()
{
if (Value.Length < 2) ThrowInvalid();
if (Value[0] != Constants.STRING_CHAR) ThrowInvalid();
if (Value[Value.Length - 1] != Constants.STRING_CHAR) ThrowInvalid();
}
public override string ToString()
{
return Value;
}
}
public class ParsedBody
{
/// <summary>
/// The main element; type, function, global, or entry point.
/// </summary>
public readonly ParserElement Element;
/// <summary>
/// Children instruction elements of a function element.
/// </summary>
public readonly IReadOnlyList<ParserElement> Children;
private static IReadOnlyList<ParserElement> s_EmptyList = new List<ParserElement>(0).AsReadOnly();
internal ParsedBody(ParserElement elem) : this(elem, s_EmptyList)
{
}
internal ParsedBody(ParserElement elem, List<ParserElement> list) : this(elem, list == null ? s_EmptyList : list.AsReadOnly())
{
}
private ParsedBody(ParserElement elem, IReadOnlyList<ParserElement> list)
{
Element = elem;
Children = list;
}
}
public static class Parser
{
private static void ParseTokenWithUnknownBody(
ReadOnlySpan<char> str,
ref ParserLocation location,
ElementParentType parentType,
ParserElement current,
ref ParserElement next,
bool baseType
)
{
ParserElement currentChild, nextChild;
str.AdvanceWhiteSpace(ref location, true);
var typeNext = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
typeNext.ExpectValidName();
current.Next = next = new ParserElement(typeNext, parentType);
if (baseType && parentType == ElementParentType.Type && next.Value == Constants.TYPE_FUNCTION_POINTER)
{
ParseTokenWithUnknownBody(str, ref location, parentType, next, ref next, false);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
typeNext.ExpectToken(string.Empty);
return;
}
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, true);
currentChild = nextChild = next.NextChild = new ParserElement(typeNext, parentType);
while (!typeNext.FoundSeparator || typeNext.SeparatorType.Value != ParserSeparatorType.EndBody)
{
bool nextIsChild = typeNext.FoundSeparator;
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, true);
if (nextIsChild)
{
currentChild.NextChild = new ParserElement(typeNext, parentType);
currentChild = nextChild = currentChild.NextChild;
}
else
{
nextChild.Next = new ParserElement(typeNext, parentType);
nextChild = nextChild.Next;
}
}
}
internal static ParserElement Parse(this ReadOnlySpan<char> str, ref ParserLocation location)
{
ParserElement current = null, next = null, nextChild = null;
var parentType = default(ElementParentType);
while (true)
{
if (!str.AdvanceWhiteSpace(ref location, false)) return null;
var chr = str[location.Offset];
// comment
if (chr == Constants.COMMENT_START)
{
if (!str.AdvanceToNextLine(ref location)) return null;
continue;
}
// element
if (chr == Constants.ELEMENT_START)
{
var type = str.GetEntity(ref location, ParserSeparatorType.StartBody, true);
var typeString = type.ToString();
switch (typeString)
{
case Constants.ELEMENT_OBFUSCATE:
// .obfuscate
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.Obfuscate;
current = new ParserElement(type, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) current.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_VERSION:
// .version int
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.FileVersion;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
var typeNext = str.GetEntity(ref location);
if (!int.TryParse(typeNext.Value.ToString(), out var __int))
typeNext.ThrowInvalid();
current.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) current.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_ENTRYPOINT:
// .entry function_name
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.EntryPoint;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
current.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) current.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_TYPE:
// .type [(export)] type(...) name
parentType = ElementParentType.Type;
current = new ParserElement(type, parentType);
if (type.FoundSeparator)
{
str.AdvanceWhiteSpace(ref location, true);
var typeChild = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!typeChild.Value.IsEmpty)
{
if (!typeChild.Equals(Constants.ELEMENT_BODY_IMPORTED))
typeChild.ExpectToken(Constants.ELEMENT_BODY_EXPORTED);
current.NextChild = new ParserElement(typeChild, parentType);
}
}
// type(...), will be looked at later
ParseTokenWithUnknownBody(str, ref location, parentType, current, ref next, true);
// name
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_ALIAS:
// .alias varname additionalname
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.Alias;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
current.Next = next = new ParserElement(typeNext, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_DEFINE:
// .define immediate_operand defname
if (type.FoundSeparator) str.EnsureBodyEmpty(ref location);
parentType = ElementParentType.Define;
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
typeNext.ExpectValidName();
current.Next = next = new ParserElement(typeNext, parentType);
// operand is an immediate value: type(data)
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
next.NextChild = new ParserElement(typeNext, parentType);
// name
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.ThrowInvalid();
return current;
case Constants.ELEMENT_GLOBALVARIABLE:
// .global [(export)] typename varname
parentType = ElementParentType.GlobalVariable;
current = next = new ParserElement(type, parentType);
if (type.FoundSeparator)
{
str.AdvanceWhiteSpace(ref location, true);
var typeChild = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!typeChild.Value.IsEmpty)
{
if (!typeChild.Equals(Constants.ELEMENT_BODY_IMPORTED))
typeChild.ExpectToken(Constants.ELEMENT_BODY_EXPORTED);
current.NextChild = new ParserElement(typeChild, parentType);
}
}
// typename
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
next = next.Next;
// varname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
next.Next = new ParserElement(typeNext, parentType);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.Next.ThrowInvalid();
return current;
case Constants.ELEMENT_FUNCTION:
// function
// .function [(export)] external callingconv internal|com(vtblindex)|class(...)|dll(...) returnsval|void name (...)
// .function [(export)] void|typename name(...)
parentType = ElementParentType.Function;
current = next = new ParserElement(type, parentType);
if (type.FoundSeparator)
{
str.AdvanceWhiteSpace(ref location, true);
var typeChild = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!typeChild.Value.IsEmpty)
{
if (!typeChild.Equals(Constants.ELEMENT_BODY_IMPORTED))
typeChild.ExpectToken(Constants.ELEMENT_BODY_EXPORTED);
current.NextChild = new ParserElement(typeChild, parentType);
}
}
// external|void|typename
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location);
typeNext.ExpectValidName();
current.Next = new ParserElement(typeNext, parentType);
next = next.Next;
str.AdvanceWhiteSpace(ref location, true);
if (next.Value == Constants.FUNCTION_EXTERNAL)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.StartBody, true);
typeNext.ExpectValidName();
if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_INTERNAL))
{
typeNext.ExpectSeparatorType(ParserSeparatorType.Unknown);
next.Next = next = new ParserElement(typeNext, parentType);
}
else
{
if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_COM))
{
// com(vtblindex)
typeNext.ExpectSeparatorType(ParserSeparatorType.StartBody);
next.Next = next = new ParserElement(typeNext, parentType);
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
if (!int.TryParse(typeNext.Value.ToString(), out __int))
typeNext.ThrowInvalid();
next.NextChild = new ParserElement(typeNext, parentType);
}
else if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_CLASS))
{
// class(classname,funcname[,property])
typeNext.ExpectSeparatorType(ParserSeparatorType.StartBody);
next.Next = next = new ParserElement(typeNext, parentType);
// classname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextBody, false);
typeNext.ExpectValidName();
next.NextChild = nextChild = new ParserElement(typeNext, parentType);
// funcname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
typeNext.ExpectValidName();
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
// property
if (typeNext.SeparatorType == ParserSeparatorType.NextBody)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
typeNext.ExpectToken(Constants.FUNCTION_EXTERNAL_CLASS_PROPERTY);
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
}
}
else if (typeNext.Equals(Constants.FUNCTION_EXTERNAL_DLL))
{
// dll(dllname,procname[,delayload][,alteredsearchpath])
typeNext.ExpectSeparatorType(ParserSeparatorType.StartBody);
next.Next = next = new ParserElement(typeNext, parentType);
// dllname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextBody, false);
typeNext.ExpectString();
next.NextChild = nextChild = new ParserElement(typeNext, parentType);
// procname
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
typeNext.ExpectString();
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
bool firstWasDelayLoad = false;
// delayload|alteredsearchpath
if (typeNext.SeparatorType == ParserSeparatorType.NextBody)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
if (!typeNext.Equals(Constants.FUNCTION_EXTERNAL_DLL_DELAYLOAD))
{
if (!typeNext.Equals(Constants.FUNCTION_EXTERNAL_DLL_ALTEREDSEARCHPATH)) typeNext.ThrowInvalid();
}
else firstWasDelayLoad = true;
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
}
// other delayload|alteredsearchpath
if (typeNext.SeparatorType == ParserSeparatorType.NextBody)
{
str.AdvanceWhiteSpace(ref location, true);
typeNext = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
typeNext.ExpectToken(
firstWasDelayLoad ?
Constants.FUNCTION_EXTERNAL_DLL_ALTEREDSEARCHPATH :
Constants.FUNCTION_EXTERNAL_DLL_DELAYLOAD
);
nextChild.NextChild = nextChild = new ParserElement(typeNext, parentType);
}
}
else
typeNext.ThrowInvalid();
str.AdvanceWhiteSpace(ref location, true);
// calling convention
typeNext = str.GetEntity(ref location);
if (
!typeNext.Equals(Constants.FUNCTION_FASTCALL) &&
!typeNext.Equals(Constants.FUNCTION_PASCAL) &&
!typeNext.Equals(Constants.FUNCTION_CDECL) &&
!typeNext.Equals(Constants.FUNCTION_STDCALL)
)
typeNext.ThrowInvalid();
next.Next = next = new ParserElement(typeNext, parentType);
}
str.AdvanceWhiteSpace(ref location, true);
// returnsval|void
typeNext = str.GetEntity(ref location);
if (!typeNext.Equals(Constants.FUNCTION_RETURN_VAL) && !typeNext.Equals(Constants.FUNCTION_RETURN_VOID)) typeNext.ThrowInvalid();
next.Next = next = new ParserElement(typeNext, parentType);
}
// name(...), will be looked at later
ParseTokenWithUnknownBody(str, ref location, parentType, next, ref next, false);
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) next.ThrowInvalid();
return current;
default:
type.ThrowInvalid();
break;
}
}
// attribute
if (chr == Constants.START_ATTRIBUTE)
{
parentType = ElementParentType.Attribute;
location.AdvanceOffset();
str.AdvanceWhiteSpace(ref location, true);
// name(operands)
var type = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
type.ExpectValidName();
current = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
// operands
next = current;
if (str[location.Offset] != Constants.END_BODY)
{
do
{
// must be immediate value: type(data)
type = str.GetEntity(ref location, ParserSeparatorType.StartBody, false);
type.ExpectValidName();
next.Next = next = new ParserElement(type, parentType);
str.AdvanceWhiteSpace(ref location, true);
type = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
next.NextChild = new ParserElement(type, parentType);
// next token may be a comma or end, let's find out
str.AdvanceWhiteSpace(ref location, true);
// expecting a blank token
type = str.GetEntity(ref location, ParserSeparatorType.NextOrEndBody, false);
type.ExpectToken(string.Empty);
} while (type.SeparatorType == ParserSeparatorType.NextBody);
}
else location.AdvanceOffset();
str.AdvanceWhiteSpace(ref location, true);
if (str[location.Offset] != Constants.END_ATTRIBUTE)
{
type.ThrowInvalid();
}
location.AdvanceOffset();
if (!str.AdvanceWhiteSpaceUntilNewLine(ref location)) type.ThrowInvalid();
return current;
}
// instruction or label
parentType = ElementParentType.Label;
var data = str.GetEntity(ref location);
data.ExpectValidName();
if (data.Value[data.Value.Length - 1] == Constants.LABEL_SPECIFIER) // label
{
data = new ParserEntity(
data.Value.Slice(0, data.Value.Length - 1),
data.Location,
data.SeparatorType.HasValue ? data.SeparatorType.Value : ParserSeparatorType.Unknown
);
var label = new ParserElement(data, parentType);
if (label.Value == Constants.LABEL_NULL) label.ThrowInvalid();
return label;
}
// instruction
parentType = ElementParentType.Instruction;
// opcode ...
current = new ParserElement(data, parentType);
if (str.AdvanceWhiteSpaceUntilNewLine(ref location)) return current;
// operands
next = current;
do
{
data = str.GetEntity(ref location, ParserSeparatorType.NextOrStartBody, true);
data.ExpectValidName();
next.Next = next = new ParserElement(data, parentType);
if (data.FoundSeparator && data.SeparatorType == ParserSeparatorType.StartBody)
{
// operand is an immediate value: type(data)
str.AdvanceWhiteSpace(ref location, true);
data = str.GetEntity(ref location, ParserSeparatorType.EndBody, false);
next.NextChild = new ParserElement(data, parentType);
// next token may be a comma or newline, let's find out
if (str.AdvanceWhiteSpaceUntilNewLine(ref location)) break;
// expecting a blank token with NextBody
data = str.GetEntity(ref location, ParserSeparatorType.NextBody, false);
data.ExpectToken(string.Empty);
}
} while (!str.AdvanceWhiteSpaceUntilNewLine(ref location));
return current;
}
}
public static List<ParsedBody> Parse(ReadOnlySpan<char> str)
{
var location = new ParserLocation();
var ret = new List<ParsedBody>();
ParserElement last = null;
List<ParserElement> list = null;
List<ParserElement> attributes = null;
do
{
var elem = str.Parse(ref location);
if (elem == null)
{
if (last != null)
{
ret.Add(new ParsedBody(last, list));
}
break;
}
switch (elem.ParentType)
{
case ElementParentType.Unknown:
elem.ThrowInvalid();
break;
case ElementParentType.Attribute:
if (attributes == null) attributes = new List<ParserElement>();
attributes.Add(elem);
break;
case ElementParentType.Instruction:
case ElementParentType.Label:
case ElementParentType.Alias:
if (attributes != null) elem.ThrowInvalid();
if (list == null) list = new List<ParserElement>();
if (last == null) elem.ThrowInvalid();
list.Add(elem);
break;
case ElementParentType.Function:
if (last != null)
{
ret.Add(new ParsedBody(last, list));
list = null;
}
list = attributes;
attributes = null;
last = elem;
break;
default:
if (last != null)
{
ret.Add(new ParsedBody(last, list));
last = null;
list = null;
}
if (elem.ParentType != ElementParentType.Type && attributes != null) elem.ThrowInvalid();
ret.Add(new ParsedBody(elem, attributes));
attributes = null;
break;
}
} while (true);
return ret;
}
public static List<ParsedBody> Parse(string str) => Parse(str.AsSpan());
}
}