blob: 53923cce4cbc59a53e86affe4ce7b1d1447637b5 [file] [log] [blame]
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
using J2N;
using J2N.Text;
using Lucene.Net.Queries.Function;
using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Expressions.JS
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <summary>An expression compiler for javascript expressions.</summary>
/// <remarks>
/// An expression compiler for javascript expressions.
/// <para/>
/// Example:
/// <code>
/// Expression foo = JavascriptCompiler.Compile("((0.3*popularity)/10.0)+(0.7*score)");
/// </code>
/// <para/>
/// See the <see cref="Lucene.Net.Expressions.JS">package documentation</see> for
/// the supported syntax and default functions.
/// <para>
/// You can compile with an alternate set of functions via <see cref="Compile(string, IDictionary{string, MethodInfo})"/>.
/// For example:
/// <code>
/// // instantiate and add all the default functions
/// IDictionary&lt;string, MethodInfo&gt; functions = new Dictionary&lt;string, MethodInfo&gt;(JavascriptCompiler.DEFAULT_FUNCTIONS);
/// // add sqrt()
/// functions["sqrt"] = (typeof(Math)).GetMethod("Sqrt", new Type[] { typeof(double) });
/// // call compile with customized function map
/// Expression foo = JavascriptCompiler.Compile("sqrt(score)+ln(popularity)", functions);
/// </code>
/// </para>
/// @lucene.experimental
/// </remarks>
public class JavascriptCompiler
{
private static readonly string COMPILED_EXPRESSION_CLASS = typeof(Expression).Namespace + ".CompiledExpression";
//private static readonly string COMPILED_EXPRESSION_INTERNAL = COMPILED_EXPRESSION_CLASS.Replace('.', '/'); // LUCENENET: Not used
private static readonly Type EXPRESSION_TYPE = Type.GetType(typeof(Expression).FullName);
private static readonly Type FUNCTION_VALUES_TYPE = typeof(FunctionValues);
private static readonly ConstructorInfo EXPRESSION_CTOR = typeof(Expression).
GetConstructor(new Type[] { typeof(string), typeof(string[]) });
private static readonly MethodInfo EVALUATE_METHOD = GetMethod(EXPRESSION_TYPE, "Evaluate",
new[] { typeof(int), typeof(FunctionValues[]) });
private static readonly MethodInfo DOUBLE_VAL_METHOD = GetMethod(FUNCTION_VALUES_TYPE, "DoubleVal",
new[] { typeof(int) });
// We use the same class name for all generated classes as they all have their own class loader.
// The source code is displayed as "source file name" in stack trace.
// to work around import clash:
private static MethodInfo GetMethod(Type type, string method, Type[] parms)
{
return type.GetMethod(method, parms);
}
private readonly string sourceText;
// LUCENENET specific: OrderedDictioary<TKey, TValue> is a replacement for LinkedHashMap<K, V> in the JDK
private readonly IDictionary<string, int> externalsMap = new JCG.OrderedDictionary<string, int>();
private TypeBuilder dynamicType;
private readonly IDictionary<string, MethodInfo> functions;
private ILGenerator gen;
private AssemblyBuilder asmBuilder;
private MethodBuilder evalMethod;
private ModuleBuilder modBuilder;
// This maximum length is theoretically 65535 bytes, but as its CESU-8 encoded we dont know how large it is in bytes, so be safe
// rcmuir: "If your ranking function is that large you need to check yourself into a mental institution!"
/// <summary>Compiles the given expression.</summary>
/// <param name="sourceText">The expression to compile</param>
/// <returns>A new compiled expression</returns>
/// <exception cref="ParseException">on failure to compile</exception>
// LUCENENET TODO: ParseException not being thrown here - need to check
// where this is thrown in Java and throw the equivalent in .NET
public static Expression Compile(string sourceText)
{
return new JavascriptCompiler(sourceText).CompileExpression();
}
/// <summary>Compiles the given expression with the supplied custom functions.</summary>
/// <remarks>
/// Compiles the given expression with the supplied custom functions.
/// <para/>
/// Functions must be <c>public static</c>, return <see cref="double"/> and
/// can take from zero to 256 <see cref="double"/> parameters.
/// </remarks>
/// <param name="sourceText">The expression to compile</param>
/// <param name="functions">map of <see cref="string"/> names to functions</param>
/// <returns>A new compiled expression</returns>
/// <exception cref="ParseException">on failure to compile</exception>
public static Expression Compile(string sourceText, IDictionary<string, MethodInfo> functions)
{
foreach (MethodInfo m in functions.Values)
{
CheckFunction(m);
}
return new JavascriptCompiler(sourceText, functions).CompileExpression();
}
/// <summary>This method is unused, it is just here to make sure that the function signatures don't change.</summary>
/// <remarks>
/// This method is unused, it is just here to make sure that the function signatures don't change.
/// If this method fails to compile, you also have to change the byte code generator to correctly
/// use the <see cref="FunctionValues"/> class.
/// </remarks>
#pragma warning disable IDE0051 // Remove unused private members
private static void UnusedTestCompile()
#pragma warning restore IDE0051 // Remove unused private members
{
FunctionValues f = null;
/*double ret = */
f.DoubleVal(2); // LUCENENET: IDE0059: Remove unnecessary value assignment
}
/// <summary>Constructs a compiler for expressions.</summary>
/// <param name="sourceText">The expression to compile</param>
private JavascriptCompiler(string sourceText)
: this(sourceText, DEFAULT_FUNCTIONS)
{
}
/// <summary>Constructs a compiler for expressions with specific set of functions</summary>
/// <param name="sourceText">The expression to compile</param>
/// <param name="functions">The set of functions to compile with</param>
private JavascriptCompiler(string sourceText, IDictionary<string, MethodInfo> functions)
{
this.sourceText = sourceText ?? throw new ArgumentNullException(nameof(sourceText)); // LUCENENET specific - changed from IllegalArgumentException to ArgumentNullException (.NET convention)
this.functions = functions;
}
/// <summary>Compiles the given expression with the specified parent classloader</summary>
/// <returns>A new compiled expression</returns>
/// <exception cref="ParseException">on failure to compile</exception>
private Expression CompileExpression()
{
try
{
var antlrTree = GetAntlrComputedExpressionTree();
BeginCompile();
RecursiveCompile(antlrTree);
EndCompile();
return
(Expression)
Activator.CreateInstance(dynamicType.CreateTypeInfo().AsType(), sourceText, externalsMap.Keys.ToArray());
}
catch (Exception exception) when (exception.IsInstantiationException() || exception.IsIllegalAccessException() ||
exception.IsNoSuchMethodException() || exception.IsInvocationTargetException())
{
throw IllegalStateException.Create("An internal error occurred attempting to compile the expression (" + sourceText + ").", exception);
}
}
private void BeginCompile()
{
var assemblyName = new AssemblyName("Lucene.Net.Expressions.Dynamic" + Math.Abs(new J2N.Randomizer().Next()));
asmBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect);
modBuilder = asmBuilder.DefineDynamicModule(assemblyName.Name + ".dll");
dynamicType = modBuilder.DefineType(COMPILED_EXPRESSION_CLASS,
TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Public | TypeAttributes.Class |
TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout, EXPRESSION_TYPE);
ConstructorBuilder constructorBuilder = dynamicType.DefineConstructor(MethodAttributes.Public,
CallingConventions.HasThis,
new[] { typeof(string), typeof(string[]) });
ILGenerator ctorGen = constructorBuilder.GetILGenerator();
ctorGen.Emit(OpCodes.Ldarg_0);
ctorGen.Emit(OpCodes.Ldarg_1);
ctorGen.Emit(OpCodes.Ldarg_2);
ctorGen.Emit(OpCodes.Call, EXPRESSION_CTOR);
ctorGen.Emit(OpCodes.Nop);
ctorGen.Emit(OpCodes.Nop);
ctorGen.Emit(OpCodes.Ret);
evalMethod = dynamicType.DefineMethod("Evaluate", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(double), new[] { typeof(int), typeof(FunctionValues[]) });
gen = evalMethod.GetILGenerator();
}
private void RecursiveCompile(JavascriptParser.ExpressionContext context)
{
var listener = new JavascriptListener(this);
listener.EnterExpression(context);
}
// LUCENENET-specific: these methods use the ANTLRv4 listener API to
// be equivalent to what RecursiveCompile was doing previously
private class JavascriptListener : JavascriptBaseListener
{
private readonly JavascriptCompiler compiler;
public JavascriptListener(JavascriptCompiler compiler)
{
this.compiler = compiler;
}
public override void EnterExpression(JavascriptParser.ExpressionContext context)
{
EnterConditional(context.conditional());
}
public override void EnterCall(JavascriptParser.CallContext context)
{
ITerminalNode identifier = context.NAMESPACE_ID();
string call = identifier.GetText();
// LUCENENET: logic changed to get arguments from parse context
var arguments = context.arguments().conditional();
int argumentCount = arguments.Length;
if (!compiler.functions.TryGetValue(call, out MethodInfo method) || method is null)
{
throw new ArgumentException("Unrecognized method call (" + call + ").");
}
int arity = method.GetParameters().Length;
if (argumentCount != arity)
{
throw new ArgumentException("Expected (" + arity + ") arguments for method call ("
+ call + "), but found (" + argumentCount + ").");
}
for (int argument = 0; argument < argumentCount; ++argument) // LUCENENET: was 1 to and including arguments
{
EnterConditional(arguments[argument]);
}
compiler.Emit(OpCodes.Call, method);
}
public override void EnterPrimary(JavascriptParser.PrimaryContext context)
{
if (context.NAMESPACE_ID() is { } namespaceId)
{
string text = namespaceId.GetText();
if (!compiler.externalsMap.TryGetValue(text, out int index))
{
compiler.externalsMap[text] = index = compiler.externalsMap.Count;
}
compiler.Emit(OpCodes.Nop);
compiler.Emit(OpCodes.Ldarg_2);
compiler.Emit(OpCodes.Ldc_I4, index);
compiler.Emit(OpCodes.Ldelem_Ref);
compiler.Emit(OpCodes.Ldarg_1);
compiler.Emit(OpCodes.Callvirt, DOUBLE_VAL_METHOD);
}
else if (context.numeric() is { } numeric)
{
EnterNumeric(numeric);
}
else if (context.conditional() is { } conditional)
{
EnterConditional(conditional);
}
else
{
throw new ParseException("Unknown primary alternative", context.Start.StartIndex);
}
}
public override void EnterNumeric(JavascriptParser.NumericContext context)
{
string text = context.GetText();
if (context.HEX() is not null)
{
compiler.PushInt64(Convert.ToInt64(text, 16));
}
else if (context.OCTAL() is not null)
{
compiler.PushInt64(Convert.ToInt64(text, 8));
}
else
{
compiler.Emit(OpCodes.Ldc_R8, double.Parse(text, CultureInfo.InvariantCulture));
}
}
public override void EnterPostfix(JavascriptParser.PostfixContext context)
{
if (context.primary() is { } primary)
{
EnterPrimary(primary);
}
else if (context.call() is { } call)
{
EnterCall(call);
}
else
{
throw new ParseException("Unknown postfix alternative", context.Start.StartIndex);
}
}
public override void EnterUnary(JavascriptParser.UnaryContext context)
{
if (context.postfix() is { } postfix)
{
EnterPostfix(postfix);
}
else if (context.AT_ADD() is not null)
{
EnterUnary(context.unary());
compiler.Emit(OpCodes.Conv_R8);
}
else if (context.unary_operator() is { } unaryOperator)
{
EnterUnary(context.unary());
if (unaryOperator.AT_SUBTRACT() is not null)
{
compiler.Emit(OpCodes.Neg);
}
else if (unaryOperator.AT_BIT_NOT() is not null)
{
compiler.Emit(OpCodes.Conv_I8); // cast to long (truncate)
compiler.Emit(OpCodes.Not);
compiler.Emit(OpCodes.Conv_R8);
}
else if (unaryOperator.AT_BOOL_NOT() is not null)
{
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Ceq);
compiler.Emit(OpCodes.Conv_R8);
}
else
{
throw new ParseException("Unknown unary_operator alternative", context.Start.StartIndex);
}
}
else
{
throw new ParseException("Unknown unary alternative", context.Start.StartIndex);
}
}
public override void EnterAdditive(JavascriptParser.AdditiveContext context)
{
CompileBinary(context, context.multiplicative, EnterMultiplicative, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_ADD)
{
compiler.PushOpWithConvert(OpCodes.Add);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_SUBTRACT)
{
compiler.PushOpWithConvert(OpCodes.Sub);
}
else
{
throw new ParseException("Unknown additive token", context.Start.StartIndex);
}
});
}
public override void EnterMultiplicative(JavascriptParser.MultiplicativeContext context)
{
CompileBinary(context, context.unary, EnterUnary, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_MULTIPLY)
{
compiler.PushOpWithConvert(OpCodes.Mul);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_DIVIDE)
{
compiler.PushOpWithConvert(OpCodes.Div);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_MODULO)
{
compiler.PushOpWithConvert(OpCodes.Rem);
}
else
{
throw new ParseException("Unknown multiplicative token", context.Start.StartIndex);
}
});
}
public override void EnterShift(JavascriptParser.ShiftContext context)
{
EnterAdditive(context.additive(0));
if (context.children.Count == 1) // if we don't have a shift token
{
return;
}
compiler.Emit(OpCodes.Conv_I8); // cast to long (truncate)
int argIndex = 1;
for (int i = 1; i < context.children.Count; i += 2)
{
if (context.children[i] is ITerminalNode terminalNode)
{
EnterAdditive(context.additive(argIndex++));
compiler.Emit(OpCodes.Conv_I4); // cast to int (truncate)
// mask off 63 to prevent overflow (fixes issue on x86 .NET Framework, #1034)
compiler.Emit(OpCodes.Ldc_I4, 0x3F);
compiler.Emit(OpCodes.And);
if (terminalNode.Symbol.Type == JavascriptParser.AT_BIT_SHL)
{
compiler.PushOpWithConvert(OpCodes.Shl);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_BIT_SHR)
{
compiler.PushOpWithConvert(OpCodes.Shr);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_BIT_SHU)
{
compiler.PushOpWithConvert(OpCodes.Shr_Un);
}
else
{
throw new ParseException("Unknown shift token", context.Start.StartIndex);
}
}
else
{
throw new ParseException("Unexpected child", context.Start.StartIndex);
}
}
}
public override void EnterRelational(JavascriptParser.RelationalContext context)
{
CompileBinary(context, context.shift, EnterShift, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_COMP_LT)
{
compiler.PushOpWithConvert(OpCodes.Clt);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_COMP_GT)
{
compiler.PushOpWithConvert(OpCodes.Cgt);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_COMP_LTE)
{
compiler.PushCondEq(OpCodes.Cgt);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_COMP_GTE)
{
compiler.PushCondEq(OpCodes.Clt);
}
else
{
throw new ParseException("Unknown relational token", context.Start.StartIndex);
}
});
}
public override void EnterEquality(JavascriptParser.EqualityContext context)
{
CompileBinary(context, context.relational, EnterRelational, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_COMP_EQ)
{
compiler.PushOpWithConvert(OpCodes.Ceq);
}
else if (terminalNode.Symbol.Type == JavascriptParser.AT_COMP_NEQ)
{
compiler.PushCondEq(OpCodes.Ceq);
}
else
{
throw new ParseException("Unknown equality token", context.Start.StartIndex);
}
});
}
public override void EnterBitwise_and(JavascriptParser.Bitwise_andContext context)
{
CompileBinary(context, context.equality, equalityContext =>
{
EnterEquality(equalityContext);
if (context.children.Count > 1) // if we have a bitwise token
{
compiler.Emit(OpCodes.Conv_I8); // cast to long (truncate)
}
}, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_BIT_AND)
{
compiler.PushOpWithConvert(OpCodes.And);
}
else
{
throw new ParseException("Unknown bitwise_and token", context.Start.StartIndex);
}
});
}
public override void EnterBitwise_xor(JavascriptParser.Bitwise_xorContext context)
{
CompileBinary(context, context.bitwise_and, andContext =>
{
EnterBitwise_and(andContext);
if (context.children.Count > 1) // if we have a bitwise token
{
compiler.Emit(OpCodes.Conv_I8); // cast to long (truncate)
}
}, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_BIT_XOR)
{
compiler.PushOpWithConvert(OpCodes.Xor);
}
else
{
throw new ParseException("Unknown bitwise_xor token", context.Start.StartIndex);
}
});
}
public override void EnterBitwise_or(JavascriptParser.Bitwise_orContext context)
{
CompileBinary(context, context.bitwise_xor, xorContext =>
{
EnterBitwise_xor(xorContext);
if (context.children.Count > 1) // if we have a bitwise token
{
compiler.Emit(OpCodes.Conv_I8); // cast to long (truncate)
}
}, terminalNode =>
{
if (terminalNode.Symbol.Type == JavascriptParser.AT_BIT_OR)
{
compiler.PushOpWithConvert(OpCodes.Or);
}
else
{
throw new ParseException("Unknown bitwise_or token", context.Start.StartIndex);
}
});
}
public override void EnterLogical_and(JavascriptParser.Logical_andContext context)
{
if (context.children.Count == 1)
{
EnterBitwise_or(context.bitwise_or(0));
}
else
{
// Evaluate the first operand and check if it is false
EnterBitwise_or(context.bitwise_or(0));
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Ceq);
// Iterate over the remaining operands
for (int i = 2; i < context.children.Count; i += 2)
{
if (context.children[i] is not JavascriptParser.Bitwise_orContext bitwiseOrContext)
{
throw new ParseException("Unexpected child of logical_and", context.Start.StartIndex);
}
// Evaluate the next operand and check if it is false
EnterBitwise_or(bitwiseOrContext);
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Ceq);
// Combine the results using OR
compiler.Emit(OpCodes.Or);
}
// Check if the combined result is false
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Ceq);
// Convert the result to a double
compiler.Emit(OpCodes.Conv_R8);
}
}
public override void EnterLogical_or(JavascriptParser.Logical_orContext context)
{
if (context.children.Count == 1)
{
EnterLogical_and(context.logical_and(0));
}
else
{
// Evaluate the first operand and check if it is true
EnterLogical_and(context.logical_and(0));
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Ceq);
compiler.Emit(OpCodes.Ldc_I4_1);
compiler.Emit(OpCodes.Xor);
// Iterate over the remaining operands
for (int i = 2; i < context.children.Count; i += 2)
{
if (context.children[i] is not JavascriptParser.Logical_andContext logicalAndContext)
{
throw new ParseException("Unexpected child of logical_or", context.Start.StartIndex);
}
// Evaluate the next operand and check if it is true
EnterLogical_and(logicalAndContext);
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Ceq);
compiler.Emit(OpCodes.Ldc_I4_1);
compiler.Emit(OpCodes.Xor);
// Combine the results using OR
compiler.Emit(OpCodes.Or);
}
// Check if the combined result is true
compiler.Emit(OpCodes.Ldc_I4_1);
compiler.Emit(OpCodes.Ceq);
// Convert the result to a double
compiler.Emit(OpCodes.Conv_R8);
}
}
public override void EnterConditional(JavascriptParser.ConditionalContext context)
{
if (context.children.Count == 1)
{
EnterLogical_or(context.logical_or());
}
else
{
Label condFalse = compiler.gen.DefineLabel();
Label condEnd = compiler.gen.DefineLabel();
// Evaluate the condition
EnterLogical_or(context.logical_or());
compiler.Emit(OpCodes.Ldc_I4_0);
compiler.Emit(OpCodes.Beq, condFalse);
// Evaluate the true branch
EnterConditional(context.conditional(0));
compiler.Emit(OpCodes.Br_S, condEnd);
// Evaluate the false branch
compiler.gen.MarkLabel(condFalse);
EnterConditional(context.conditional(1));
// Mark the end of the conditional
compiler.gen.MarkLabel(condEnd);
}
}
private static void CompileBinary<TContext, TArg>(
TContext context,
Func<int, TArg> getArg,
Action<TArg> enterArg,
Action<ITerminalNode> enterOp)
where TContext : ParserRuleContext
where TArg : ParserRuleContext
{
int argIndex = 0;
enterArg(getArg(argIndex++));
for (int i = 1; i < context.children.Count; i += 2)
{
if (context.children[i] is ITerminalNode terminalNode)
{
enterArg(getArg(argIndex++));
enterOp(terminalNode);
}
else
{
throw new ParseException("Unexpected child", context.Start.StartIndex);
}
}
}
}
private void PushCondEq(OpCode opCode)
{
Emit(opCode);
Emit(OpCodes.Ldc_I4_1);
Emit(OpCodes.Xor);
Emit(OpCodes.Conv_R8);
}
private void PushOpWithConvert(OpCode opCode)
{
Emit(opCode);
Emit(OpCodes.Conv_R8);
}
/// <summary>
/// NOTE: This was pushLong() in Lucene
/// </summary>
private void PushInt64(long i)
{
Emit(OpCodes.Ldc_I8, i);
if (!sourceText.Contains("<<"))
{
Emit(OpCodes.Conv_R8);
}
}
// LUCENENET-specific - wrapping Emit methods which is helpful for debugging
private void Emit(OpCode opcode)
{
// Console.WriteLine(opcode);
gen.Emit(opcode);
}
private void Emit(OpCode opcode, Label label)
{
// Console.WriteLine(opcode + " " + label);
gen.Emit(opcode, label);
}
private void Emit(OpCode opcode, double arg)
{
// Console.WriteLine(opcode + " " + arg);
gen.Emit(opcode, arg);
}
private void Emit(OpCode opcode, long arg)
{
// Console.WriteLine(opcode + " " + arg);
gen.Emit(opcode, arg);
}
private void Emit(OpCode opcode, MethodInfo arg)
{
// Console.WriteLine(opcode + " " + arg);
gen.Emit(opcode, arg);
}
private void EndCompile()
{
gen.Emit(OpCodes.Ret);
dynamicType.DefineMethodOverride(evalMethod, EVALUATE_METHOD);
}
private JavascriptParser.ExpressionContext GetAntlrComputedExpressionTree()
{
ICharStream input = new AntlrInputStream(sourceText);
JavascriptLexer lexer = new JavascriptLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
JavascriptParser parser = new JavascriptParser(tokens);
try
{
parser.ErrorHandler = new ParseExceptionErrorStrategy(); // LUCENENET-specific
return parser.expression();
}
catch (RecognitionException re)
{
throw new ArgumentException(re.Message, re);
}
}
// LUCENENET-specific: Throw a ParseException in the event of parsing errors
private class ParseExceptionErrorStrategy : DefaultErrorStrategy
{
public override void Recover(Parser recognizer, RecognitionException e)
{
int errorOffset = -1;
for (ParserRuleContext parserRuleContext = recognizer.Context; parserRuleContext != null; parserRuleContext = (ParserRuleContext)parserRuleContext.Parent)
{
if (errorOffset < 0)
{
errorOffset = parserRuleContext.Start.StartIndex;
}
parserRuleContext.exception = e;
}
throw new ParseException(e.Message, errorOffset);
}
public override IToken RecoverInline(Parser recognizer)
{
InputMismatchException cause = new InputMismatchException(recognizer);
int errorOffset = -1;
for (ParserRuleContext parserRuleContext = recognizer.Context; parserRuleContext != null; parserRuleContext = (ParserRuleContext)parserRuleContext.Parent)
{
if (errorOffset < 0)
{
errorOffset = parserRuleContext.Start.StartIndex;
}
parserRuleContext.exception = cause;
}
throw new ParseException(cause.Message, errorOffset);
}
public override void Sync(Parser recognizer)
{
}
}
/// <summary>The default set of functions available to expressions.</summary>
/// <remarks>
/// The default set of functions available to expressions.
/// <para/>
/// See the <see cref="Lucene.Net.Expressions.JS">package documentation</see> for a list.
/// </remarks>
[SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "This is a SonarCloud issue")]
[SuppressMessage("Performance", "S3887:Use an immutable collection or reduce the accessibility of the non-private readonly field", Justification = "Collection is immutable")]
[SuppressMessage("Performance", "S2386:Use an immutable collection or reduce the accessibility of the public static field", Justification = "Collection is immutable")]
public static readonly IDictionary<string, MethodInfo> DEFAULT_FUNCTIONS = LoadDefaultFunctions();
private static IDictionary<string, MethodInfo> LoadDefaultFunctions() // LUCENENET: Avoid static constructors (see https://github.com/apache/lucenenet/pull/224#issuecomment-469284006)
{
IDictionary<string, MethodInfo> map = new Dictionary<string, MethodInfo>();
try
{
foreach (var property in GetDefaultSettings())
{
string[] vals = property.Value.Split(',').TrimEnd();
if (vals.Length != 3)
{
throw Error.Create("Error reading Javascript functions from settings");
}
string typeName = vals[0];
Type clazz;
if (vals[0].Contains("Lucene.Net"))
{
clazz = GetType(vals[0] + ", Lucene.Net");
}
else
{
clazz = GetType(typeName);
}
string methodName = vals[1].Trim();
int arity = int.Parse(vals[2], CultureInfo.InvariantCulture);
Type[] args = new Type[arity];
Arrays.Fill(args, typeof(double));
MethodInfo method = clazz.GetMethod(methodName, args);
CheckFunction(method);
map[property.Key] = method;
}
}
catch (Exception e) when (e.IsNoSuchMethodException() || e.IsClassNotFoundException() || e.IsIOException())
{
throw Error.Create("Cannot resolve function", e);
}
return Collections.AsReadOnly(map);
}
private static Type GetType(string typeName)
{
try
{
return Type.GetType(typeName, true);
}
catch
{
return null;
}
}
private static IDictionary<string, string> GetDefaultSettings()
{
var settings = new Dictionary<string, string>();
var type = typeof(JavascriptCompiler);
using var reader = new StreamReader(type.FindAndGetManifestResourceStream(type.Name + ".properties"), Encoding.UTF8);
settings.LoadProperties(reader);
return settings;
}
private static void CheckFunction(MethodInfo method)
{
// do some checks if the signature is "compatible":
if (!method.IsStatic)
{
throw new ArgumentException(method + " is not static.");
}
if (!method.IsPublic)
{
throw new ArgumentException(method + " is not public.");
}
if (!method.DeclaringType.IsPublic)
{
//.NET Port. Inner class is being returned as not public even when declared public
if (method.DeclaringType.IsNestedAssembly)
{
throw new ArgumentException(method.DeclaringType.FullName + " is not public.");
}
}
if (method.GetParameters().Any(parmType => parmType.ParameterType != (typeof(double))))
{
throw new ArgumentException(method + " must take only double parameters");
}
if (method.ReturnType != typeof(double))
{
throw new ArgumentException(method + " does not return a double.");
}
}
}
}