feat(core/asm): add assembler lexer
This commit is contained in:
1 parent
77e45f92f6
commit
2ae19e0ee0
1 file changed
+243
@@ -0,0 +1,243 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Chip8.Core.Assembly;
|
||||||
|
|
||||||
|
public sealed class Lexer
|
||||||
|
{
|
||||||
|
readonly string source;
|
||||||
|
int offset;
|
||||||
|
int line = 1;
|
||||||
|
int lineStart;
|
||||||
|
|
||||||
|
public Lexer(string source)
|
||||||
|
{
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Token> tokenize()
|
||||||
|
{
|
||||||
|
var tokens = new List<Token>();
|
||||||
|
|
||||||
|
while (offset < source.Length)
|
||||||
|
{
|
||||||
|
char c = source[offset];
|
||||||
|
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
tokens.Add(makeToken(TokenKind.EndOfLine, "\\n"));
|
||||||
|
advanceLine();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\r')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ' ' || c == '\t')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ';' || (c == '/' && peek(1) == '/'))
|
||||||
|
{
|
||||||
|
skipToLineEnd();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.Add(scanToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.Add(makeToken(TokenKind.EndOfLine, "\\n"));
|
||||||
|
tokens.Add(makeToken(TokenKind.EndOfFile, ""));
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token scanToken()
|
||||||
|
{
|
||||||
|
char c = source[offset];
|
||||||
|
|
||||||
|
if (isIdentifierStart(c))
|
||||||
|
{
|
||||||
|
return scanIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (char.IsDigit(c) || c == '$' || c == '%')
|
||||||
|
{
|
||||||
|
return scanNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanPunctuation();
|
||||||
|
}
|
||||||
|
|
||||||
|
Token scanIdentifier()
|
||||||
|
{
|
||||||
|
int column = currentColumn();
|
||||||
|
int start = offset;
|
||||||
|
while (offset < source.Length && isIdentifierPart(source[offset]))
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
string text = source.Substring(start, offset - start);
|
||||||
|
return new Token(TokenKind.Identifier, text, 0, new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
Token scanNumber()
|
||||||
|
{
|
||||||
|
int column = currentColumn();
|
||||||
|
int start = offset;
|
||||||
|
char c = source[offset];
|
||||||
|
|
||||||
|
if (c == '$')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
return finishRadixNumber(start, column, 16, "hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '%')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
return finishRadixNumber(start, column, 2, "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '0' && (peek(1) == 'x' || peek(1) == 'X'))
|
||||||
|
{
|
||||||
|
offset += 2;
|
||||||
|
return finishRadixNumber(start, column, 16, "hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '0' && (peek(1) == 'b' || peek(1) == 'B'))
|
||||||
|
{
|
||||||
|
offset += 2;
|
||||||
|
return finishRadixNumber(start, column, 2, "binary");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (offset < source.Length && char.IsDigit(source[offset]))
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
string decimalText = source.Substring(start, offset - start);
|
||||||
|
int decimalValue = int.Parse(decimalText, CultureInfo.InvariantCulture);
|
||||||
|
return new Token(TokenKind.Number, decimalText, decimalValue, new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
Token finishRadixNumber(int start, int column, int radix, string label)
|
||||||
|
{
|
||||||
|
int digitsStart = offset;
|
||||||
|
while (offset < source.Length && isRadixDigit(source[offset], radix))
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset == digitsStart)
|
||||||
|
{
|
||||||
|
throw new AsmException("malformed " + label + " literal", new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
string digits = source.Substring(digitsStart, offset - digitsStart);
|
||||||
|
int value = 0;
|
||||||
|
foreach (char digit in digits)
|
||||||
|
{
|
||||||
|
value = value * radix + digitValue(digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
string text = source.Substring(start, offset - start);
|
||||||
|
return new Token(TokenKind.Number, text, value, new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
Token scanPunctuation()
|
||||||
|
{
|
||||||
|
int column = currentColumn();
|
||||||
|
char c = source[offset];
|
||||||
|
char next = peek(1);
|
||||||
|
|
||||||
|
if (c == '<' && next == '<')
|
||||||
|
{
|
||||||
|
offset += 2;
|
||||||
|
return new Token(TokenKind.ShiftLeft, "<<", 0, new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '>' && next == '>')
|
||||||
|
{
|
||||||
|
offset += 2;
|
||||||
|
return new Token(TokenKind.ShiftRight, ">>", 0, new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenKind kind = c switch
|
||||||
|
{
|
||||||
|
',' => TokenKind.Comma,
|
||||||
|
':' => TokenKind.Colon,
|
||||||
|
'-' => TokenKind.Minus,
|
||||||
|
'+' => TokenKind.Plus,
|
||||||
|
'*' => TokenKind.Star,
|
||||||
|
'/' => TokenKind.Slash,
|
||||||
|
'&' => TokenKind.Ampersand,
|
||||||
|
'|' => TokenKind.Pipe,
|
||||||
|
'^' => TokenKind.Caret,
|
||||||
|
'~' => TokenKind.Tilde,
|
||||||
|
'(' => TokenKind.LeftParen,
|
||||||
|
')' => TokenKind.RightParen,
|
||||||
|
'[' => TokenKind.LeftBracket,
|
||||||
|
']' => TokenKind.RightBracket,
|
||||||
|
_ => throw new AsmException("unexpected character '" + c + "'", new SourcePosition(line, column)),
|
||||||
|
};
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
return new Token(kind, c.ToString(), 0, new SourcePosition(line, column));
|
||||||
|
}
|
||||||
|
|
||||||
|
void skipToLineEnd()
|
||||||
|
{
|
||||||
|
while (offset < source.Length && source[offset] != '\n')
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void advanceLine()
|
||||||
|
{
|
||||||
|
offset++;
|
||||||
|
line++;
|
||||||
|
lineStart = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token makeToken(TokenKind kind, string text) =>
|
||||||
|
new(kind, text, 0, new SourcePosition(line, currentColumn()));
|
||||||
|
|
||||||
|
int currentColumn() => offset - lineStart + 1;
|
||||||
|
|
||||||
|
char peek(int ahead)
|
||||||
|
{
|
||||||
|
int index = offset + ahead;
|
||||||
|
return index < source.Length ? source[index] : '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isIdentifierStart(char c) => char.IsLetter(c) || c == '_' || c == '.';
|
||||||
|
|
||||||
|
static bool isIdentifierPart(char c) => char.IsLetterOrDigit(c) || c == '_' || c == '.';
|
||||||
|
|
||||||
|
static bool isRadixDigit(char c, int radix) => radix switch
|
||||||
|
{
|
||||||
|
2 => c == '0' || c == '1',
|
||||||
|
16 => Uri.IsHexDigit(c),
|
||||||
|
_ => char.IsDigit(c),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int digitValue(char c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
{
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if (c >= 'a' && c <= 'f')
|
||||||
|
{
|
||||||
|
return c - 'a' + 10;
|
||||||
|
}
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user