I have posted this question 2 days ago and got amazing feedback on it.
Hands back to the keyboard, I believe I took the suggestions into account and improved the classes drastically. In this question, would love further criticism and suggestions in regard to my code logic and coding style.
The TokenFactory
and TokenParser
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace ShirLanguageCompiler
{
public class TokenParser
{
private Regex Pattern;
private SyntaxKind Type;
public bool CanParse(TokenFactory factory) => Pattern.IsMatch(factory.UpcomingCode);
public Token Parse(TokenFactory factory)
{
Match match = Pattern.Match(factory.UpcomingCode);
return new Token(factory.Position, match.Length, factory.Location, Type, match.Value);
}
public TokenParser(Regex pattern, SyntaxKind type)
{
this.Pattern = pattern;
this.Type = type;
}
}
public class TokenFactory
{
private int Length { get; }
private string Code { get; set; }
public int Position { get; set; }
public (int Line, int Column) Location;
public string UpcomingCode => this.Code.Substring(Position);
TokenParser[] Parsers = {
new TokenParser(new Regex(@"^""(?:\\""|[^""])*""", RegexOptions.Compiled),SyntaxKind.LiteralStringToken ),
new TokenParser(new Regex(@"^'(?:\\'|[^'])+'", RegexOptions.Compiled),SyntaxKind.LiteralCharToken ),
new TokenParser(new Regex(@"^d\d+", RegexOptions.Compiled),SyntaxKind.LiteralNumberToken ),
new TokenParser(new Regex(@"^false", RegexOptions.Compiled),SyntaxKind.LiteralFalseKeyword ),
new TokenParser(new Regex(@"^true", RegexOptions.Compiled),SyntaxKind.LiteralTrueKeyword ),
new TokenParser(new Regex(@"^:", RegexOptions.Compiled),SyntaxKind.ColonToken ),
new TokenParser(new Regex(@"^;", RegexOptions.Compiled),SyntaxKind.SemiColonToken ),
new TokenParser(new Regex(@"^\s", RegexOptions.Compiled),SyntaxKind.WhitespaceToken ),
new TokenParser(new Regex(@"^[\r\n]+", RegexOptions.Compiled),SyntaxKind.EOLToken ),
new TokenParser(new Regex(@"^,", RegexOptions.Compiled),SyntaxKind.CommaToken ),
new TokenParser(new Regex(@"^->", RegexOptions.Compiled),SyntaxKind.AccessorToken ),
new TokenParser(new Regex(@"^=>", RegexOptions.Compiled),SyntaxKind.AssignmentToken ),
new TokenParser(new Regex(@"^\(", RegexOptions.Compiled),SyntaxKind.OpenParenthesisToken ),
new TokenParser(new Regex(@"^\)", RegexOptions.Compiled),SyntaxKind.CloseParenthesisToken ),
new TokenParser(new Regex(@"^\{", RegexOptions.Compiled),SyntaxKind.OpenCurlyBracketToken ),
new TokenParser(new Regex(@"^\)}", RegexOptions.Compiled),SyntaxKind.CloseCurlyBracketToken ),
new TokenParser(new Regex(@"^\[", RegexOptions.Compiled),SyntaxKind.OpenSquareBracketToken ),
new TokenParser(new Regex(@"^\]", RegexOptions.Compiled),SyntaxKind.CloseSquareBracketToken ),
new TokenParser(new Regex(@"^\?", RegexOptions.Compiled),SyntaxKind.QuestionMarkToken ),
new TokenParser(new Regex(@"^\+", RegexOptions.Compiled),SyntaxKind.PlusOperationToken ),
new TokenParser(new Regex(@"^\-", RegexOptions.Compiled),SyntaxKind.MinusOperationToken ),
new TokenParser(new Regex(@"^\*}", RegexOptions.Compiled),SyntaxKind.MultiplyOperationToken ),
new TokenParser(new Regex(@"^\*\*", RegexOptions.Compiled),SyntaxKind.PowerOperationToken ),
new TokenParser(new Regex(@"^\/", RegexOptions.Compiled),SyntaxKind.DivideOperationToken ),
new TokenParser(new Regex(@"^\/\/", RegexOptions.Compiled),SyntaxKind.RootOperationToken ),
new TokenParser(new Regex(@"^==", RegexOptions.Compiled),SyntaxKind.EqualToken ),
new TokenParser(new Regex(@"^!=", RegexOptions.Compiled),SyntaxKind.InEqualToken ),
new TokenParser(new Regex(@"^letter", RegexOptions.Compiled),SyntaxKind.LetterKeyword ),
new TokenParser(new Regex(@"^number", RegexOptions.Compiled),SyntaxKind.NumberKeyword ),
new TokenParser(new Regex(@"^boolean", RegexOptions.Compiled),SyntaxKind.BooleanKeyword ),
new TokenParser(new Regex(@"^str", RegexOptions.Compiled),SyntaxKind.StringKeyword ),
new TokenParser(new Regex(@"^bind", RegexOptions.Compiled),SyntaxKind.BindKeyword ),
new TokenParser(new Regex(@"^return", RegexOptions.Compiled),SyntaxKind.ReturnKeyword ),
new TokenParser(new Regex(@"^ref", RegexOptions.Compiled),SyntaxKind.RefKeyword ),
new TokenParser(new Regex(@"^val", RegexOptions.Compiled),SyntaxKind.ValKeyword ),
new TokenParser(new Regex(@"^\D\w+\(\)", RegexOptions.Compiled),SyntaxKind.FunctionNameToken ),
new TokenParser(new Regex(@"^\D\w+", RegexOptions.Compiled),SyntaxKind.VariableNameToken )
};
public TokenFactory(string code)
{
this.Code = code;
this.Length = code.Length;
this.Position = 0;
this.Location = (0, 0);
}
public IEnumerable<Token> GetTokenStream()
{
while(this.Position < this.Length)
{
TokenParser Parser = Parsers.First(n => n.CanParse(this));
Token Parsed = Parser.Parse(this);
if (Parsed.Type == SyntaxKind.EOLToken)
this.Location = (this.Location.Line++,0);
else
this.Location.Column += Parsed.Length;
this.Position += Parsed.Length;
#if DEBUG
Console.Write(Parsed);
#endif
yield return Parsed;
}
}
}
}
And here the Token
class:
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace ShirLanguageCompiler
{
public enum SyntaxKind
{
PlusOperationToken,
MinusOperationToken,
DivideOperationToken,
MultiplyOperationToken,
PowerOperationToken,
RootOperationToken,
EqualToken,
InEqualToken,
VariableNameToken,
FunctionNameToken,
NumberKeyword,
BooleanKeyword,
LetterKeyword,
StringKeyword,
BindKeyword,
ReturnKeyword,
RefKeyword,
ValKeyword,
LiteralTrueKeyword,
LiteralFalseKeyword,
LiteralNumberToken,
LiteralCharToken,
LiteralStringToken,
EOLToken,
QuoteToken,
ColonToken,
SemiColonToken,
CommaToken,
QuestionMarkToken,
WhitespaceToken,
AssignmentToken,
AccessorToken,
OpenParenthesisToken,
CloseParenthesisToken,
OpenCurlyBracketToken,
CloseCurlyBracketToken,
OpenSquareBracketToken,
CloseSquareBracketToken
}
public interface IContainsLocation
{
(int Line, int Column) Location { get; set; }
string LocationString { get; }
}
[DebuggerDisplay("Value = {EscapedString} Start = {Start}, Length = {Length}, Location = {LocationString}")]
public class Token: IContainsLocation
{
public int Start, Length;
public (int Line, int Column) Location { get; set; }
public SyntaxKind Type { get; private set; }
public string InnerValue { get; private set; }
public string LocationString
{
get
{
return $"(Line:{this.Location.Line},Collumn:{this.Location.Column})";
}
}
public string EscapedString
{
get
{
return Regex.Escape(InnerValue);
}
}
public Token(int start, int length, (int Line, int Column) Location, SyntaxKind type, string innervalue)
{
this.Start = start;
this.Length = length;
this.Type = type;
this.InnerValue = innervalue;
this.Location = Location;
}
public override string ToString() => this.InnerValue;
}
}