using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace SteamLanguageParser { class Token(string name, string value) { public string Name { get; } = name; public string Value { get; } = value; public TokenSourceInfo? Source { get; } public Token(string name, string value, TokenSourceInfo source) : this(name, value) { Source = source; } } readonly struct TokenSourceInfo(string fileName, int startLineNumber, int startColumnNumber, int endLineNumber, int endColumnNumber) { public string FileName { get; } = fileName; public int StartLineNumber { get; } = startLineNumber; public int StartColumnNumber { get; } = startColumnNumber; public int EndLineNumber { get; } = endLineNumber; public int EndColumnNumber { get; } = endColumnNumber; } partial class LanguageParser { [GeneratedRegex( @"(?\s+)|" + @"(?[;])|" + "[\"](?.+?)[\"]|" + @"\/\/(?.*)$|" + @"(?-?[a-zA-Z_0-9][a-zA-Z0-9_:.]*)|" + @"[#](?[a-zA-Z]*)|" + @"(?[{}<>\]=|])|" + @"(?[^\s]+)", RegexOptions.Multiline)] private static partial Regex RegexPattern(); public static Queue TokenizeString(string buffer, string fileName = "") { var regexPattern = RegexPattern(); var bufferLines = buffer.Split(new[] { Environment.NewLine }, StringSplitOptions.None); MatchCollection matches = regexPattern.Matches(buffer); Queue tokenList = new Queue(); foreach (Match match in matches.Cast()) { int i = 0; foreach (Group group in match.Groups.Cast()) { string matchValue = group.Value; bool success = group.Success; if (success && i > 1) { string groupName = regexPattern.GroupNameFromNumber(i); if (groupName == "comment") { continue; // don't create tokens for comments } CalculateTextOffset(bufferLines, match.Index, out var startLineNumber, out var startColumnNumber); CalculateTextOffset(bufferLines, match.Index + match.Length, out var endLineNumber, out var endColumnNumber); var tokenSource = new TokenSourceInfo(fileName, startLineNumber, startColumnNumber, endLineNumber, endColumnNumber); var token = new Token(groupName, matchValue, tokenSource); tokenList.Enqueue(token); } i++; } } return tokenList; } static void CalculateTextOffset(string[] textLines, int index, out int lineNumber, out int columnNumber) { int offset = 0; for (lineNumber = 0; lineNumber < textLines.Length; lineNumber++) { var currentLineLength = textLines[lineNumber].Length; if (offset + currentLineLength >= index) { break; } offset += currentLineLength + Environment.NewLine.Length; } ArgumentOutOfRangeException.ThrowIfEqual(lineNumber, textLines.Length); lineNumber++; // Human line numbering starts from 1, even though it's the 0th line in the file programatically. columnNumber = index - offset + 1; } } }