using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Sprache; using System.Globalization; using XNeedle.Parser.Elements; using Part = XNeedle.Parser.Elements.Part; using NullPart = XNeedle.Parser.Elements.NullPart; using XNeedle.Transformation; using Rule = XNeedle.Transformation.Rule; using System.Data; using System.ComponentModel.DataAnnotations; using System.Text; namespace XNeedle.Parser { public class XnParser { static readonly ParameterExpression CtxParam = Expression.Parameter(typeof(Context), "ctx"); static Parser Operator(string op, ExpressionType opType) { return Parse.String(op).Token().Return(opType); } //static MethodInfo ConcatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); static readonly Parser Add = Operator("+", ExpressionType.AddChecked); static readonly Parser Subtract = Operator("-", ExpressionType.SubtractChecked); static readonly Parser Multiply = Operator("*", ExpressionType.MultiplyChecked); static readonly Parser Divide = Operator("/", ExpressionType.Divide); static readonly Parser Modulo = Operator("%", ExpressionType.Modulo); static readonly Parser Power = Operator("^", ExpressionType.Power); static Parser Function(Context ctx) { return from name in Parse.Letter.AtLeastOnce().Text() from lparen in Parse.Char('(') from expr in Parse.Ref(() => Expr(ctx)).DelimitedBy(Parse.Char(',').Token()).Optional() from rparen in Parse.Char(')') select CallFunction(ctx, name, expr.GetOrElse(new Expression[0]).ToArray()); } static Expression CallFunction(Context ctx, string name, Expression[] parameters) { var mathMethodInfo = typeof(Math).GetTypeInfo().GetMethod(name, parameters.Select(e => e.Type).ToArray()); if (mathMethodInfo != null) { return Expression.Call(mathMethodInfo, parameters); } var ctxMethodInfo = typeof(ContextFunctions).GetTypeInfo().GetMethod(name, parameters.Select(e => e.Type).Prepend(typeof(Context)).ToArray()); if (ctxMethodInfo != null) { return Expression.Call(ctxMethodInfo, parameters.Prepend(CtxParam).ToArray()); } throw new ParseException(string.Format("Function '{0}({1})' does not exist.", name, string.Join(",", parameters.Select(e => e.Type.Name)))); } static readonly Parser NumberConstant = Parse.Decimal .Select(x => Expression.Constant(double.Parse(x))) .Named("number"); private static readonly Parser StringConstant = from open in Parse.Char('"') from value in Parse.CharExcept('"').Many().Text() from close in Parse.Char('"') select Expression.Constant(new string(value)); static Parser Factor(Context ctx) { return (from lparen in Parse.Char('(') from expr in Parse.Ref(() => Expr(ctx)) from rparen in Parse.Char(')') select expr).Named("expression") .XOr(NumberConstant) .XOr(StringConstant) .XOr(Function(ctx)); } static Parser Operand(Context ctx) { return ((from sign in Parse.Char('-') from factor in Factor(ctx) select Expression.Negate(factor) ).XOr(Factor(ctx))).Token(); } static Parser InnerTerm(Context ctx) { return Parse.ChainRightOperator(Power, Operand(ctx), Expression.MakeBinary); } static Parser Term(Context ctx) { return Parse.ChainOperator(Multiply.Or(Divide).Or(Modulo), InnerTerm(ctx), Expression.MakeBinary); } static Parser ConcatExpr(Context ctx) { var concatMethod = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }); return from left in StringConstant.Or(Function(ctx)) from op in Parse.String("..").Token() from right in StringConstant.Or(Function(ctx)) select Expression.Add(left, right, concatMethod); } static Parser ArithExpr(Context ctx) { return Parse.ChainOperator(Add.Or(Subtract), Term(ctx), Expression.MakeBinary); } static Parser Expr(Context ctx) { return ConcatExpr(ctx).Or(ArithExpr(ctx)); } static Parser ExprPart(Context ctx) { return Expr(ctx).Select(body => { Expression stringBody = body.Type == typeof(string) ? body : Expression.Call(body, typeof(object).GetMethod("ToString", Type.EmptyTypes)!); return new ExpressionPart { Expr = Expression.Lambda>(stringBody, CtxParam) }; }); } static readonly Parser XPath = from path in Parse.LetterOrDigit .XOr(Parse.Char('/')) .XOr(Parse.Char(' ')) .XOr(Parse.Char('[')) .XOr(Parse.Char(']')) .XOr(Parse.Char('=')) .XOr(Parse.Char('.')) .XOr(Parse.Char('_')) .XOr(Parse.Char('*')) .XOr(Parse.Char('@')) .XOr(Parse.Char('#')) .XOr(Parse.Char('(')) .XOr(Parse.Char(')')) .XOr(Parse.Char('\'')) .Many() select new string(path.ToArray()); static Parser Selector(Parser selection) { return from open in Parse.String("$(\"").Token() from s in selection from close in Parse.String("\")").Token() select s; } public static readonly Parser XPathSelector = Selector(XPath); public static readonly Parser Identifier = from first in Parse.Letter.Once() from rest in Parse.LetterOrDigit.XOr(Parse.Char('-')).XOr(Parse.Char('_')).Many() select new string(first.Concat(rest).ToArray()); private static readonly Parser StringObject = from open in Parse.Char('"') from value in Parse.CharExcept('"').Many().Text() from close in Parse.Char('"') select new string(value); private static readonly Parser NumberObject = Parse.DecimalInvariant .Select(s => double.Parse(s, CultureInfo.InvariantCulture)) .Select(v => v.ToString()); public static readonly Parser Argument = from argument in StringObject.Or(NumberObject) select argument; public static readonly Parser CallObject = from identifier in Identifier from open in Parse.Char('(') from arguments in Argument.DelimitedBy(Parse.Char(',').Token()) from close in Parse.Char(')') select new Call{Identifier = new string(identifier.ToArray()), Arguments = arguments}; /*public static readonly Parser ExpressionObject = from expression in CallObject */ public static CommentParser Comment = new CommentParser { Single = "#", NewLine = Environment.NewLine }; public static readonly Parser BangObject = from bang in Parse.Char('!') from name in Parse.AnyChar.Until(Parse.Char('(')) from arguments in Argument.DelimitedBy(Parse.Char(',').Token()) from bclose in Parse.Char(')') let ctx = new Context{XPathSelector = "//*."} from ws1 in Parse.WhiteSpace.Many().Optional() from open in Parse.Char('{') from ws2 in Parse.WhiteSpace.Many().Optional() from expressions in ( from expr in Parse.Ref(() => BangObject) .Or(Parse.Ref(() => RuleObject)) .Or(ExprPart(ctx).Select(e => (Part)e)) from semi in Parse.Char(';').Token() select expr ).Many() from ws3 in Parse.WhiteSpace.Many().Optional() from close in Parse.Char('}') select new Bang{Name = new string([.. name]), Arguments = arguments, Body = expressions}; public static readonly Parser RuleObject = from selector in Selector(XPath).Token() let ctx = new Context{XPathSelector = selector} from ws1 in Parse.WhiteSpace.Many().Optional() from open in Parse.Char('{') from ws2 in Parse.WhiteSpace.Many().Optional() from expressions in ( from expr in Parse.Ref(() => BangObject) .Or(Parse.Ref(() => RuleObject)) .Or(ExprPart(ctx).Select(e => (Part)e)) from semi in Parse.Char(';').Token() select expr ).Many() from ws3 in Parse.WhiteSpace.Many().Optional() from close in Parse.Char('}') select new Rule{XPathSelector = selector, Body = expressions}; public static readonly Parser> Rules = (from rule in RuleObject.Token() from semi in Parse.Char(';').Token() select rule) .Many() .End(); static public string StripComments(string source) { var result = new StringBuilder(); foreach (var line in source.Split('\n')) { bool inSingle = false, inDouble = false; int commentIdx = -1; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (c == '\'' && !inDouble) inSingle = !inSingle; else if (c == '"' && !inSingle) inDouble = !inDouble; else if (c == '#' && !inSingle && !inDouble) { commentIdx = i; break; } } result.Append(commentIdx >= 0 ? line[..commentIdx] : line); result.Append('\n'); } return result.ToString(); } } }