jtr/Jtr.Parsing/Lexer.Rule.cs
mdnapo 96c203f842
All checks were successful
Run the JSON parser tests / test (push) Has been skipped
Moved UI to separate project, added Dockerfile & renamed project
2024-10-19 12:04:40 +02:00

211 lines
5.8 KiB
C#

namespace Jtr.Parsing;
public abstract partial class Lexer<TToken> where TToken : Enum
{
public delegate bool Rule(Context ctx);
protected static class DefaultRule
{
private const int DefaultSpaceSize = 1;
private const int DefaultTabSize = 4;
public static bool IgnoreWhitespace(Context context, int space = DefaultSpaceSize, int tab = DefaultTabSize)
{
space = space <= 0 ? DefaultSpaceSize : space;
tab = tab <= 0 ? DefaultTabSize : tab;
var src = context.Source;
if (src.Match('\r'))
{
src.Ignore();
return true;
}
if (src.Match(' '))
{
src.Ignore();
src.Column += space;
return true;
}
if (src.Match('\t'))
{
src.Ignore();
src.Column += tab;
return true;
}
return false;
}
public static bool NewLine(Context context)
{
if (!context.Source.Match('\n')) return false;
context.Source.Line++;
context.Source.Column = 1;
context.Source.Ignore();
return true;
}
public static Rule SingleQuoteString(TToken token)
{
return context =>
{
if (!context.Source.Match('\'')) return false;
var src = context.Source;
int line = src.Line, column = src.Column;
while (src.Peek() != '\'' && !src.Ended())
{
// Account for source lines and columns when dealing with a string spanning multiple lines.
if (src.Peek() == '\n')
{
src.Line++;
src.Column = 1;
}
src.Advance();
}
if (src.Ended())
{
throw new SyntaxException(
$"[line: {src.Line}, column: {src.Column}] Unterminated string near '{src.Last}'."
);
}
// Include the closing quotation mark.
src.Advance();
var lexeme = src.Extract();
context.AddToken(
token,
lexeme.Trim('\''),
line,
column
);
return true;
};
}
public static Rule DoubleQuoteString(TToken token)
{
return context =>
{
if (!context.Source.Match('"')) return false;
var src = context.Source;
int line = src.Line, column = src.Column;
while (src.Peek() != '"' && !src.Ended())
{
// Account for source lines and columns when dealing with a string spanning multiple lines.
if (src.Peek() == '\n')
{
src.Line++;
src.Column = 0;
}
src.Advance();
}
if (src.Ended())
{
throw new SyntaxException(
$"[line: {src.Line}, column: {src.Column}] Unterminated string near '{src.Last}'."
);
}
// Include the closing quotation mark.
src.Advance();
var lexeme = src.Extract();
context.AddToken(
token,
lexeme.Trim('"'),
line,
column
);
return true;
};
}
public static Rule Identifier(TToken token)
{
return context =>
{
var src = context.Source;
if (!IsAlpha(src.Peek())) return false;
int line = src.Line, column = src.Column;
while (IsAlphaNumeric(src.Peek())) src.Advance();
var lexeme = src.Extract();
context.AddToken(token, lexeme, line, column);
return true;
};
}
// public static bool DoubleSlashComment(Context context)
// {
// var src = context.Source;
//
// if (!src.Match("//")) return false;
//
// while (!src.Ended() && src.Peek() != '\n') src.Advance();
//
// src.Ignore();
//
// return true;
// }
private static bool IsAlpha(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or '_';
private static bool IsAlphaNumeric(char c) => IsAlpha(c) || IsDigit(c);
public static Rule Number(TToken token)
{
return context =>
{
var src = context.Source;
if (!IsDigit(src.Peek())) return false;
int line = src.Line, column = src.Column;
ProcessDigits(src);
if (IsDigit(src.Peek(1)) && src.Match('.'))
{
ProcessDigits(src);
}
var lexeme = src.Extract();
context.AddToken(token, lexeme, line, column);
return true;
};
}
private static bool IsDigit(char c) => c is >= '0' and <= '9';
private static void ProcessDigits(Source src)
{
while (IsDigit(src.Peek()))
src.Advance();
}
}
}