feat(octoasm): add Octo lexer
This commit is contained in:
1 parent
d3cf619571
commit
da135491c8
1 file changed
+192
@@ -0,0 +1,192 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Chip8.OctoAsm;
|
||||||
|
|
||||||
|
public sealed class OctoLexer
|
||||||
|
{
|
||||||
|
readonly string source;
|
||||||
|
int offset;
|
||||||
|
int line = 1;
|
||||||
|
int lineStart;
|
||||||
|
|
||||||
|
public OctoLexer(string source)
|
||||||
|
{
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<OctoToken> tokenize(string source) => new OctoLexer(source).scan();
|
||||||
|
|
||||||
|
public List<OctoToken> scan()
|
||||||
|
{
|
||||||
|
var tokens = new List<OctoToken>();
|
||||||
|
|
||||||
|
while (offset < source.Length)
|
||||||
|
{
|
||||||
|
char c = source[offset];
|
||||||
|
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
line++;
|
||||||
|
lineStart = offset;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\r' || c == ' ' || c == '\t')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '#')
|
||||||
|
{
|
||||||
|
skipComment();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBracket(c))
|
||||||
|
{
|
||||||
|
tokens.Add(makeBracket(c));
|
||||||
|
offset++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.Add(scanWord());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.Add(new OctoToken(OctoTokenKind.EndOfFile, "", 0, position()));
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
OctoToken scanWord()
|
||||||
|
{
|
||||||
|
SourcePosition start = position();
|
||||||
|
int begin = offset;
|
||||||
|
|
||||||
|
while (offset < source.Length && !isDelimiter(source[offset]))
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
string text = source.Substring(begin, offset - begin);
|
||||||
|
if (tryParseNumber(text, out int value))
|
||||||
|
{
|
||||||
|
return new OctoToken(OctoTokenKind.Number, text, value, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OctoToken(OctoTokenKind.Word, text, 0, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
OctoToken makeBracket(char c)
|
||||||
|
{
|
||||||
|
OctoTokenKind kind = c switch
|
||||||
|
{
|
||||||
|
'{' => OctoTokenKind.OpenBrace,
|
||||||
|
'}' => OctoTokenKind.CloseBrace,
|
||||||
|
'(' => OctoTokenKind.OpenParen,
|
||||||
|
_ => OctoTokenKind.CloseParen,
|
||||||
|
};
|
||||||
|
return new OctoToken(kind, c.ToString(), 0, position());
|
||||||
|
}
|
||||||
|
|
||||||
|
void skipComment()
|
||||||
|
{
|
||||||
|
while (offset < source.Length && source[offset] != '\n')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SourcePosition position() => new(line, offset - lineStart + 1);
|
||||||
|
|
||||||
|
static bool isBracket(char c) => c == '{' || c == '}' || c == '(' || c == ')';
|
||||||
|
|
||||||
|
static bool isDelimiter(char c) =>
|
||||||
|
c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#' || isBracket(c);
|
||||||
|
|
||||||
|
public static bool tryParseNumber(string text, out int value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
if (text.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
bool negative = false;
|
||||||
|
if (text[0] == '-')
|
||||||
|
{
|
||||||
|
negative = true;
|
||||||
|
index = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= text.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
long magnitude;
|
||||||
|
if (hasPrefix(text, index, "0x") || hasPrefix(text, index, "0X"))
|
||||||
|
{
|
||||||
|
ok = parseRadix(text, index + 2, 16, out magnitude);
|
||||||
|
}
|
||||||
|
else if (hasPrefix(text, index, "0b") || hasPrefix(text, index, "0B"))
|
||||||
|
{
|
||||||
|
ok = parseRadix(text, index + 2, 2, out magnitude);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ok = parseRadix(text, index, 10, out magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long signed = negative ? -magnitude : magnitude;
|
||||||
|
value = (int)signed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasPrefix(string text, int index, string prefix) =>
|
||||||
|
index + prefix.Length <= text.Length && text.Substring(index, prefix.Length) == prefix;
|
||||||
|
|
||||||
|
static bool parseRadix(string text, int start, int radix, out long value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
if (start >= text.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = start; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
int digit = digitValue(text[i]);
|
||||||
|
if (digit < 0 || digit >= radix)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = value * radix + digit;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int digitValue(char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
{
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if (c >= 'a' && c <= 'f')
|
||||||
|
{
|
||||||
|
return c - 'a' + 10;
|
||||||
|
}
|
||||||
|
if (c >= 'A' && c <= 'F')
|
||||||
|
{
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user