Code
·
109 lines
·
4374 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System.Text;
using Lexer = System.Func<InteractiveReadLine.LineState, InteractiveReadLine.Tokenizing.TokenizedLine>;
using RegexList = System.Collections.Generic.List<InteractiveReadLine.Tokenizing.RegexTokenDef>;
namespace InteractiveReadLine.Tokenizing
{
public static class CommonLexers
{
public static RegexList Regex => new();
/// <summary>
/// Adds a new token type to the end of the regular expression token list. The token must begin with
/// the regular expression start of text character (^) or an ArgumentException will be thrown
/// </summary>
/// <param name="list"></param>
/// <param name="pattern">The regular expression pattern which identifies the token, must start with ^</param>
/// <param name="typeCode">An optional type code, such that any token matched by this definition will have
/// this value in the .TypeCode property of the IToken interface</param>
/// <returns></returns>
public static RegexList AddTokenType(this RegexList list, string pattern, int typeCode = 0)
{
if (!pattern.StartsWith("^"))
throw new ArgumentException($"Regex token definitions must start with the ^ character, which was not present in the pattern '{pattern}'");
list.Add(new RegexTokenDef(pattern, typeCode));
return list;
}
public static RegexList AddAnyNonWhitespace(this RegexList list, int typeCode = 0) =>
list.AddTokenType(@"^\S+", typeCode);
public static RegexList AddDoubleQuoteStringLiterals(this RegexList list, int typeCode = 0) =>
list.AddTokenType(@"^""(?:[^""\\]|\\.)*""", typeCode);
public static RegexList AddSingleQuoteStringLiterals(this RegexList list, int typeCode = 0) =>
list.AddTokenType(@"^'(?:[^'\\]|\\.)*'", typeCode);
/// <summary>
/// From a list of RegexTokenDefs, produce a function that will tokenize a LineState according to
/// the specified token definitions in the order they were provided. Effectively this renders a list
/// of regular expression token definitions into a functioning lexer.
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
public static Lexer ToLexer(this RegexList list)
{
if (list.Count == 0)
throw new ArgumentException("The RegexTokenDefs list must not be empty");
return lineState =>
{
var tokenized = new TokenizedLine();
var ignored = new StringBuilder();
for (int i = 0; i < lineState.Text.Length; i++)
{
var substring = lineState.Text.Substring(i);
bool hasMatch = false;
foreach (var tokenDef in list)
{
var match = tokenDef.Regex.Match(substring);
if (match.Success)
{
if (ignored.Length > 0)
{
tokenized.Add(ignored.ToString(), true, null);
ignored.Clear();
}
tokenized.Add(match.Value, false, null, tokenDef.TypeCode);
i += match.Value.Length - 1;
hasMatch = true;
break;
}
}
if (!hasMatch)
{
ignored.Append(lineState.Text[i]);
}
}
if (ignored.Length > 0)
{
tokenized.Add(ignored.ToString(), true);
ignored.Clear();
}
tokenized.Cursor = lineState.Cursor;
if (!tokenized.Any() ||
(tokenized.Last.IsHidden && tokenized.Last.Cursor == tokenized.Last.Text.Length))
{
tokenized.Add(string.Empty, false, 0);
}
return tokenized;
};
}
public static Lexer SplitOnWhitespace => Regex.AddAnyNonWhitespace().ToLexer();
}
}