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 "";
}
}
}
}