229 lines
9.9 KiB
C#
229 lines
9.9 KiB
C#
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 XNeedle.Transformation;
|
|
using Rule = XNeedle.Transformation.Rule;
|
|
using System.Data;
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
namespace XNeedle.Parser
|
|
{
|
|
public class XnParser
|
|
{
|
|
static readonly ParameterExpression CtxParam = Expression.Parameter(typeof(Context), "ctx");
|
|
|
|
static Parser<ExpressionType> 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<ExpressionType> Add = Operator("+", ExpressionType.AddChecked);
|
|
static readonly Parser<ExpressionType> Subtract = Operator("-", ExpressionType.SubtractChecked);
|
|
static readonly Parser<ExpressionType> Multiply = Operator("*", ExpressionType.MultiplyChecked);
|
|
static readonly Parser<ExpressionType> Divide = Operator("/", ExpressionType.Divide);
|
|
static readonly Parser<ExpressionType> Modulo = Operator("%", ExpressionType.Modulo);
|
|
static readonly Parser<ExpressionType> Power = Operator("^", ExpressionType.Power);
|
|
|
|
static Parser<Expression> 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<Expression> NumberConstant =
|
|
Parse.Decimal
|
|
.Select(x => Expression.Constant(double.Parse(x)))
|
|
.Named("number");
|
|
|
|
private static readonly Parser<Expression> 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<Expression> 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<Expression> Operand(Context ctx)
|
|
{
|
|
return ((from sign in Parse.Char('-')
|
|
from factor in Factor(ctx)
|
|
select Expression.Negate(factor)
|
|
).XOr(Factor(ctx))).Token();
|
|
}
|
|
static Parser<Expression> InnerTerm(Context ctx)
|
|
{
|
|
return Parse.ChainRightOperator(Power, Operand(ctx), Expression.MakeBinary);
|
|
}
|
|
|
|
static Parser<Expression> Term(Context ctx)
|
|
{
|
|
return Parse.ChainOperator(Multiply.Or(Divide).Or(Modulo), InnerTerm(ctx), Expression.MakeBinary);
|
|
}
|
|
|
|
static Parser<Expression> 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<Expression> ArithExpr(Context ctx)
|
|
{
|
|
return Parse.ChainOperator(Add.Or(Subtract), Term(ctx), Expression.MakeBinary);
|
|
}
|
|
|
|
static Parser<Expression> Expr(Context ctx)
|
|
{
|
|
return ConcatExpr(ctx).Or(ArithExpr(ctx));
|
|
}
|
|
|
|
static Parser<ExpressionPart> 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<Func<Context, string>>(stringBody, CtxParam) };
|
|
});
|
|
}
|
|
|
|
static readonly Parser<string> 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<T> Selector<T>(Parser<T> selection)
|
|
{
|
|
return from open in Parse.String("$(\"").Token()
|
|
from s in selection
|
|
from close in Parse.String("\")").Token()
|
|
select s;
|
|
}
|
|
|
|
public static readonly Parser<string> XPathSelector = Selector(XPath);
|
|
|
|
public static readonly Parser<string> 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<string> 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<string> NumberObject =
|
|
Parse.DecimalInvariant
|
|
.Select(s => double.Parse(s, CultureInfo.InvariantCulture))
|
|
.Select(v => v.ToString());
|
|
|
|
public static readonly Parser<string> Argument =
|
|
from argument in StringObject.Or(NumberObject)
|
|
select argument;
|
|
|
|
public static readonly Parser<Call> 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<Expression> ExpressionObject =
|
|
from expression in CallObject
|
|
*/
|
|
|
|
public static CommentParser Comment = new CommentParser { Single = "#", NewLine = Environment.NewLine };
|
|
|
|
public static readonly Parser<Part> BangObject =
|
|
from c1 in Comment.SingleLineComment.Optional()
|
|
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 c2 in Comment.SingleLineComment.Optional()
|
|
from expressions in Parse.Ref(() => BangObject)
|
|
.Or(Parse.Ref(() => RuleObject))
|
|
.Or(ExprPart(ctx).Select(e => (Part)e))
|
|
.DelimitedBy(Parse.Char(';').Token())
|
|
from tailingsemi in Parse.Char(';').Optional()
|
|
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<Part> RuleObject =
|
|
from c1 in Comment.SingleLineComment.Optional()
|
|
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 c2 in Comment.SingleLineComment.Optional()
|
|
from expressions in Parse.Ref(() => BangObject)
|
|
.Or(Parse.Ref(() => RuleObject))
|
|
.Or(ExprPart(ctx).Select(e => (Part)e))
|
|
.DelimitedBy(Parse.Char(';').Token())
|
|
from tailingsemi in Parse.Char(';').Optional()
|
|
from ws3 in Parse.WhiteSpace.Many().Optional()
|
|
from close in Parse.Char('}')
|
|
select new Rule{XPathSelector = selector, Body = expressions};
|
|
|
|
}
|
|
} |