using System; using System.Reflection; using System.Text.RegularExpressions; using System.Windows.Media; namespace RuriLib.LS { /// /// The allowed types of tokens that can be parsed from a line. /// public enum TokenType { /// A block label. Label, /// A generic parameter, usually an enum value. Parameter, /// A string between double quotes. Literal, /// The character sequence ->. Arrow, /// A boolean value in the format Name=Value where Name is the name of the property. Boolean, /// An integer value. Integer } /// /// Contains methods used to parse tokens from a LoliScript line of code. /// public static class LineParser { /// /// Parses a token of a given type from a line. /// /// The reference to the line of code /// The type of token to parse /// Whether an exception should be thrown if the token cannot be parsed /// Whether to remove the token from the original line after parsing it /// The parsed token or, if parse fails but the token is not essential, an empty string public static string ParseToken(ref string input, TokenType type, bool essential, bool proceed = true) { var pattern = GetPattern(type); var token = ""; Regex r = new Regex(pattern); Match m = r.Match(input); if (m.Success) { token = m.Value; if (proceed) input = input.Substring(token.Length).Trim(); if (type == TokenType.Literal) token = token.Substring(1, token.Length - 2).Replace("\\\\", "\\").Replace("\\\"", "\""); } else { if (essential) { throw new ArgumentException("Cannot parse token"); } } return token; } /// /// Sets a boolean property by parsing its name and value from a line. /// /// The reference to the line of code /// The instance of the object containing the boolean property public static void SetBool(ref string input, object instance) { var result = ParseToken(ref input, TokenType.Parameter, true, true).Split('='); PropertyInfo prop = null; try { prop = instance.GetType().GetProperty(result[0]); } catch { throw new ArgumentException($"There is no property called {result[0]} in the type {instance.GetType().ToString()}"); } var propVal = prop.GetValue(instance); if (propVal.GetType() != typeof(bool)) throw new InvalidCastException($"The property {result[0]} is not a boolean"); switch (result[1].ToUpper()) { case "TRUE": prop.SetValue(instance, true); break; case "FALSE": prop.SetValue(instance, false); break; default: throw new ArgumentException($"Expected bool value for '{prop.Name}'"); } } /// /// Parses an enum value from a line. /// /// The reference to the line of code /// Debug information about the expected enum /// The type of the enum /// An enum of the provided enumType public static dynamic ParseEnum(ref string input, string label, Type enumType) { // Parse the token first var token = ""; try { token = ParseToken(ref input, TokenType.Parameter, true); } catch { throw new ArgumentException($"Missing '{label}'"); } try { return Enum.Parse(enumType, token, true); } catch { throw new ArgumentException($"Invalid '{label}'"); } } /// /// Parses a literal value from a line. /// /// The reference to the line of code /// Debug information about the expected literal /// Whether to perform variable replacement in the literal /// The BotData needed for variable replacement /// The literal without the leading and trailing double quotes public static string ParseLiteral(ref string input, string label, bool replace = false, BotData data = null) { try { if (replace) return BlockBase.ReplaceValues(ParseToken(ref input, TokenType.Literal, true), data); else return ParseToken(ref input, TokenType.Literal, true); } catch { throw new ArgumentException($"Expected Literal value for '{label}'"); } } /// /// Parses an integer value from a line. /// /// The reference to the line of code /// Debug information about the expected integer /// The integer value public static int ParseInt(ref string input, string label) { try { return int.Parse(LineParser.ParseToken(ref input, TokenType.Parameter, true)); } catch { throw new ArgumentException($"Expected Integer value for '{label}'"); } } /// /// Parses a block label from a line. /// /// The reference to the line of code /// The label of the block, if defined public static string ParseLabel(ref string input) { return ParseToken(ref input, TokenType.Label, false).Substring(1); } /// /// Makes sure that a specified identifier is present and moves past it. An exception will be thrown if the identifier is not present. /// /// The reference to the line of code /// The expected identifier public static void EnsureIdentifier(ref string input, string id) { var token = ParseToken(ref input, TokenType.Parameter, true); if (token.ToUpper() != id.ToUpper()) throw new ArgumentException($"Expected identifier '{id}'"); } /// /// Gets the type of the first token in the given line of code. /// /// The reference to the line of code /// The type of the token public static TokenType Lookahead(ref string input) { var i = 0; var token = ParseToken(ref input, TokenType.Parameter, true, false); if (token.Contains("\"")) return TokenType.Literal; else if (token == "->") return TokenType.Arrow; else if (token.StartsWith("#")) return TokenType.Label; else if (token.ToUpper().Contains("=TRUE") || token.ToUpper().Contains("=FALSE")) return TokenType.Boolean; else if (int.TryParse(token, out i)) return TokenType.Integer; else return TokenType.Parameter; } /// /// Checks if the next token is a given identifier. /// /// The reference to the line of code /// The identifier to check /// Whether the token and the identifier are equal public static bool CheckIdentifier(ref string input, string id) { try { var token = ParseToken(ref input, TokenType.Parameter, true, false); return token.ToUpper() == id.ToUpper(); } catch { return false; } } /// /// Gets the regex pattern to match a given token type. /// /// The token type /// The regex pattern to parse the token type private static string GetPattern(TokenType type) { switch (type) { case TokenType.Label: return "^#[^ ]*"; case TokenType.Parameter: return "^[^ ]*"; case TokenType.Literal: //return "\\\"(\\\\.|[^\\\"])*\\\""; return "\"(\\\\.|[^\\\"])*\""; case TokenType.Arrow: return "^->"; default: return ""; } } } }