diff --git a/src/Flee.Net45/ExpressionElements/Base/ExpressionElement.cs b/src/Flee.Net45/ExpressionElements/Base/ExpressionElement.cs index d0b99e2..58e8bc3 100644 --- a/src/Flee.Net45/ExpressionElements/Base/ExpressionElement.cs +++ b/src/Flee.Net45/ExpressionElements/Base/ExpressionElement.cs @@ -44,11 +44,6 @@ protected void ThrowAmbiguousCallException(Type leftType, Type rightType, object this.ThrowCompileException(CompileErrorResourceKeys.AmbiguousOverloadedOperator, CompileExceptionReason.AmbiguousMatch, leftType.Name, rightType.Name, operation); } - protected FleeILGenerator CreateTempFleeILGenerator(FleeILGenerator ilgCurrent) - { - DynamicMethod dm = new DynamicMethod("temp", typeof(Int32), null, this.GetType()); - return new FleeILGenerator(dm.GetILGenerator(), ilgCurrent.Length, true); - } protected string Name { diff --git a/src/Flee.Net45/ExpressionElements/Base/Literals/LiteralElement.cs b/src/Flee.Net45/ExpressionElements/Base/Literals/LiteralElement.cs index e6260ee..fe17db2 100644 --- a/src/Flee.Net45/ExpressionElements/Base/Literals/LiteralElement.cs +++ b/src/Flee.Net45/ExpressionElements/Base/Literals/LiteralElement.cs @@ -42,7 +42,7 @@ protected static void EmitLoad(Int64 value, FleeILGenerator ilg) } else if (value >= 0 & value <= UInt32.MaxValue) { - EmitLoad(Convert.ToInt32(value), ilg); + ilg.Emit(OpCodes.Ldc_I4, unchecked((int)Convert.ToUInt32(value))); ilg.Emit(OpCodes.Conv_U8); } else diff --git a/src/Flee.Net45/ExpressionElements/Conditional.cs b/src/Flee.Net45/ExpressionElements/Conditional.cs index c3ef069..a09225c 100644 --- a/src/Flee.Net45/ExpressionElements/Conditional.cs +++ b/src/Flee.Net45/ExpressionElements/Conditional.cs @@ -45,79 +45,33 @@ public ConditionalElement(ExpressionElement condition, ExpressionElement whenTru public override void Emit(FleeILGenerator ilg, IServiceProvider services) { - BranchManager bm = new BranchManager(); - bm.GetLabel("falseLabel", ilg); - bm.GetLabel("endLabel", ilg); - - if (ilg.IsTemp == true) - { - // If this is a fake emit, then do a fake emit and return - this.EmitConditional(ilg, services, bm); - return; - } - - FleeILGenerator ilgTemp = this.CreateTempFleeILGenerator(ilg); - Utility.SyncFleeILGeneratorLabels(ilg, ilgTemp); - - // Emit fake conditional to get branch target positions - this.EmitConditional(ilgTemp, services, bm); - - bm.ComputeBranches(); - - // Emit real conditional now that we have the branch target locations - this.EmitConditional(ilg, services, bm); + this.EmitConditional(ilg, services); } - private void EmitConditional(FleeILGenerator ilg, IServiceProvider services, BranchManager bm) + private void EmitConditional(FleeILGenerator ilg, IServiceProvider services) { - Label falseLabel = bm.FindLabel("falseLabel"); - Label endLabel = bm.FindLabel("endLabel"); + Label falseLabel = ilg.DefineLabel(); + Label endLabel = ilg.DefineLabel(); // Emit the condition _myCondition.Emit(ilg, services); // On false go to the false operand - if (ilg.IsTemp == true) - { - bm.AddBranch(ilg, falseLabel); - ilg.Emit(OpCodes.Brfalse_S, falseLabel); - } - else if (bm.IsLongBranch(ilg, falseLabel) == false) - { - ilg.Emit(OpCodes.Brfalse_S, falseLabel); - } - else - { - ilg.Emit(OpCodes.Brfalse, falseLabel); - } + ilg.EmitBranchFalse(falseLabel); // Emit the true operand _myWhenTrue.Emit(ilg, services); ImplicitConverter.EmitImplicitConvert(_myWhenTrue.ResultType, _myResultType, ilg); // Jump to end - if (ilg.IsTemp == true) - { - bm.AddBranch(ilg, endLabel); - ilg.Emit(OpCodes.Br_S, endLabel); - } - else if (bm.IsLongBranch(ilg, endLabel) == false) - { - ilg.Emit(OpCodes.Br_S, endLabel); - } - else - { - ilg.Emit(OpCodes.Br, endLabel); - } + ilg.EmitBranch(endLabel); - bm.MarkLabel(ilg, falseLabel); ilg.MarkLabel(falseLabel); // Emit the false operand _myWhenFalse.Emit(ilg, services); ImplicitConverter.EmitImplicitConvert(_myWhenFalse.ResultType, _myResultType, ilg); // Fall through to end - bm.MarkLabel(ilg, endLabel); ilg.MarkLabel(endLabel); } diff --git a/src/Flee.Net45/ExpressionElements/In.cs b/src/Flee.Net45/ExpressionElements/In.cs index 2a7c8ef..b844e10 100644 --- a/src/Flee.Net45/ExpressionElements/In.cs +++ b/src/Flee.Net45/ExpressionElements/In.cs @@ -118,24 +118,11 @@ public override void Emit(FleeILGenerator ilg, IServiceProvider services) if ((MyTargetCollectionType != null)) { this.EmitCollectionIn(ilg, services); - } else { - BranchManager bm = new BranchManager(); - bm.GetLabel("endLabel", ilg); - bm.GetLabel("trueTerminal", ilg); - - // Do a fake emit to get branch positions - FleeILGenerator ilgTemp = this.CreateTempFleeILGenerator(ilg); - Utility.SyncFleeILGeneratorLabels(ilg, ilgTemp); - - this.EmitListIn(ilgTemp, services, bm); - - bm.ComputeBranches(); - // Do the real emit - this.EmitListIn(ilg, services, bm); + this.EmitListIn(ilg, services); } } @@ -167,11 +154,11 @@ private MethodInfo GetCollectionContainsMethod() return MyTargetCollectionType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); } - private void EmitListIn(FleeILGenerator ilg, IServiceProvider services, BranchManager bm) + private void EmitListIn(FleeILGenerator ilg, IServiceProvider services) { CompareElement ce = new CompareElement(); - Label endLabel = bm.FindLabel("endLabel"); - Label trueTerminal = bm.FindLabel("trueTerminal"); + Label endLabel = ilg.DefineLabel(); + Label trueTerminal = ilg.DefineLabel(); // Cache the operand since we will be comparing against it a lot LocalBuilder lb = ilg.DeclareLocal(MyOperand.ResultType); @@ -189,36 +176,22 @@ private void EmitListIn(FleeILGenerator ilg, IServiceProvider services, BranchMa ce.Initialize(targetShim, argumentElement, LogicalCompareOperation.Equal); ce.Emit(ilg, services); - EmitBranchToTrueTerminal(ilg, trueTerminal, bm); + EmitBranchToTrueTerminal(ilg, trueTerminal); } ilg.Emit(OpCodes.Ldc_I4_0); ilg.Emit(OpCodes.Br_S, endLabel); - bm.MarkLabel(ilg, trueTerminal); ilg.MarkLabel(trueTerminal); ilg.Emit(OpCodes.Ldc_I4_1); - bm.MarkLabel(ilg, endLabel); ilg.MarkLabel(endLabel); } - private static void EmitBranchToTrueTerminal(FleeILGenerator ilg, Label trueTerminal, BranchManager bm) + private static void EmitBranchToTrueTerminal(FleeILGenerator ilg, Label trueTerminal) { - if (ilg.IsTemp == true) - { - bm.AddBranch(ilg, trueTerminal); - ilg.Emit(OpCodes.Brtrue_S, trueTerminal); - } - else if (bm.IsLongBranch(ilg, trueTerminal) == false) - { - ilg.Emit(OpCodes.Brtrue_S, trueTerminal); - } - else - { - ilg.Emit(OpCodes.Brtrue, trueTerminal); - } + ilg.EmitBranchTrue(trueTerminal); } public override System.Type ResultType => typeof(bool); diff --git a/src/Flee.Net45/ExpressionElements/LogicalBitwise/AndOr.cs b/src/Flee.Net45/ExpressionElements/LogicalBitwise/AndOr.cs index 9ae9ceb..fe70a3b 100644 --- a/src/Flee.Net45/ExpressionElements/LogicalBitwise/AndOr.cs +++ b/src/Flee.Net45/ExpressionElements/LogicalBitwise/AndOr.cs @@ -79,20 +79,6 @@ private void DoEmitLogical(FleeILGenerator ilg, IServiceProvider services) { // We have to do a 'fake' emit so we can get the positions of the labels ShortCircuitInfo info = new ShortCircuitInfo(); - // Create a temporary IL generator - FleeILGenerator ilgTemp = this.CreateTempFleeILGenerator(ilg); - - // We have to make sure that the label count for the temp FleeILGenerator matches our real FleeILGenerator - Utility.SyncFleeILGeneratorLabels(ilg, ilgTemp); - // Do the fake emit - this.EmitLogical(ilgTemp, info, services); - - // Clear everything except the label positions - info.ClearTempState(); - - info.Branches.ComputeBranches(); - - Utility.SyncFleeILGeneratorLabels(ilgTemp, ilg); // Do the real emit this.EmitLogical(ilg, info, services); @@ -112,7 +98,7 @@ private void DoEmitLogical(FleeILGenerator ilg, IServiceProvider services) private void EmitLogical(FleeILGenerator ilg, ShortCircuitInfo info, IServiceProvider services) { // We always have an end label - info.Branches.GetLabel(OurEndLabelKey, ilg); + Label endLabel = ilg.DefineLabel(); // Populate our data structures this.PopulateData(info); @@ -124,9 +110,9 @@ private void EmitLogical(FleeILGenerator ilg, ShortCircuitInfo info, IServicePro ExpressionElement terminalOperand = (ExpressionElement)info.Operands.Pop(); // Emit it EmitOperand(terminalOperand, info, ilg, services); - // And jump to the end - Label endLabel = info.Branches.FindLabel(OurEndLabelKey); - ilg.Emit(OpCodes.Br_S, endLabel); + + // only 1-3 opcodes, always a short branch + ilg.EmitBranch(endLabel); // Emit our true/false terminals EmitTerminals(info, ilg, endLabel); @@ -163,61 +149,14 @@ private static void EmitLogicalShortCircuit(FleeILGenerator ilg, ShortCircuitInf private static void EmitBranch(AndOrElement op, FleeILGenerator ilg, Label target, ShortCircuitInfo info) { - if (ilg.IsTemp == true) - { - info.Branches.AddBranch(ilg, target); - - // Temp mode; just emit a short branch and return - OpCode shortBranch = GetBranchOpcode(op, false); - ilg.Emit(shortBranch, target); - - return; - } - - // Emit the proper branch opcode - - // Determine if it is a long branch - bool longBranch = info.Branches.IsLongBranch(ilg, target); - // Get the branch opcode - OpCode brOpcode = GetBranchOpcode(op, longBranch); - - // Emit the branch - ilg.Emit(brOpcode, target); - } - - /// - /// Emit a short/long branch for an And/Or element - /// - /// - /// - /// - private static OpCode GetBranchOpcode(AndOrElement op, bool longBranch) - { if (op._myOperation == AndOrOperation.And) - { - if (longBranch == true) - { - return OpCodes.Brfalse; - } - else - { - return OpCodes.Brfalse_S; - } - } + ilg.EmitBranchFalse(target); else - { - if (longBranch == true) - { - return OpCodes.Brtrue; - } - else - { - return OpCodes.Brtrue_S; - } - } + ilg.EmitBranchTrue(target); } + /// /// Get the label for a short-circuit /// @@ -316,14 +255,11 @@ private void Pop(Stack operands, Stack operators) private static void EmitOperand(ExpressionElement operand, ShortCircuitInfo info, FleeILGenerator ilg, IServiceProvider services) { // Is this operand the target of a label? - if (info.Branches.HasLabel(operand) == true) + if (info.HasLabel(operand) == true) { // Yes, so mark it - Label leftLabel = info.Branches.FindLabel(operand); + Label leftLabel = info.FindLabel(operand); ilg.MarkLabel(leftLabel); - - // Note the label's position - MarkBranchTarget(info, leftLabel, ilg); } // Emit the operand @@ -339,53 +275,41 @@ private static void EmitOperand(ExpressionElement operand, ShortCircuitInfo info private static void EmitTerminals(ShortCircuitInfo info, FleeILGenerator ilg, Label endLabel) { // Emit the false case if it was used - if (info.Branches.HasLabel(OurFalseTerminalKey) == true) + if (info.HasLabel(OurFalseTerminalKey) == true) { - Label falseLabel = info.Branches.FindLabel(OurFalseTerminalKey); + Label falseLabel = info.FindLabel(OurFalseTerminalKey); // Mark the label and note its position ilg.MarkLabel(falseLabel); - MarkBranchTarget(info, falseLabel, ilg); ilg.Emit(OpCodes.Ldc_I4_0); // If we also have a true terminal, then skip over it - if (info.Branches.HasLabel(OurTrueTerminalKey) == true) + if (info.HasLabel(OurTrueTerminalKey) == true) { + // only 1-3 opcodes, always a short branch ilg.Emit(OpCodes.Br_S, endLabel); } } // Emit the true case if it was used - if (info.Branches.HasLabel(OurTrueTerminalKey) == true) + if (info.HasLabel(OurTrueTerminalKey) == true) { - Label trueLabel = info.Branches.FindLabel(OurTrueTerminalKey); + Label trueLabel = info.FindLabel(OurTrueTerminalKey); // Mark the label and note its position ilg.MarkLabel(trueLabel); - MarkBranchTarget(info, trueLabel, ilg); ilg.Emit(OpCodes.Ldc_I4_1); } } - /// - /// Note a label's position if we are in mark mode - /// - /// - /// - /// - private static void MarkBranchTarget(ShortCircuitInfo info, Label target, FleeILGenerator ilg) - { - if (ilg.IsTemp == true) - { - info.Branches.MarkLabel(ilg, target); - } - } private static Label GetLabel(object key, FleeILGenerator ilg, ShortCircuitInfo info) { - return info.Branches.GetLabel(key, ilg); + if (info.HasLabel(key)) + return info.FindLabel(key); + return info.AddLabel(key, ilg.DefineLabel()); } /// diff --git a/src/Flee.Net45/InternalTypes/BranchManager.cs b/src/Flee.Net45/InternalTypes/BranchManager.cs index c1729d3..1e01d2a 100644 --- a/src/Flee.Net45/InternalTypes/BranchManager.cs +++ b/src/Flee.Net45/InternalTypes/BranchManager.cs @@ -9,32 +9,51 @@ namespace Flee.InternalTypes [Obsolete("Manages branch information and allows us to determine if we should emit a short or long branch")] internal class BranchManager { - private IList MyBranchInfos; + private readonly IList MyBranchInfos; - private IDictionary MyKeyLabelMap; public BranchManager() { MyBranchInfos = new List(); - MyKeyLabelMap = new Dictionary(); } /// - /// Determine whether to use short or long branches + /// check if any long branches exist /// - /// - public void ComputeBranches() + /// + public bool HasLongBranches() { - List betweenBranches = new List(); - foreach (BranchInfo bi in MyBranchInfos) { - betweenBranches.Clear(); + if (bi.ComputeIsLongBranch()) return true; + } + return false; + } - // Find any branches between the start and end locations of this branch - this.FindBetweenBranches(bi, betweenBranches); + /// + /// Determine whether to use short or long branches. + /// This advances the ilg offset with No-op to adjust + /// for the long branches needed. + /// + /// + public bool ComputeBranches() + { + // + // we need to iterate in reverse order of the + // starting location, as branch between our + // branch could push our branch to a long branch. + // + for( var idx=MyBranchInfos.Count-1; idx >= 0; idx--) + { + var bi = MyBranchInfos[idx]; - // Count the number of long branches in the above set - int longBranchesBetween = this.CountLongBranches(betweenBranches); + // count long branches between + int longBranchesBetween = 0; + for( var ii=idx+1; ii < MyBranchInfos.Count; ii++) + { + var bi2 = MyBranchInfos[ii]; + if (bi2.IsBetween(bi) && bi2.ComputeIsLongBranch()) + ++longBranchesBetween; + } // Adjust the branch as necessary bi.AdjustForLongBranchesBetween(longBranchesBetween); @@ -54,63 +73,31 @@ public void ComputeBranches() // Keep a tally of the number of long branches longBranchCount += Convert.ToInt32(bi.IsLongBranch); } - } - - /// - /// Count the number of long branches in a set - /// - /// - /// - /// - private int CountLongBranches(ICollection dest) - { - int count = 0; - - foreach (BranchInfo bi in dest) - { - count += Convert.ToInt32(bi.ComputeIsLongBranch()); - } - return count; + return (longBranchCount > 0); } - /// - /// Find all the branches between the start and end locations of a target branch - /// - /// - /// - /// - private void FindBetweenBranches(BranchInfo target, ICollection dest) - { - foreach (BranchInfo bi in MyBranchInfos) - { - if (bi.IsBetween(target) == true) - { - dest.Add(bi); - } - } - } /// /// Determine if a branch from a point to a label will be long /// /// - /// /// /// - public bool IsLongBranch(FleeILGenerator ilg, Label target) + public bool IsLongBranch(FleeILGenerator ilg) { - //return true; ILLocation startLoc = new ILLocation(ilg.Length); - BranchInfo bi = new BranchInfo(startLoc, target); - int index = MyBranchInfos.IndexOf(bi); - if (index > -1 && index < MyBranchInfos.Count) - { - bi = MyBranchInfos[index]; - } + foreach (var bi in MyBranchInfos) + { + if (bi.Equals(startLoc)) + return bi.IsLongBranch; + } - return bi.IsLongBranch; + // we don't really know since this branch didn't exist. + // we could throw an exceptio but + // do a long branch to be safe. + return true; } /// @@ -124,48 +111,10 @@ public void AddBranch(FleeILGenerator ilg, Label target) ILLocation startLoc = new ILLocation(ilg.Length); BranchInfo bi = new BranchInfo(startLoc, target); + // branches will be sorted in order MyBranchInfos.Add(bi); } - /// - /// Get a label by a key - /// - /// - /// - /// - public Label FindLabel(object key) - { - return MyKeyLabelMap[key]; - } - - /// - /// Get a label by a key. Create the label if it is not present. - /// - /// - /// - /// - /// - public Label GetLabel(object key, FleeILGenerator ilg) - { - Label lbl; - if (MyKeyLabelMap.TryGetValue(key, out lbl) == false) - { - lbl = ilg.DefineLabel(); - MyKeyLabelMap.Add(key, lbl); - } - return lbl; - } - - /// - /// Determines if we have a label for a key - /// - /// - /// - /// - public bool HasLabel(object key) - { - return MyKeyLabelMap.ContainsKey(key); - } /// /// Set the position for a label @@ -268,12 +217,13 @@ public int CompareTo(ILLocation other) } [Obsolete("Represents a branch from a start location to an end location")] - internal class BranchInfo : IEquatable + internal class BranchInfo { private readonly ILLocation _myStart; private readonly ILLocation _myEnd; private Label _myLabel; private bool _myIsLongBranch; + public BranchInfo(ILLocation startLocation, Label endLabel) { _myStart = startLocation; @@ -284,6 +234,9 @@ public BranchInfo(ILLocation startLocation, Label endLabel) public void AdjustForLongBranches(int longBranchCount) { _myStart.AdjustForLongBranch(longBranchCount); + // end not necessarily needed once we determine + // if this is long, but keep it accurate anyway. + _myEnd.AdjustForLongBranch(longBranchCount); } public void BakeIsLongBranch() @@ -314,13 +267,16 @@ public void Mark(Label target, int position) } } - public bool Equals1(BranchInfo other) - { - return _myStart.Equals1(other._myStart) && _myLabel.Equals(other._myLabel); - } - bool System.IEquatable.Equals(BranchInfo other) + /// + /// We only need to compare the start point. Can only have a single + /// brach from the exact address, so if label doesn't match we have + /// bigger problems. + /// + /// + /// + public bool Equals(ILLocation start) { - return Equals1(other); + return _myStart.Equals1(start); } public override string ToString() diff --git a/src/Flee.Net45/InternalTypes/Expression.cs b/src/Flee.Net45/InternalTypes/Expression.cs index 31600a7..2f58f0b 100644 --- a/src/Flee.Net45/InternalTypes/Expression.cs +++ b/src/Flee.Net45/InternalTypes/Expression.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Reflection.Emit; using System.Reflection; -using System.Linq.Expressions; using Flee.ExpressionElements; using Flee.ExpressionElements.Base; using Flee.PublicTypes; @@ -88,13 +87,20 @@ private void Compile(string expression, ExpressionOptions options) // Emit the IL rootElement.Emit(ilg, services); + if (ilg.NeedsSecondPass()) + { + // second pass required due to long branches. + dm = this.CreateDynamicMethod(); + ilg.PrepareSecondPass(dm.GetILGenerator()); + rootElement.Emit(ilg, services); + } ilg.ValidateLength(); // Emit to an assembly if required if (options.EmitToAssembly == true) { - EmitToAssembly(rootElement, services); + EmitToAssembly(ilg, rootElement, services); } Type delegateType = typeof(ExpressionEvaluator<>).MakeGenericType(typeof(T)); @@ -125,26 +131,32 @@ private void AddServices(IServiceContainer dest) dest.AddService(typeof(ExpressionInfo), _myInfo); } - private static void EmitToAssembly(ExpressionElement rootElement, IServiceContainer services) + /// + /// Emit to an assembly. We've already computed long branches at this point, + /// so we emit as a second pass + /// + /// + /// + /// + private static void EmitToAssembly(FleeILGenerator ilg, ExpressionElement rootElement, IServiceContainer services) { AssemblyName assemblyName = new AssemblyName(EmitAssemblyName); string assemblyFileName = string.Format("{0}.dll", EmitAssemblyName); - AssemblyBuilder assemblyBuilder = System.AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyFileName, assemblyFileName); + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyFileName); MethodBuilder mb = moduleBuilder.DefineGlobalMethod("Evaluate", MethodAttributes.Public | MethodAttributes.Static, typeof(T), new Type[] { - typeof(object), - typeof(ExpressionContext), - typeof(VariableCollection) - }); - FleeILGenerator ilg = new FleeILGenerator(mb.GetILGenerator()); + typeof(object),typeof(ExpressionContext),typeof(VariableCollection)}); + // already emitted once for local use, + ilg.PrepareSecondPass(mb.GetILGenerator()); rootElement.Emit(ilg, services); moduleBuilder.CreateGlobalFunctions(); - assemblyBuilder.Save(assemblyFileName); + //assemblyBuilder.Save(assemblyFileName); + assemblyBuilder.CreateInstance(assemblyFileName); } private void ValidateOwner(object owner) diff --git a/src/Flee.Net45/InternalTypes/FleeILGenerator.cs b/src/Flee.Net45/InternalTypes/FleeILGenerator.cs index c294fc4..f9a7b6b 100644 --- a/src/Flee.Net45/InternalTypes/FleeILGenerator.cs +++ b/src/Flee.Net45/InternalTypes/FleeILGenerator.cs @@ -9,17 +9,21 @@ namespace Flee.InternalTypes { internal class FleeILGenerator { - private readonly ILGenerator _myIlGenerator; + private ILGenerator _myIlGenerator; private int _myLength; private int _myLabelCount; private readonly Dictionary _localBuilderTemp; - private readonly bool _myIsTemp; - public FleeILGenerator(ILGenerator ilg, int startLength = 0, bool isTemp = false) + private int _myPass; + private int _brContext; + private BranchManager _bm; + + public FleeILGenerator(ILGenerator ilg) { _myIlGenerator = ilg; _localBuilderTemp = new Dictionary(); - _myIsTemp = isTemp; - _myLength = startLength; + _myLength = 0; + _myPass = 1; + _bm = new BranchManager(); } public int GetTempLocalIndex(Type localType) @@ -35,6 +39,30 @@ public int GetTempLocalIndex(Type localType) return local.LocalIndex; } + /// + /// after first pass, check for long branches. + /// If any, we need to generate again. + /// + /// + public bool NeedsSecondPass() + { + return _bm.HasLongBranches(); + } + + /// + /// need a new ILGenerator for 2nd pass. This can also + /// get called for a 3rd pass when emitting to assembly. + /// + /// + public void PrepareSecondPass(ILGenerator ilg) + { + _bm.ComputeBranches(); + _localBuilderTemp.Clear(); + _myIlGenerator = ilg; + _myLength = 0; + _myPass++; + } + public void Emit(OpCode op) { this.RecordOpcode(op); @@ -119,17 +147,72 @@ public void Emit(OpCode op, Label arg) _myIlGenerator.Emit(op, arg); } + public void EmitBranch(Label arg) + { + if (_myPass == 1) + { + _bm.AddBranch(this, arg); + Emit(OpCodes.Br_S, arg); + } + else if (_bm.IsLongBranch(this) == false) + { + Emit(OpCodes.Br_S, arg); + } + else + { + Emit(OpCodes.Br, arg); + } + } + + public void EmitBranchFalse(Label arg) + { + if (_myPass == 1) + { + _bm.AddBranch(this, arg); + Emit(OpCodes.Brfalse_S, arg); + } + else if (_bm.IsLongBranch(this) == false) + { + Emit(OpCodes.Brfalse_S, arg); + } + else + { + Emit(OpCodes.Brfalse, arg); + } + } + + public void EmitBranchTrue(Label arg) + { + if (_myPass == 1) + { + _bm.AddBranch(this, arg); + Emit(OpCodes.Brtrue_S, arg); + } + else if (_bm.IsLongBranch(this) == false) + { + Emit(OpCodes.Brtrue_S, arg); + } + else + { + Emit(OpCodes.Brtrue, arg); + } + } + public void MarkLabel(Label lbl) { _myIlGenerator.MarkLabel(lbl); + _bm.MarkLabel(this, lbl); } + public Label DefineLabel() { _myLabelCount += 1; - return _myIlGenerator.DefineLabel(); + var label = _myIlGenerator.DefineLabel(); + return label; } + public LocalBuilder DeclareLocal(Type localType) { return _myIlGenerator.DeclareLocal(localType); @@ -185,7 +268,5 @@ public void ValidateLength() public int LabelCount => _myLabelCount; private int ILGeneratorLength => Utility.GetILGeneratorLength(_myIlGenerator); - - public bool IsTemp => _myIsTemp; } } diff --git a/src/Flee.Net45/InternalTypes/Miscellaneous.cs b/src/Flee.Net45/InternalTypes/Miscellaneous.cs index 7646efd..5cce032 100644 --- a/src/Flee.Net45/InternalTypes/Miscellaneous.cs +++ b/src/Flee.Net45/InternalTypes/Miscellaneous.cs @@ -192,13 +192,13 @@ public void ComputeScore(Type[] argTypes) } else if (@params.Length == 1 && argTypes.Length == 0)//extension method without parameter support -> prefer members { - _myScore = 0.1F; + _myScore = 0.1F; } else if (IsParamArray == true) { _myScore = this.ComputeScoreForParamArray(@params, argTypes); } - else if(IsExtensionMethod == true) + else if (IsExtensionMethod == true) { _myScore = this.ComputeScoreExtensionMethodInternal(@params, argTypes); } @@ -221,7 +221,7 @@ private float ComputeScoreExtensionMethodInternal(ParameterInfo[] parameters, Ty for (int i = 0; i <= argTypes.Length - 1; i++) { - sum += ImplicitConverter.GetImplicitConvertScore(argTypes[i], parameters[i+1].ParameterType); + sum += ImplicitConverter.GetImplicitConvertScore(argTypes[i], parameters[i + 1].ParameterType); } return sum; @@ -238,7 +238,7 @@ private float ComputeScoreInternal(ParameterInfo[] parameters, Type[] argTypes) // Our score is the average of the scores of each parameter. The lower the score, the better the match. int sum = ComputeSum(parameters, argTypes); - return sum / argTypes.Length; + return (float)sum / (float)argTypes.Length; } private static int ComputeSum(ParameterInfo[] parameters, Type[] argTypes) @@ -321,7 +321,7 @@ public bool IsMatch(Type[] argTypes, MemberElement previous, ExpressionContext c if (lastParam.IsDefined(typeof(ParamArrayAttribute), false) == false) { //Extension method support - if(parameters.Length == argTypes.Length + 1) + if (parameters.Length == argTypes.Length + 1) { IsExtensionMethod = true; return AreValidExtensionMethodArgumentsForParameters(argTypes, parameters, previous, context); @@ -418,7 +418,7 @@ private static bool AreValidExtensionMethodArgumentsForParameters(Type[] argType //Match if every given argument is implicitly convertible to the method's corresponding parameter for (int i = 0; i <= argTypes.Length - 1; i++) { - if (ImplicitConverter.EmitImplicitConvert(argTypes[i], parameters[i+1].ParameterType, null) == false) + if (ImplicitConverter.EmitImplicitConvert(argTypes[i], parameters[i + 1].ParameterType, null) == false) { return false; } @@ -463,13 +463,13 @@ internal class ShortCircuitInfo public Stack Operands; public Stack Operators; + private Dictionary Labels; - public BranchManager Branches; public ShortCircuitInfo() { this.Operands = new Stack(); this.Operators = new Stack(); - this.Branches = new BranchManager(); + this.Labels = new Dictionary(); } public void ClearTempState() @@ -477,6 +477,22 @@ public void ClearTempState() this.Operands.Clear(); this.Operators.Clear(); } + + public Label AddLabel(object key, Label lbl) + { + Labels.Add(key, lbl); + return lbl; + } + + public bool HasLabel(object key) + { + return Labels.ContainsKey(key); + } + + public Label FindLabel(object key) + { + return Labels[key]; + } } [Obsolete("Wraps an expression element so that it is loaded from a local slot")] diff --git a/src/Flee.Net45/InternalTypes/Utility.cs b/src/Flee.Net45/InternalTypes/Utility.cs index 3de1ca5..3a7ec97 100644 --- a/src/Flee.Net45/InternalTypes/Utility.cs +++ b/src/Flee.Net45/InternalTypes/Utility.cs @@ -42,11 +42,15 @@ public static void EmitStoreLocal(FleeILGenerator ilg, int index) break; } } - else + else if (index < 256) { - Debug.Assert(index < 256, "local index too large"); ilg.Emit(OpCodes.Stloc_S, Convert.ToByte(index)); } + else + { + Debug.Assert(index < 65535, "local index too large"); + ilg.Emit(OpCodes.Stloc, unchecked((short)Convert.ToUInt16(index))); + } } public static void EmitLoadLocal(FleeILGenerator ilg, int index) @@ -71,11 +75,15 @@ public static void EmitLoadLocal(FleeILGenerator ilg, int index) break; } } - else + else if (index < 256) { - Debug.Assert(index < 256, "local index too large"); ilg.Emit(OpCodes.Ldloc_S, Convert.ToByte(index)); } + else + { + Debug.Assert(index < 65535, "local index too large"); + ilg.Emit(OpCodes.Ldloc, unchecked((short)Convert.ToUInt16(index))); + } } public static void EmitLoadLocalAddress(FleeILGenerator ilg, int index) @@ -179,13 +187,7 @@ public static void EmitArrayStore(FleeILGenerator ilg, Type elementType) } } - public static void SyncFleeILGeneratorLabels(FleeILGenerator source, FleeILGenerator target) - { - while (source.LabelCount != target.LabelCount) - { - target.DefineLabel(); - } - } + public static bool IsIntegralType(Type t) { diff --git a/src/Flee.NetStandard20/ExpressionElements/Base/ExpressionElement.cs b/src/Flee.NetStandard20/ExpressionElements/Base/ExpressionElement.cs index d0b99e2..58e8bc3 100644 --- a/src/Flee.NetStandard20/ExpressionElements/Base/ExpressionElement.cs +++ b/src/Flee.NetStandard20/ExpressionElements/Base/ExpressionElement.cs @@ -44,11 +44,6 @@ protected void ThrowAmbiguousCallException(Type leftType, Type rightType, object this.ThrowCompileException(CompileErrorResourceKeys.AmbiguousOverloadedOperator, CompileExceptionReason.AmbiguousMatch, leftType.Name, rightType.Name, operation); } - protected FleeILGenerator CreateTempFleeILGenerator(FleeILGenerator ilgCurrent) - { - DynamicMethod dm = new DynamicMethod("temp", typeof(Int32), null, this.GetType()); - return new FleeILGenerator(dm.GetILGenerator(), ilgCurrent.Length, true); - } protected string Name { diff --git a/src/Flee.NetStandard20/ExpressionElements/Base/Literals/LiteralElement.cs b/src/Flee.NetStandard20/ExpressionElements/Base/Literals/LiteralElement.cs index e6260ee..fe17db2 100644 --- a/src/Flee.NetStandard20/ExpressionElements/Base/Literals/LiteralElement.cs +++ b/src/Flee.NetStandard20/ExpressionElements/Base/Literals/LiteralElement.cs @@ -42,7 +42,7 @@ protected static void EmitLoad(Int64 value, FleeILGenerator ilg) } else if (value >= 0 & value <= UInt32.MaxValue) { - EmitLoad(Convert.ToInt32(value), ilg); + ilg.Emit(OpCodes.Ldc_I4, unchecked((int)Convert.ToUInt32(value))); ilg.Emit(OpCodes.Conv_U8); } else diff --git a/src/Flee.NetStandard20/ExpressionElements/Conditional.cs b/src/Flee.NetStandard20/ExpressionElements/Conditional.cs index c3ef069..a09225c 100644 --- a/src/Flee.NetStandard20/ExpressionElements/Conditional.cs +++ b/src/Flee.NetStandard20/ExpressionElements/Conditional.cs @@ -45,79 +45,33 @@ public ConditionalElement(ExpressionElement condition, ExpressionElement whenTru public override void Emit(FleeILGenerator ilg, IServiceProvider services) { - BranchManager bm = new BranchManager(); - bm.GetLabel("falseLabel", ilg); - bm.GetLabel("endLabel", ilg); - - if (ilg.IsTemp == true) - { - // If this is a fake emit, then do a fake emit and return - this.EmitConditional(ilg, services, bm); - return; - } - - FleeILGenerator ilgTemp = this.CreateTempFleeILGenerator(ilg); - Utility.SyncFleeILGeneratorLabels(ilg, ilgTemp); - - // Emit fake conditional to get branch target positions - this.EmitConditional(ilgTemp, services, bm); - - bm.ComputeBranches(); - - // Emit real conditional now that we have the branch target locations - this.EmitConditional(ilg, services, bm); + this.EmitConditional(ilg, services); } - private void EmitConditional(FleeILGenerator ilg, IServiceProvider services, BranchManager bm) + private void EmitConditional(FleeILGenerator ilg, IServiceProvider services) { - Label falseLabel = bm.FindLabel("falseLabel"); - Label endLabel = bm.FindLabel("endLabel"); + Label falseLabel = ilg.DefineLabel(); + Label endLabel = ilg.DefineLabel(); // Emit the condition _myCondition.Emit(ilg, services); // On false go to the false operand - if (ilg.IsTemp == true) - { - bm.AddBranch(ilg, falseLabel); - ilg.Emit(OpCodes.Brfalse_S, falseLabel); - } - else if (bm.IsLongBranch(ilg, falseLabel) == false) - { - ilg.Emit(OpCodes.Brfalse_S, falseLabel); - } - else - { - ilg.Emit(OpCodes.Brfalse, falseLabel); - } + ilg.EmitBranchFalse(falseLabel); // Emit the true operand _myWhenTrue.Emit(ilg, services); ImplicitConverter.EmitImplicitConvert(_myWhenTrue.ResultType, _myResultType, ilg); // Jump to end - if (ilg.IsTemp == true) - { - bm.AddBranch(ilg, endLabel); - ilg.Emit(OpCodes.Br_S, endLabel); - } - else if (bm.IsLongBranch(ilg, endLabel) == false) - { - ilg.Emit(OpCodes.Br_S, endLabel); - } - else - { - ilg.Emit(OpCodes.Br, endLabel); - } + ilg.EmitBranch(endLabel); - bm.MarkLabel(ilg, falseLabel); ilg.MarkLabel(falseLabel); // Emit the false operand _myWhenFalse.Emit(ilg, services); ImplicitConverter.EmitImplicitConvert(_myWhenFalse.ResultType, _myResultType, ilg); // Fall through to end - bm.MarkLabel(ilg, endLabel); ilg.MarkLabel(endLabel); } diff --git a/src/Flee.NetStandard20/ExpressionElements/In.cs b/src/Flee.NetStandard20/ExpressionElements/In.cs index 2a7c8ef..b844e10 100644 --- a/src/Flee.NetStandard20/ExpressionElements/In.cs +++ b/src/Flee.NetStandard20/ExpressionElements/In.cs @@ -118,24 +118,11 @@ public override void Emit(FleeILGenerator ilg, IServiceProvider services) if ((MyTargetCollectionType != null)) { this.EmitCollectionIn(ilg, services); - } else { - BranchManager bm = new BranchManager(); - bm.GetLabel("endLabel", ilg); - bm.GetLabel("trueTerminal", ilg); - - // Do a fake emit to get branch positions - FleeILGenerator ilgTemp = this.CreateTempFleeILGenerator(ilg); - Utility.SyncFleeILGeneratorLabels(ilg, ilgTemp); - - this.EmitListIn(ilgTemp, services, bm); - - bm.ComputeBranches(); - // Do the real emit - this.EmitListIn(ilg, services, bm); + this.EmitListIn(ilg, services); } } @@ -167,11 +154,11 @@ private MethodInfo GetCollectionContainsMethod() return MyTargetCollectionType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); } - private void EmitListIn(FleeILGenerator ilg, IServiceProvider services, BranchManager bm) + private void EmitListIn(FleeILGenerator ilg, IServiceProvider services) { CompareElement ce = new CompareElement(); - Label endLabel = bm.FindLabel("endLabel"); - Label trueTerminal = bm.FindLabel("trueTerminal"); + Label endLabel = ilg.DefineLabel(); + Label trueTerminal = ilg.DefineLabel(); // Cache the operand since we will be comparing against it a lot LocalBuilder lb = ilg.DeclareLocal(MyOperand.ResultType); @@ -189,36 +176,22 @@ private void EmitListIn(FleeILGenerator ilg, IServiceProvider services, BranchMa ce.Initialize(targetShim, argumentElement, LogicalCompareOperation.Equal); ce.Emit(ilg, services); - EmitBranchToTrueTerminal(ilg, trueTerminal, bm); + EmitBranchToTrueTerminal(ilg, trueTerminal); } ilg.Emit(OpCodes.Ldc_I4_0); ilg.Emit(OpCodes.Br_S, endLabel); - bm.MarkLabel(ilg, trueTerminal); ilg.MarkLabel(trueTerminal); ilg.Emit(OpCodes.Ldc_I4_1); - bm.MarkLabel(ilg, endLabel); ilg.MarkLabel(endLabel); } - private static void EmitBranchToTrueTerminal(FleeILGenerator ilg, Label trueTerminal, BranchManager bm) + private static void EmitBranchToTrueTerminal(FleeILGenerator ilg, Label trueTerminal) { - if (ilg.IsTemp == true) - { - bm.AddBranch(ilg, trueTerminal); - ilg.Emit(OpCodes.Brtrue_S, trueTerminal); - } - else if (bm.IsLongBranch(ilg, trueTerminal) == false) - { - ilg.Emit(OpCodes.Brtrue_S, trueTerminal); - } - else - { - ilg.Emit(OpCodes.Brtrue, trueTerminal); - } + ilg.EmitBranchTrue(trueTerminal); } public override System.Type ResultType => typeof(bool); diff --git a/src/Flee.NetStandard20/ExpressionElements/LogicalBitwise/AndOr.cs b/src/Flee.NetStandard20/ExpressionElements/LogicalBitwise/AndOr.cs index 9ae9ceb..fe70a3b 100644 --- a/src/Flee.NetStandard20/ExpressionElements/LogicalBitwise/AndOr.cs +++ b/src/Flee.NetStandard20/ExpressionElements/LogicalBitwise/AndOr.cs @@ -79,20 +79,6 @@ private void DoEmitLogical(FleeILGenerator ilg, IServiceProvider services) { // We have to do a 'fake' emit so we can get the positions of the labels ShortCircuitInfo info = new ShortCircuitInfo(); - // Create a temporary IL generator - FleeILGenerator ilgTemp = this.CreateTempFleeILGenerator(ilg); - - // We have to make sure that the label count for the temp FleeILGenerator matches our real FleeILGenerator - Utility.SyncFleeILGeneratorLabels(ilg, ilgTemp); - // Do the fake emit - this.EmitLogical(ilgTemp, info, services); - - // Clear everything except the label positions - info.ClearTempState(); - - info.Branches.ComputeBranches(); - - Utility.SyncFleeILGeneratorLabels(ilgTemp, ilg); // Do the real emit this.EmitLogical(ilg, info, services); @@ -112,7 +98,7 @@ private void DoEmitLogical(FleeILGenerator ilg, IServiceProvider services) private void EmitLogical(FleeILGenerator ilg, ShortCircuitInfo info, IServiceProvider services) { // We always have an end label - info.Branches.GetLabel(OurEndLabelKey, ilg); + Label endLabel = ilg.DefineLabel(); // Populate our data structures this.PopulateData(info); @@ -124,9 +110,9 @@ private void EmitLogical(FleeILGenerator ilg, ShortCircuitInfo info, IServicePro ExpressionElement terminalOperand = (ExpressionElement)info.Operands.Pop(); // Emit it EmitOperand(terminalOperand, info, ilg, services); - // And jump to the end - Label endLabel = info.Branches.FindLabel(OurEndLabelKey); - ilg.Emit(OpCodes.Br_S, endLabel); + + // only 1-3 opcodes, always a short branch + ilg.EmitBranch(endLabel); // Emit our true/false terminals EmitTerminals(info, ilg, endLabel); @@ -163,61 +149,14 @@ private static void EmitLogicalShortCircuit(FleeILGenerator ilg, ShortCircuitInf private static void EmitBranch(AndOrElement op, FleeILGenerator ilg, Label target, ShortCircuitInfo info) { - if (ilg.IsTemp == true) - { - info.Branches.AddBranch(ilg, target); - - // Temp mode; just emit a short branch and return - OpCode shortBranch = GetBranchOpcode(op, false); - ilg.Emit(shortBranch, target); - - return; - } - - // Emit the proper branch opcode - - // Determine if it is a long branch - bool longBranch = info.Branches.IsLongBranch(ilg, target); - // Get the branch opcode - OpCode brOpcode = GetBranchOpcode(op, longBranch); - - // Emit the branch - ilg.Emit(brOpcode, target); - } - - /// - /// Emit a short/long branch for an And/Or element - /// - /// - /// - /// - private static OpCode GetBranchOpcode(AndOrElement op, bool longBranch) - { if (op._myOperation == AndOrOperation.And) - { - if (longBranch == true) - { - return OpCodes.Brfalse; - } - else - { - return OpCodes.Brfalse_S; - } - } + ilg.EmitBranchFalse(target); else - { - if (longBranch == true) - { - return OpCodes.Brtrue; - } - else - { - return OpCodes.Brtrue_S; - } - } + ilg.EmitBranchTrue(target); } + /// /// Get the label for a short-circuit /// @@ -316,14 +255,11 @@ private void Pop(Stack operands, Stack operators) private static void EmitOperand(ExpressionElement operand, ShortCircuitInfo info, FleeILGenerator ilg, IServiceProvider services) { // Is this operand the target of a label? - if (info.Branches.HasLabel(operand) == true) + if (info.HasLabel(operand) == true) { // Yes, so mark it - Label leftLabel = info.Branches.FindLabel(operand); + Label leftLabel = info.FindLabel(operand); ilg.MarkLabel(leftLabel); - - // Note the label's position - MarkBranchTarget(info, leftLabel, ilg); } // Emit the operand @@ -339,53 +275,41 @@ private static void EmitOperand(ExpressionElement operand, ShortCircuitInfo info private static void EmitTerminals(ShortCircuitInfo info, FleeILGenerator ilg, Label endLabel) { // Emit the false case if it was used - if (info.Branches.HasLabel(OurFalseTerminalKey) == true) + if (info.HasLabel(OurFalseTerminalKey) == true) { - Label falseLabel = info.Branches.FindLabel(OurFalseTerminalKey); + Label falseLabel = info.FindLabel(OurFalseTerminalKey); // Mark the label and note its position ilg.MarkLabel(falseLabel); - MarkBranchTarget(info, falseLabel, ilg); ilg.Emit(OpCodes.Ldc_I4_0); // If we also have a true terminal, then skip over it - if (info.Branches.HasLabel(OurTrueTerminalKey) == true) + if (info.HasLabel(OurTrueTerminalKey) == true) { + // only 1-3 opcodes, always a short branch ilg.Emit(OpCodes.Br_S, endLabel); } } // Emit the true case if it was used - if (info.Branches.HasLabel(OurTrueTerminalKey) == true) + if (info.HasLabel(OurTrueTerminalKey) == true) { - Label trueLabel = info.Branches.FindLabel(OurTrueTerminalKey); + Label trueLabel = info.FindLabel(OurTrueTerminalKey); // Mark the label and note its position ilg.MarkLabel(trueLabel); - MarkBranchTarget(info, trueLabel, ilg); ilg.Emit(OpCodes.Ldc_I4_1); } } - /// - /// Note a label's position if we are in mark mode - /// - /// - /// - /// - private static void MarkBranchTarget(ShortCircuitInfo info, Label target, FleeILGenerator ilg) - { - if (ilg.IsTemp == true) - { - info.Branches.MarkLabel(ilg, target); - } - } private static Label GetLabel(object key, FleeILGenerator ilg, ShortCircuitInfo info) { - return info.Branches.GetLabel(key, ilg); + if (info.HasLabel(key)) + return info.FindLabel(key); + return info.AddLabel(key, ilg.DefineLabel()); } /// diff --git a/src/Flee.NetStandard20/InternalTypes/BranchManager.cs b/src/Flee.NetStandard20/InternalTypes/BranchManager.cs index 9e1b18c..1e01d2a 100644 --- a/src/Flee.NetStandard20/InternalTypes/BranchManager.cs +++ b/src/Flee.NetStandard20/InternalTypes/BranchManager.cs @@ -9,32 +9,51 @@ namespace Flee.InternalTypes [Obsolete("Manages branch information and allows us to determine if we should emit a short or long branch")] internal class BranchManager { - private IList MyBranchInfos; + private readonly IList MyBranchInfos; - private IDictionary MyKeyLabelMap; public BranchManager() { MyBranchInfos = new List(); - MyKeyLabelMap = new Dictionary(); } /// - /// Determine whether to use short or long branches + /// check if any long branches exist /// - /// - public void ComputeBranches() + /// + public bool HasLongBranches() { - List betweenBranches = new List(); - foreach (BranchInfo bi in MyBranchInfos) { - betweenBranches.Clear(); + if (bi.ComputeIsLongBranch()) return true; + } + return false; + } - // Find any branches between the start and end locations of this branch - this.FindBetweenBranches(bi, betweenBranches); + /// + /// Determine whether to use short or long branches. + /// This advances the ilg offset with No-op to adjust + /// for the long branches needed. + /// + /// + public bool ComputeBranches() + { + // + // we need to iterate in reverse order of the + // starting location, as branch between our + // branch could push our branch to a long branch. + // + for( var idx=MyBranchInfos.Count-1; idx >= 0; idx--) + { + var bi = MyBranchInfos[idx]; - // Count the number of long branches in the above set - int longBranchesBetween = this.CountLongBranches(betweenBranches); + // count long branches between + int longBranchesBetween = 0; + for( var ii=idx+1; ii < MyBranchInfos.Count; ii++) + { + var bi2 = MyBranchInfos[ii]; + if (bi2.IsBetween(bi) && bi2.ComputeIsLongBranch()) + ++longBranchesBetween; + } // Adjust the branch as necessary bi.AdjustForLongBranchesBetween(longBranchesBetween); @@ -54,62 +73,31 @@ public void ComputeBranches() // Keep a tally of the number of long branches longBranchCount += Convert.ToInt32(bi.IsLongBranch); } - } - /// - /// Count the number of long branches in a set - /// - /// - /// - /// - private int CountLongBranches(ICollection dest) - { - int count = 0; - - foreach (BranchInfo bi in dest) - { - count += Convert.ToInt32(bi.ComputeIsLongBranch()); - } - - return count; + return (longBranchCount > 0); } - /// - /// Find all the branches between the start and end locations of a target branch - /// - /// - /// - /// - private void FindBetweenBranches(BranchInfo target, ICollection dest) - { - foreach (BranchInfo bi in MyBranchInfos) - { - if (bi.IsBetween(target) == true) - { - dest.Add(bi); - } - } - } /// /// Determine if a branch from a point to a label will be long /// /// - /// /// /// - public bool IsLongBranch(FleeILGenerator ilg, Label target) + public bool IsLongBranch(FleeILGenerator ilg) { ILLocation startLoc = new ILLocation(ilg.Length); - BranchInfo bi = new BranchInfo(startLoc, target); - int index = MyBranchInfos.IndexOf(bi); - if (index > -1 && index < MyBranchInfos.Count) + foreach (var bi in MyBranchInfos) { - bi = MyBranchInfos[index]; + if (bi.Equals(startLoc)) + return bi.IsLongBranch; } - return bi.IsLongBranch; + // we don't really know since this branch didn't exist. + // we could throw an exceptio but + // do a long branch to be safe. + return true; } /// @@ -123,48 +111,10 @@ public void AddBranch(FleeILGenerator ilg, Label target) ILLocation startLoc = new ILLocation(ilg.Length); BranchInfo bi = new BranchInfo(startLoc, target); + // branches will be sorted in order MyBranchInfos.Add(bi); } - /// - /// Get a label by a key - /// - /// - /// - /// - public Label FindLabel(object key) - { - return MyKeyLabelMap[key]; - } - - /// - /// Get a label by a key. Create the label if it is not present. - /// - /// - /// - /// - /// - public Label GetLabel(object key, FleeILGenerator ilg) - { - Label lbl; - if (MyKeyLabelMap.TryGetValue(key, out lbl) == false) - { - lbl = ilg.DefineLabel(); - MyKeyLabelMap.Add(key, lbl); - } - return lbl; - } - - /// - /// Determines if we have a label for a key - /// - /// - /// - /// - public bool HasLabel(object key) - { - return MyKeyLabelMap.ContainsKey(key); - } /// /// Set the position for a label @@ -267,12 +217,13 @@ public int CompareTo(ILLocation other) } [Obsolete("Represents a branch from a start location to an end location")] - internal class BranchInfo : IEquatable + internal class BranchInfo { private readonly ILLocation _myStart; private readonly ILLocation _myEnd; private Label _myLabel; private bool _myIsLongBranch; + public BranchInfo(ILLocation startLocation, Label endLabel) { _myStart = startLocation; @@ -283,6 +234,9 @@ public BranchInfo(ILLocation startLocation, Label endLabel) public void AdjustForLongBranches(int longBranchCount) { _myStart.AdjustForLongBranch(longBranchCount); + // end not necessarily needed once we determine + // if this is long, but keep it accurate anyway. + _myEnd.AdjustForLongBranch(longBranchCount); } public void BakeIsLongBranch() @@ -313,13 +267,16 @@ public void Mark(Label target, int position) } } - public bool Equals1(BranchInfo other) - { - return _myStart.Equals1(other._myStart) && _myLabel.Equals(other._myLabel); - } - bool System.IEquatable.Equals(BranchInfo other) + /// + /// We only need to compare the start point. Can only have a single + /// brach from the exact address, so if label doesn't match we have + /// bigger problems. + /// + /// + /// + public bool Equals(ILLocation start) { - return Equals1(other); + return _myStart.Equals1(start); } public override string ToString() diff --git a/src/Flee.NetStandard20/InternalTypes/Expression.cs b/src/Flee.NetStandard20/InternalTypes/Expression.cs index 5345f75..2f58f0b 100644 --- a/src/Flee.NetStandard20/InternalTypes/Expression.cs +++ b/src/Flee.NetStandard20/InternalTypes/Expression.cs @@ -87,13 +87,20 @@ private void Compile(string expression, ExpressionOptions options) // Emit the IL rootElement.Emit(ilg, services); + if (ilg.NeedsSecondPass()) + { + // second pass required due to long branches. + dm = this.CreateDynamicMethod(); + ilg.PrepareSecondPass(dm.GetILGenerator()); + rootElement.Emit(ilg, services); + } ilg.ValidateLength(); // Emit to an assembly if required if (options.EmitToAssembly == true) { - EmitToAssembly(rootElement, services); + EmitToAssembly(ilg, rootElement, services); } Type delegateType = typeof(ExpressionEvaluator<>).MakeGenericType(typeof(T)); @@ -124,7 +131,14 @@ private void AddServices(IServiceContainer dest) dest.AddService(typeof(ExpressionInfo), _myInfo); } - private static void EmitToAssembly(ExpressionElement rootElement, IServiceContainer services) + /// + /// Emit to an assembly. We've already computed long branches at this point, + /// so we emit as a second pass + /// + /// + /// + /// + private static void EmitToAssembly(FleeILGenerator ilg, ExpressionElement rootElement, IServiceContainer services) { AssemblyName assemblyName = new AssemblyName(EmitAssemblyName); @@ -135,7 +149,8 @@ private static void EmitToAssembly(ExpressionElement rootElement, IServiceContai MethodBuilder mb = moduleBuilder.DefineGlobalMethod("Evaluate", MethodAttributes.Public | MethodAttributes.Static, typeof(T), new Type[] { typeof(object),typeof(ExpressionContext),typeof(VariableCollection)}); - FleeILGenerator ilg = new FleeILGenerator(mb.GetILGenerator()); + // already emitted once for local use, + ilg.PrepareSecondPass(mb.GetILGenerator()); rootElement.Emit(ilg, services); diff --git a/src/Flee.NetStandard20/InternalTypes/FleeILGenerator.cs b/src/Flee.NetStandard20/InternalTypes/FleeILGenerator.cs index c294fc4..f9a7b6b 100644 --- a/src/Flee.NetStandard20/InternalTypes/FleeILGenerator.cs +++ b/src/Flee.NetStandard20/InternalTypes/FleeILGenerator.cs @@ -9,17 +9,21 @@ namespace Flee.InternalTypes { internal class FleeILGenerator { - private readonly ILGenerator _myIlGenerator; + private ILGenerator _myIlGenerator; private int _myLength; private int _myLabelCount; private readonly Dictionary _localBuilderTemp; - private readonly bool _myIsTemp; - public FleeILGenerator(ILGenerator ilg, int startLength = 0, bool isTemp = false) + private int _myPass; + private int _brContext; + private BranchManager _bm; + + public FleeILGenerator(ILGenerator ilg) { _myIlGenerator = ilg; _localBuilderTemp = new Dictionary(); - _myIsTemp = isTemp; - _myLength = startLength; + _myLength = 0; + _myPass = 1; + _bm = new BranchManager(); } public int GetTempLocalIndex(Type localType) @@ -35,6 +39,30 @@ public int GetTempLocalIndex(Type localType) return local.LocalIndex; } + /// + /// after first pass, check for long branches. + /// If any, we need to generate again. + /// + /// + public bool NeedsSecondPass() + { + return _bm.HasLongBranches(); + } + + /// + /// need a new ILGenerator for 2nd pass. This can also + /// get called for a 3rd pass when emitting to assembly. + /// + /// + public void PrepareSecondPass(ILGenerator ilg) + { + _bm.ComputeBranches(); + _localBuilderTemp.Clear(); + _myIlGenerator = ilg; + _myLength = 0; + _myPass++; + } + public void Emit(OpCode op) { this.RecordOpcode(op); @@ -119,17 +147,72 @@ public void Emit(OpCode op, Label arg) _myIlGenerator.Emit(op, arg); } + public void EmitBranch(Label arg) + { + if (_myPass == 1) + { + _bm.AddBranch(this, arg); + Emit(OpCodes.Br_S, arg); + } + else if (_bm.IsLongBranch(this) == false) + { + Emit(OpCodes.Br_S, arg); + } + else + { + Emit(OpCodes.Br, arg); + } + } + + public void EmitBranchFalse(Label arg) + { + if (_myPass == 1) + { + _bm.AddBranch(this, arg); + Emit(OpCodes.Brfalse_S, arg); + } + else if (_bm.IsLongBranch(this) == false) + { + Emit(OpCodes.Brfalse_S, arg); + } + else + { + Emit(OpCodes.Brfalse, arg); + } + } + + public void EmitBranchTrue(Label arg) + { + if (_myPass == 1) + { + _bm.AddBranch(this, arg); + Emit(OpCodes.Brtrue_S, arg); + } + else if (_bm.IsLongBranch(this) == false) + { + Emit(OpCodes.Brtrue_S, arg); + } + else + { + Emit(OpCodes.Brtrue, arg); + } + } + public void MarkLabel(Label lbl) { _myIlGenerator.MarkLabel(lbl); + _bm.MarkLabel(this, lbl); } + public Label DefineLabel() { _myLabelCount += 1; - return _myIlGenerator.DefineLabel(); + var label = _myIlGenerator.DefineLabel(); + return label; } + public LocalBuilder DeclareLocal(Type localType) { return _myIlGenerator.DeclareLocal(localType); @@ -185,7 +268,5 @@ public void ValidateLength() public int LabelCount => _myLabelCount; private int ILGeneratorLength => Utility.GetILGeneratorLength(_myIlGenerator); - - public bool IsTemp => _myIsTemp; } } diff --git a/src/Flee.NetStandard20/InternalTypes/Miscellaneous.cs b/src/Flee.NetStandard20/InternalTypes/Miscellaneous.cs index a5fbbcd..5cce032 100644 --- a/src/Flee.NetStandard20/InternalTypes/Miscellaneous.cs +++ b/src/Flee.NetStandard20/InternalTypes/Miscellaneous.cs @@ -238,7 +238,7 @@ private float ComputeScoreInternal(ParameterInfo[] parameters, Type[] argTypes) // Our score is the average of the scores of each parameter. The lower the score, the better the match. int sum = ComputeSum(parameters, argTypes); - return sum / argTypes.Length; + return (float)sum / (float)argTypes.Length; } private static int ComputeSum(ParameterInfo[] parameters, Type[] argTypes) @@ -463,13 +463,13 @@ internal class ShortCircuitInfo public Stack Operands; public Stack Operators; + private Dictionary Labels; - public BranchManager Branches; public ShortCircuitInfo() { this.Operands = new Stack(); this.Operators = new Stack(); - this.Branches = new BranchManager(); + this.Labels = new Dictionary(); } public void ClearTempState() @@ -477,6 +477,22 @@ public void ClearTempState() this.Operands.Clear(); this.Operators.Clear(); } + + public Label AddLabel(object key, Label lbl) + { + Labels.Add(key, lbl); + return lbl; + } + + public bool HasLabel(object key) + { + return Labels.ContainsKey(key); + } + + public Label FindLabel(object key) + { + return Labels[key]; + } } [Obsolete("Wraps an expression element so that it is loaded from a local slot")] diff --git a/src/Flee.NetStandard20/InternalTypes/Utility.cs b/src/Flee.NetStandard20/InternalTypes/Utility.cs index 3de1ca5..3a7ec97 100644 --- a/src/Flee.NetStandard20/InternalTypes/Utility.cs +++ b/src/Flee.NetStandard20/InternalTypes/Utility.cs @@ -42,11 +42,15 @@ public static void EmitStoreLocal(FleeILGenerator ilg, int index) break; } } - else + else if (index < 256) { - Debug.Assert(index < 256, "local index too large"); ilg.Emit(OpCodes.Stloc_S, Convert.ToByte(index)); } + else + { + Debug.Assert(index < 65535, "local index too large"); + ilg.Emit(OpCodes.Stloc, unchecked((short)Convert.ToUInt16(index))); + } } public static void EmitLoadLocal(FleeILGenerator ilg, int index) @@ -71,11 +75,15 @@ public static void EmitLoadLocal(FleeILGenerator ilg, int index) break; } } - else + else if (index < 256) { - Debug.Assert(index < 256, "local index too large"); ilg.Emit(OpCodes.Ldloc_S, Convert.ToByte(index)); } + else + { + Debug.Assert(index < 65535, "local index too large"); + ilg.Emit(OpCodes.Ldloc, unchecked((short)Convert.ToUInt16(index))); + } } public static void EmitLoadLocalAddress(FleeILGenerator ilg, int index) @@ -179,13 +187,7 @@ public static void EmitArrayStore(FleeILGenerator ilg, Type elementType) } } - public static void SyncFleeILGeneratorLabels(FleeILGenerator source, FleeILGenerator target) - { - while (source.LabelCount != target.LabelCount) - { - target.DefineLabel(); - } - } + public static bool IsIntegralType(Type t) { diff --git a/src/Flee.NetStandard20/Parsing/ExpressionParser.cs b/src/Flee.NetStandard20/Parsing/ExpressionParser.cs index c9f5025..911e9cd 100644 --- a/src/Flee.NetStandard20/Parsing/ExpressionParser.cs +++ b/src/Flee.NetStandard20/Parsing/ExpressionParser.cs @@ -12,7 +12,7 @@ namespace Flee.Parsing /// /// A token stream parser. /// - internal class ExpressionParser : RecursiveDescentParser + internal class ExpressionParser : StackParser { private enum SynteticPatterns { diff --git a/src/Flee.NetStandard20/Parsing/ExpressionTokenizer.cs b/src/Flee.NetStandard20/Parsing/ExpressionTokenizer.cs index 8bb28e0..5ade82b 100644 --- a/src/Flee.NetStandard20/Parsing/ExpressionTokenizer.cs +++ b/src/Flee.NetStandard20/Parsing/ExpressionTokenizer.cs @@ -121,13 +121,13 @@ private void CreatePatterns() customPattern = new RealPattern(Convert.ToInt32(ExpressionConstants.REAL), "REAL", TokenPattern.PatternType.REGEXP, "\\d{0}\\{1}\\d+([e][+-]\\d{{1,3}})?(d|f|m)?"); customPattern.Initialize(Convert.ToInt32(ExpressionConstants.REAL), "REAL", TokenPattern.PatternType.REGEXP, "\\d{0}\\{1}\\d+([e][+-]\\d{{1,3}})?(d|f|m)?", _myContext); - AddPattern(customPattern); + AddPattern(customPattern, false); pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.STRING_LITERAL), "STRING_LITERAL", TokenPattern.PatternType.REGEXP, "\"([^\"\\r\\n\\\\]|\\\\u[0-9a-f]{4}|\\\\[\\\\\"'trn])*\""); - AddPattern(pattern); + AddPattern(pattern, false); pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.CHAR_LITERAL), "CHAR_LITERAL", TokenPattern.PatternType.REGEXP, "'([^'\\r\\n\\\\]|\\\\u[0-9a-f]{4}|\\\\[\\\\\"'trn])'"); - AddPattern(pattern); + AddPattern(pattern, false); pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.TRUE), "TRUE", TokenPattern.PatternType.STRING, "True"); AddPattern(pattern); @@ -145,7 +145,7 @@ private void CreatePatterns() AddPattern(pattern); pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.TIMESPAN), "TIMESPAN", TokenPattern.PatternType.REGEXP, "##(\\d+\\.)?\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,7})?)?#"); - AddPattern(pattern); + AddPattern(pattern, false); pattern = new TokenPattern(Convert.ToInt32(ExpressionConstants.DATETIME), "DATETIME", TokenPattern.PatternType.REGEXP, "#[^#]+#"); AddPattern(pattern); diff --git a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/ParserCreationException.cs b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/ParserCreationException.cs index 15b7071..8de38cb 100644 --- a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/ParserCreationException.cs +++ b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/ParserCreationException.cs @@ -63,6 +63,9 @@ public enum ErrorType * ambiguities that cannot be resolved. */ INHERENT_AMBIGUITY + + + } private readonly ErrorType _type; diff --git a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/RecursiveDescentParser.cs b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/RecursiveDescentParser.cs index 8e4f336..370c676 100644 --- a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/RecursiveDescentParser.cs +++ b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/RecursiveDescentParser.cs @@ -15,6 +15,8 @@ namespace Flee.Parsing.grammatica_1._5.alpha2.PerCederberg.Grammatica.Runtime */ internal class RecursiveDescentParser : Parser { + private int _stackdepth = 0; + public RecursiveDescentParser(TextReader input) : base(input) { } @@ -79,6 +81,7 @@ public override void Prepare() protected override Node ParseStart() { + _stackdepth = 0; var node = ParsePattern(GetStartPattern()); var token = PeekToken(0); if (token != null) @@ -94,22 +97,37 @@ protected override Node ParseStart() return node; } + private Node ParsePattern(ProductionPattern pattern) { - var defaultAlt = pattern.DefaultAlternative; - for (int i = 0; i < pattern.Count; i++) + _stackdepth++; + + if (_stackdepth > 200) + { + throw new System.StackOverflowException(); + } + + try { - var alt = pattern[i]; - if (defaultAlt != alt && IsNext(alt)) + var defaultAlt = pattern.DefaultAlternative; + for (int i = 0; i < pattern.Count; i++) + { + var alt = pattern[i]; + if (defaultAlt != alt && IsNext(alt)) + { + return ParseAlternative(alt); + } + } + if (defaultAlt == null || !IsNext(defaultAlt)) { - return ParseAlternative(alt); + ThrowParseException(FindUnion(pattern)); } + return ParseAlternative(defaultAlt); } - if (defaultAlt == null || !IsNext(defaultAlt)) + finally { - ThrowParseException(FindUnion(pattern)); + _stackdepth--; } - return ParseAlternative(defaultAlt); } private Node ParseAlternative(ProductionPatternAlternative alt) @@ -537,6 +555,7 @@ private LookAheadSet FindUnion(ProductionPattern pattern) return result; } + private void ThrowParseException(LookAheadSet set) { ArrayList list = new ArrayList(); diff --git a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/StackParser.cs b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/StackParser.cs new file mode 100644 index 0000000..957dd34 --- /dev/null +++ b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/StackParser.cs @@ -0,0 +1,764 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Flee.Parsing.grammatica_1._5.alpha2.PerCederberg.Grammatica.Runtime +{ + /** + * based on recursive descent parser, this implementation removes recursion + * and uses a stack instead. This parser handles LL(n) grammars, + * selecting the appropriate pattern to parse based on the next few + * tokens. + */ + internal class StackParser : Parser + { + /** + * this is the parser state that is pushed onto the stack, simulating + * the variable state needed in recursive version. Some variables + * substitute for execution position, such as validnext, so patterns + * are processed in the proper order. + */ + internal class ParseState + { + /** + * pattern for this state + */ + internal ProductionPattern pattern; + /** + * index of the alt pattern we are currently checking + */ + internal int altindex; + + /** + * index into the list of elements for the alt pattern + */ + internal int elementindex; + + /** + * index to the token we are processing. + */ + internal int tokenindex; + + /** + * The node for current state + */ + internal Node node; + + /** + * true if we already checked IsNext on the current pattern + * so we should not call it again + */ + internal bool validnext; + + } + + + public StackParser(TextReader input) : base(input) + { + } + + public StackParser(TextReader input, Analyzer analyzer) + : base(input, analyzer) + { + } + + public StackParser(Tokenizer tokenizer) + : base(tokenizer) + { + } + + public StackParser(Tokenizer tokenizer, + Analyzer analyzer) + : base(tokenizer, analyzer) + { + } + + public override void AddPattern(ProductionPattern pattern) + { + + // Check for empty matches + if (pattern.IsMatchingEmpty()) + { + throw new ParserCreationException( + ParserCreationException.ErrorType.INVALID_PRODUCTION, + pattern.Name, + "zero elements can be matched (minimum is one)"); + } + + // Check for left-recusive patterns + if (pattern.IsLeftRecursive()) + { + throw new ParserCreationException( + ParserCreationException.ErrorType.INVALID_PRODUCTION, + pattern.Name, + "left recursive patterns are not allowed"); + } + + // Add pattern + base.AddPattern(pattern); + } + + public override void Prepare() + { + // Performs production pattern checks + base.Prepare(); + SetInitialized(false); + + // Calculate production look-ahead sets + var e = GetPatterns().GetEnumerator(); + while (e.MoveNext()) + { + CalculateLookAhead((ProductionPattern)e.Current); + } + + // Set initialized flag + SetInitialized(true); + } + + protected override Node ParseStart() + { + var node = ParsePatterns(GetStartPattern()); + + + var token = PeekToken(0); + if (token != null) + { + var list = new ArrayList(1) { "" }; + throw new ParseException( + ParseException.ErrorType.UNEXPECTED_TOKEN, + token.ToShortString(), + list, + token.StartLine, + token.StartColumn); + } + return node; + } + + + + private ParseState NewState(ProductionPattern pattern) + { + return new ParseState() + { + pattern = pattern, + altindex = 0, + elementindex = 0, + tokenindex = 0, + node = null, + validnext = false + }; + } + + /// + /// parse patterns using a stack. The stack is local to this method, since the parser + /// is a singleton and may be parsing expressions from multiple threads, so cannot + /// use the object to store our stack. + /// + /// + /// + private Node ParsePatterns(ProductionPattern start) + { + Stack _stack = new Stack(); + _stack.Push(NewState(start)); + + while (_stack.Count > 0) + { + ParseState state = _stack.Peek(); + ProductionPattern pattern = state.pattern; + var defaultAlt = pattern.DefaultAlternative; + ProductionPattern nextpattern = null; + while (state.altindex < pattern.Count) + { + var alt = pattern[state.altindex]; + if (state.validnext || (defaultAlt != alt && IsNext(alt))) + { + state.validnext = true; + nextpattern = ParseAlternative(state, alt); + break; + } + else + { + state.altindex++; + state.validnext = false; + } + } + + // check if completed pass through alt patterns. try default + if (state.altindex >= pattern.Count) + { + if (!state.validnext && (defaultAlt == null || !IsNext(defaultAlt))) + { + ThrowParseException(FindUnion(pattern)); + } + else + { + state.validnext = true; + nextpattern = ParseAlternative(state, defaultAlt); + } + } + + if (nextpattern != null) + { + _stack.Push(NewState(nextpattern)); + } + + // we finished current pattern, so back up to previous state. + else + { + // if we have a node set, add it to the parent + var child = state.node; + _stack.Pop(); + if (_stack.Count == 0) + { + // back to top, can return our result, which is top node + return child; + } + state = _stack.Peek(); + AddNode((Production)state.node, child); + } + } + + // should never get here, but must show we return something. + return null; + } + + /** + * return the pattern to push onto stack and process next. + */ + private ProductionPattern ParseAlternative(ParseState state, ProductionPatternAlternative alt) + { + if (state.node == null) + { + state.node = NewProduction(alt.Pattern); + state.elementindex = 0; + EnterNode(state.node); + } + while (state.elementindex < alt.Count) + { + try + { + var pattern = ParseElement(state, alt[state.elementindex]); + if (pattern == null) + state.elementindex++; + else + return pattern; + } + catch (ParseException e) + { + AddError(e, true); + NextToken(); + } + } + + state.node = ExitNode(state.node); + return null; + } + + private ProductionPattern ParseElement(ParseState state, + ProductionPatternElement elem) + { + for (int i = state.tokenindex; i < elem.MaxCount; i++) + { + if (i < elem.MinCount || IsNext(elem)) + { + Node child; + if (elem.IsToken()) + { + child = NextToken(elem.Id); + EnterNode(child); + AddNode((Production)state.node, ExitNode(child)); + } + else + { + // continue from next token when we return + state.tokenindex = i + 1; + // return to start processing the new pattern at this state + return GetPattern(elem.Id); ; + } + } + else + { + break; + } + } + // + // we completed processing this element + state.tokenindex = 0; + return null; + } + + private bool IsNext(ProductionPattern pattern) + { + LookAheadSet set = pattern.LookAhead; + + if (set == null) + { + return false; + } + else + { + return set.IsNext(this); + } + } + + private bool IsNext(ProductionPatternAlternative alt) + { + LookAheadSet set = alt.LookAhead; + + if (set == null) + { + return false; + } + else + { + return set.IsNext(this); + } + } + + private bool IsNext(ProductionPatternElement elem) + { + LookAheadSet set = elem.LookAhead; + + if (set != null) + { + return set.IsNext(this); + } + else if (elem.IsToken()) + { + return elem.IsMatch(PeekToken(0)); + } + else + { + return IsNext(GetPattern(elem.Id)); + } + } + + private void CalculateLookAhead(ProductionPattern pattern) + { + ProductionPatternAlternative alt; + LookAheadSet previous = new LookAheadSet(0); + int length = 1; + int i; + CallStack stack = new CallStack(); + + // Calculate simple look-ahead + stack.Push(pattern.Name, 1); + var result = new LookAheadSet(1); + var alternatives = new LookAheadSet[pattern.Count]; + for (i = 0; i < pattern.Count; i++) + { + alt = pattern[i]; + alternatives[i] = FindLookAhead(alt, 1, 0, stack, null); + alt.LookAhead = alternatives[i]; + result.AddAll(alternatives[i]); + } + if (pattern.LookAhead == null) + { + pattern.LookAhead = result; + } + var conflicts = FindConflicts(pattern, 1); + + // Resolve conflicts + while (conflicts.Size() > 0) + { + length++; + stack.Clear(); + stack.Push(pattern.Name, length); + conflicts.AddAll(previous); + for (i = 0; i < pattern.Count; i++) + { + alt = pattern[i]; + if (alternatives[i].Intersects(conflicts)) + { + alternatives[i] = FindLookAhead(alt, + length, + 0, + stack, + conflicts); + alt.LookAhead = alternatives[i]; + } + if (alternatives[i].Intersects(conflicts)) + { + if (pattern.DefaultAlternative == null) + { + pattern.DefaultAlternative = alt; + } + else if (pattern.DefaultAlternative != alt) + { + result = alternatives[i].CreateIntersection(conflicts); + ThrowAmbiguityException(pattern.Name, + null, + result); + } + } + } + previous = conflicts; + conflicts = FindConflicts(pattern, length); + } + + // Resolve conflicts inside rules + for (i = 0; i < pattern.Count; i++) + { + CalculateLookAhead(pattern[i], 0); + } + } + + private void CalculateLookAhead(ProductionPatternAlternative alt, + int pos) + { + LookAheadSet previous = new LookAheadSet(0); + int length = 1; + + // Check trivial cases + if (pos >= alt.Count) + { + return; + } + + // Check for non-optional element + var pattern = alt.Pattern; + var elem = alt[pos]; + if (elem.MinCount == elem.MaxCount) + { + CalculateLookAhead(alt, pos + 1); + return; + } + + // Calculate simple look-aheads + var first = FindLookAhead(elem, 1, new CallStack(), null); + var follow = FindLookAhead(alt, 1, pos + 1, new CallStack(), null); + + // Resolve conflicts + var location = "at position " + (pos + 1); + var conflicts = FindConflicts(pattern.Name, + location, + first, + follow); + while (conflicts.Size() > 0) + { + length++; + conflicts.AddAll(previous); + first = FindLookAhead(elem, + length, + new CallStack(), + conflicts); + follow = FindLookAhead(alt, + length, + pos + 1, + new CallStack(), + conflicts); + first = first.CreateCombination(follow); + elem.LookAhead = first; + if (first.Intersects(conflicts)) + { + first = first.CreateIntersection(conflicts); + ThrowAmbiguityException(pattern.Name, location, first); + } + previous = conflicts; + conflicts = FindConflicts(pattern.Name, + location, + first, + follow); + } + + // Check remaining elements + CalculateLookAhead(alt, pos + 1); + } + + private LookAheadSet FindLookAhead(ProductionPattern pattern, + int length, + CallStack stack, + LookAheadSet filter) + { + // Check for infinite loop + if (stack.Contains(pattern.Name, length)) + { + throw new ParserCreationException( + ParserCreationException.ErrorType.INFINITE_LOOP, + pattern.Name, + (String)null); + } + + // Find pattern look-ahead + stack.Push(pattern.Name, length); + var result = new LookAheadSet(length); + for (int i = 0; i < pattern.Count; i++) + { + var temp = FindLookAhead(pattern[i], + length, + 0, + stack, + filter); + result.AddAll(temp); + } + stack.Pop(); + + return result; + } + + private LookAheadSet FindLookAhead(ProductionPatternAlternative alt, + int length, + int pos, + CallStack stack, + LookAheadSet filter) + { + LookAheadSet follow; + // Check trivial cases + if (length <= 0 || pos >= alt.Count) + { + return new LookAheadSet(0); + } + + // Find look-ahead for this element + var first = FindLookAhead(alt[pos], length, stack, filter); + if (alt[pos].MinCount == 0) + { + first.AddEmpty(); + } + + // Find remaining look-ahead + if (filter == null) + { + length -= first.GetMinLength(); + if (length > 0) + { + follow = FindLookAhead(alt, length, pos + 1, stack, null); + first = first.CreateCombination(follow); + } + } + else if (filter.IsOverlap(first)) + { + var overlaps = first.CreateOverlaps(filter); + length -= overlaps.GetMinLength(); + filter = filter.CreateFilter(overlaps); + follow = FindLookAhead(alt, length, pos + 1, stack, filter); + first.RemoveAll(overlaps); + first.AddAll(overlaps.CreateCombination(follow)); + } + + return first; + } + + private LookAheadSet FindLookAhead(ProductionPatternElement elem, + int length, + CallStack stack, + LookAheadSet filter) + { + // Find initial element look-ahead + var first = FindLookAhead(elem, length, 0, stack, filter); + var result = new LookAheadSet(length); + result.AddAll(first); + if (filter == null || !filter.IsOverlap(result)) + { + return result; + } + + // Handle element repetitions + if (elem.MaxCount == Int32.MaxValue) + { + first = first.CreateRepetitive(); + } + var max = elem.MaxCount; + if (length < max) + { + max = length; + } + for (int i = 1; i < max; i++) + { + first = first.CreateOverlaps(filter); + if (first.Size() <= 0 || first.GetMinLength() >= length) + { + break; + } + var follow = FindLookAhead(elem, + length, + 0, + stack, + filter.CreateFilter(first)); + first = first.CreateCombination(follow); + result.AddAll(first); + } + + return result; + } + + private LookAheadSet FindLookAhead(ProductionPatternElement elem, + int length, + int dummy, + CallStack stack, + LookAheadSet filter) + { + LookAheadSet result; + + if (elem.IsToken()) + { + result = new LookAheadSet(length); + result.Add(elem.Id); + } + else + { + var pattern = GetPattern(elem.Id); + result = FindLookAhead(pattern, length, stack, filter); + if (stack.Contains(pattern.Name)) + { + result = result.CreateRepetitive(); + } + } + + return result; + } + + private LookAheadSet FindConflicts(ProductionPattern pattern, + int maxLength) + { + + LookAheadSet result = new LookAheadSet(maxLength); + for (int i = 0; i < pattern.Count; i++) + { + var set1 = pattern[i].LookAhead; + for (int j = 0; j < i; j++) + { + var set2 = pattern[j].LookAhead; + result.AddAll(set1.CreateIntersection(set2)); + } + } + if (result.IsRepetitive()) + { + ThrowAmbiguityException(pattern.Name, null, result); + } + return result; + } + + private LookAheadSet FindConflicts(string pattern, + string location, + LookAheadSet set1, + LookAheadSet set2) + { + var result = set1.CreateIntersection(set2); + if (result.IsRepetitive()) + { + ThrowAmbiguityException(pattern, location, result); + } + return result; + } + + private LookAheadSet FindUnion(ProductionPattern pattern) + { + LookAheadSet result; + int length = 0; + int i; + + for (i = 0; i < pattern.Count; i++) + { + result = pattern[i].LookAhead; + if (result.GetMaxLength() > length) + { + length = result.GetMaxLength(); + } + } + result = new LookAheadSet(length); + for (i = 0; i < pattern.Count; i++) + { + result.AddAll(pattern[i].LookAhead); + } + + return result; + } + + + private void ThrowParseException(LookAheadSet set) + { + ArrayList list = new ArrayList(); + + // Read tokens until mismatch + while (set.IsNext(this, 1)) + { + set = set.CreateNextSet(NextToken().Id); + } + + // Find next token descriptions + var initials = set.GetInitialTokens(); + for (int i = 0; i < initials.Length; i++) + { + list.Add(GetTokenDescription(initials[i])); + } + + // Create exception + var token = NextToken(); + throw new ParseException(ParseException.ErrorType.UNEXPECTED_TOKEN, + token.ToShortString(), + list, + token.StartLine, + token.StartColumn); + } + + private void ThrowAmbiguityException(string pattern, + string location, + LookAheadSet set) + { + + ArrayList list = new ArrayList(); + + // Find next token descriptions + var initials = set.GetInitialTokens(); + for (int i = 0; i < initials.Length; i++) + { + list.Add(GetTokenDescription(initials[i])); + } + + // Create exception + throw new ParserCreationException( + ParserCreationException.ErrorType.INHERENT_AMBIGUITY, + pattern, + location, + list); + } + + + private class CallStack + { + private readonly ArrayList _nameStack = new ArrayList(); + private readonly ArrayList _valueStack = new ArrayList(); + public bool Contains(string name) + { + return _nameStack.Contains(name); + } + + public bool Contains(string name, int value) + { + for (int i = 0; i < _nameStack.Count; i++) + { + if (_nameStack[i].Equals(name) + && _valueStack[i].Equals(value)) + { + + return true; + } + } + return false; + } + + public void Clear() + { + _nameStack.Clear(); + _valueStack.Clear(); + } + + public void Push(string name, int value) + { + _nameStack.Add(name); + _valueStack.Add(value); + } + + public void Pop() + { + if (_nameStack.Count > 0) + { + _nameStack.RemoveAt(_nameStack.Count - 1); + _valueStack.RemoveAt(_valueStack.Count - 1); + } + } + } + } +} diff --git a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/Tokenizer.cs b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/Tokenizer.cs index 19f3e48..0fe781e 100644 --- a/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/Tokenizer.cs +++ b/src/Flee.NetStandard20/Parsing/grammatica-1.5.alpha2/PerCederberg.Grammatica.Runtime/Tokenizer.cs @@ -86,7 +86,10 @@ public int GetCurrentColumn() return _buffer.ColumnNumber; } - public void AddPattern(TokenPattern pattern) + /** + * nfa - true to attempt as an nfa pattern for regexp. This handles most things except the complex repeates, ie {1,4} + */ + public void AddPattern(TokenPattern pattern, bool nfa=true) { switch (pattern.Type) { @@ -105,11 +108,18 @@ public void AddPattern(TokenPattern pattern) } break; case TokenPattern.PatternType.REGEXP: - try + if (nfa) { - _nfaMatcher.AddPattern(pattern); + try + { + _nfaMatcher.AddPattern(pattern); + } + catch (Exception) + { + nfa = false; + } } - catch (Exception) + if (!nfa) { try { @@ -124,6 +134,7 @@ public void AddPattern(TokenPattern pattern) e.Message); } } + break; default: throw new ParserCreationException( diff --git a/src/Flee.NetStandard20/PublicTypes/ExpressionContext.cs b/src/Flee.NetStandard20/PublicTypes/ExpressionContext.cs index c684733..03eb6d3 100644 --- a/src/Flee.NetStandard20/PublicTypes/ExpressionContext.cs +++ b/src/Flee.NetStandard20/PublicTypes/ExpressionContext.cs @@ -96,14 +96,14 @@ internal ExpressionContext CloneInternal(bool cloneVariables) { ExpressionContext context = (ExpressionContext)this.MemberwiseClone(); context._myProperties = _myProperties.Clone(); - context._myProperties.SetValue("Options", this.Options.Clone()); - context._myProperties.SetValue("ParserOptions", this.ParserOptions.Clone()); - context._myProperties.SetValue("Imports", this.Imports.Clone()); + context._myProperties.SetValue("Options", context.Options.Clone()); + context._myProperties.SetValue("ParserOptions", context.ParserOptions.Clone()); + context._myProperties.SetValue("Imports", context.Imports.Clone()); context.Imports.SetContext(context); if (cloneVariables == true) { - context._myVariables = new VariableCollection(this); + context._myVariables = new VariableCollection(context); this.Variables.Copy(context._myVariables); } diff --git a/test/Flee.Console/Flee.Console.csproj b/test/Flee.Console/Flee.Console.csproj index 3445e9e..ea62205 100644 --- a/test/Flee.Console/Flee.Console.csproj +++ b/test/Flee.Console/Flee.Console.csproj @@ -6,7 +6,7 @@ - + diff --git a/test/Flee.Test/CalcEngineTests/LongScriptTests.cs b/test/Flee.Test/CalcEngineTests/LongScriptTests.cs index 22d5a0a..1e4ab35 100644 --- a/test/Flee.Test/CalcEngineTests/LongScriptTests.cs +++ b/test/Flee.Test/CalcEngineTests/LongScriptTests.cs @@ -13,15 +13,32 @@ namespace Flee.Test.CalcEngineTests public class LongScriptTests { private SimpleCalcEngine _myEngine; + + + public class TestFunction + { + static public Decimal Price(String s) + { + return 1.0m; + } + + static public Decimal First(params object[] args) + { + return (Decimal)args[0]; + } + } + public LongScriptTests() { var engine = new SimpleCalcEngine(); var context = new ExpressionContext(); - context.Imports.AddType(typeof(Math)); + context.Imports.AddType(typeof(TestFunction)); + context.Imports.AddType(typeof(Math)); // context.Imports.AddType(typeof(Math), "math"); //' add convert methods e.g. .ToInt64, .ToString, .ToDateTime... https://msdn.microsoft.com/en-us/library/system.convert.aspx?f=255&MSPPError=-2147217396 context.Imports.AddType(typeof(Convert)); + context.Imports.AddType(typeof(string)); engine.Context = context; _myEngine = engine; @@ -30,7 +47,22 @@ public LongScriptTests() [Test] public void LongScriptWithManyFunctions() { - var script = System.IO.File.ReadAllText(@"test\Flee.Test\TestScripts\LongScriptWithManyFunctions.js"); + //var script = System.IO.File.ReadAllText(@"test\Flee.Test\TestScripts\LongScriptWithManyFunctions.js"); + var script = @"If((""LongTextToPushScriptLengthOver256CharactersJustToMakeSureItDoesntMatter"")=""C"", + ( + If((""D"") = ""E"", + 2, + 3 + ) + ), + ( + If((""D"") = ""E"", + Ceiling((((((4000) / 46.228) + 8) * 46.228) * 3) + 5126) + 1471 / 304.8 + 20, + Ceiling(((((((((4000) / 46.228) + 8) * 46.228) * 3) + 5126) + 1217) / 304.8) + 20) + ) + ) +)"; + var expr = _myEngine.Context.CompileDynamic(script); var result = expr.Evaluate(); @@ -41,11 +73,133 @@ public void LongScriptWithManyFunctions() [Test] public void FailingLongScriptWithManyFunctions() { - var script = System.IO.File.ReadAllText(@"test\Flee.Test\TestScripts\FailingLongScriptWithManyFunctions.js"); + //var script = System.IO.File.ReadAllText(@"test\Flee.Test\TestScripts\FailingLongScriptWithManyFunctions.js"); + var script = @" +If(""A"" = ""A"", + ( + If((""LongTextToPushScriptLengthOver256CharactersJustToMakeSureItDoesntMatter"") = ""C"", + ( + If((""D"") = ""E"", + 2, + 3 + ) + ), + ( + If((""D"") = ""E"", + Ceiling((((((4000) / 46.228) + 8) * 46.228) * 3) + 5126) + 1471 / 304.8 + 20, + Ceiling(((((((((4000) / 46.228) + 8) * 46.228) * 3) + 5126) + 1217) / 304.8) + 20) + ) + ) + ) + ),0 + ) +"; var expr = _myEngine.Context.CompileDynamic(script); var result = expr.Evaluate(); Assert.AreEqual(84.0d, result); } + + [Test] + public void NestedConditionalsForLongBranches() + { + //var script = System.IO.File.ReadAllText(@"test\Flee.Test\TestScripts\NestedConditionals.js"); + var script = @"IF(2.1 <> 2.1, +IF(2.1 > 2.1, 2.1, +IF(2.1 > 2.1 AND 2.1 <= 2.1, 2.1, +IF(2.1 > 2.1 AND 2.1 <= 2.1, 2.1, 2.1))), +IF(2.1 > 2.1, 2.1, +IF(2.1 > 2.1 AND 2.1 <= 2.1, 2.1, +IF(2.1 > 2.1 AND 2.1 <= 2.1, 2.1, 2.1))))"; + var expr = _myEngine.Context.CompileDynamic(script); + var result = expr.Evaluate(); + + Assert.AreEqual(2.1d, Convert.ToDecimal(result)); + } + + [Test] + public void ShortCircuitLongBranches() + { + //var script = System.IO.File.ReadAllText(@"test\Flee.Test\TestScripts\NestedConditionals.js"); + var script = @"IF( +1 = 2 AND (16 * 24 + 8 * -1 < 0 OR 1+1+1+1+1+1+1+1+1+1+1+1+2+3+4+5+6+7+8+9+1+2+3+4+5+6+7+8+9+1+2+3+4+5+6+7+8+9+1+2+3*3-900 < 0) +AND (5*6+13-6*9-3+1+2+3+4+5+6+7+8 = 5+6+7+8+9+1+2+3+4+5+6+1+2+3+4+9-48 OR 6+5+2+3+8+1*9-6*7 > 8+6*4*(15-6)*(5+1+1+1+1+1+1+1+2)) +, +1.4d,2.6d +)"; + var expr = _myEngine.Context.CompileDynamic(script); + var result = expr.Evaluate(); + + Assert.AreEqual(2.6d, result); + } + + +static string crashscript= @" +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 10.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 20.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 30.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 40.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 50.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 60.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 70.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 80.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1, +if(ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 = 90.99, ceiling(First(6.29,if(6.39<100.01,6.39*0.66,6.39*.25)))-.01 + 1,"; + + + + [Test] + public void CrashTest() + { + _myEngine.Context.Options.RealLiteralDataType = RealLiteralDataType.Decimal; + bool gotex = false; + try + { + var e = _myEngine.Context.CompileDynamic(crashscript); + } + catch (ExpressionCompileException e) + { + gotex = true; + } + Assert.IsTrue(gotex); + } + + + [Test] + public void SeparatorExpressionParse() + { + var context = new ExpressionContext(); + context.Options.ParseCulture = new System.Globalization.CultureInfo("de-DE"); + context.ParserOptions.RecreateParser(); + var script = @"If(2,57 < 2,7; 3,57; 1000)"; + var expr = context.CompileDynamic(script); + var result = expr.Evaluate(); + + Assert.AreEqual(3.57d, result); + } + + + [Test] + public void StringTest() + { + var e = _myEngine.Context.CompileDynamic("\"TEST\".Substring(0,2)"); + var result = e.Evaluate(); + + Assert.AreEqual("TE", result); + } + + + [Test] + public void DivideByZero() + { + var context = new ExpressionContext(); + context.Options.IntegersAsDoubles = true; + + var script = @"1 / (1/0)"; + var expr = context.CompileDynamic(script); + var result = expr.Evaluate(); + + Assert.AreEqual(0d, result); + } + } } diff --git a/test/Flee.Test/ExpressionTests/Benchmarks.cs b/test/Flee.Test/ExpressionTests/Benchmarks.cs index dc62dfe..2c87b8c 100644 --- a/test/Flee.Test/ExpressionTests/Benchmarks.cs +++ b/test/Flee.Test/ExpressionTests/Benchmarks.cs @@ -40,9 +40,630 @@ public void TestFastVariables() Assert.Less(sw.ElapsedMilliseconds, expectedTime, "Test time above expected value"); } + private const String BigExpression = @" +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( 28 In (VAR4)) + OR ( 28 In (VAR5)) + OR ( 28 In (VAR6)) + OR ( 28 In (VAR7)) + OR ( 28 In (VAR8)) + OR ( 28 In (VAR9)) + OR ( 28 In (VAR10)) + OR ( 28 In (VAR11)) + OR ( 28 In (VAR12)) + OR (24 In (VAR4)) + OR ( 24 In (VAR5)) + OR ( 24 In (VAR6)) + OR ( 24 In (VAR7)) + OR ( 24 In (VAR8)) + OR ( 24 In (VAR9)) + OR ( 24 In (VAR10)) + OR ( 24 In (VAR11)) + OR ( 24 In (VAR12)) + OR (25 In (VAR4)) + OR ( 25 In (VAR5)) + OR ( 25 In (VAR6)) + OR ( 25 In (VAR7)) + OR ( 25 In (VAR8)) + OR ( 25 In (VAR9)) + OR ( 25 In (VAR10)) + OR ( 25 In (VAR11)) + OR ( 25 In (VAR12)) + ) + AND + ( + ( NOT 44 In (VAR4)) + AND ( NOT 44 In (VAR5)) + AND ( NOT 44 In (VAR6)) + AND ( NOT 44 In (VAR7)) + AND ( NOT 44 In (VAR8)) + AND ( NOT 44 In (VAR9)) + AND ( NOT 44 In (VAR10)) + AND ( NOT 44 In (VAR11)) + AND ( NOT 44 In (VAR12)) + AND ( NOT 34 In (VAR4)) + AND ( NOT 34 In (VAR5)) + AND ( NOT 34 In (VAR6)) + AND ( NOT 34 In (VAR7)) + AND ( NOT 34 In (VAR8)) + AND ( NOT 34 In (VAR9)) + AND ( NOT 34 In (VAR10)) + AND ( NOT 34 In (VAR11)) + AND ( NOT 34 In (VAR12)) + AND ( NOT 183 In (VAR4)) + AND ( NOT 183 In (VAR5)) + AND ( NOT 183 In (VAR6)) + AND ( NOT 183 In (VAR7)) + AND ( NOT 183 In (VAR8)) + AND ( NOT 183 In (VAR9)) + AND ( NOT 183 In (VAR10)) + AND ( NOT 183 In (VAR11)) + AND ( NOT 183 In (VAR12)) + AND ( NOT 81 In (VAR4)) + AND ( NOT 81 In (VAR5)) + AND ( NOT 81 In (VAR6)) + AND ( NOT 81 In (VAR7)) + AND ( NOT 81 In (VAR8)) + AND ( NOT 81 In (VAR9)) + AND ( NOT 81 In (VAR10)) + AND ( NOT 81 In (VAR11)) + AND ( NOT 81 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( NOT 28 In (VAR4)) + AND ( NOT 28 In (VAR5)) + AND ( NOT 28 In (VAR6)) + AND ( NOT 28 In (VAR7)) + AND ( NOT 28 In (VAR8)) + AND ( NOT 28 In (VAR9)) + AND ( NOT 28 In (VAR10)) + AND ( NOT 28 In (VAR11)) + AND ( NOT 28 In (VAR12)) + AND ( NOT 24 In (VAR4)) + AND ( NOT 24 In (VAR5)) + AND ( NOT 24 In (VAR6)) + AND ( NOT 24 In (VAR7)) + AND ( NOT 24 In (VAR8)) + AND ( NOT 24 In (VAR9)) + AND ( NOT 24 In (VAR10)) + AND ( NOT 24 In (VAR11)) + AND ( NOT 24 In (VAR12)) + AND ( NOT 25 In (VAR4)) + AND ( NOT 25 In (VAR5)) + AND ( NOT 25 In (VAR6)) + AND ( NOT 25 In (VAR7)) + AND ( NOT 25 In (VAR8)) + AND ( NOT 25 In (VAR9)) + AND ( NOT 25 In (VAR10)) + AND ( NOT 25 In (VAR11)) + AND ( NOT 25 In (VAR12)) + ) + AND + ( + ( NOT 44 In (VAR4)) + AND ( NOT 44 In (VAR5)) + AND ( NOT 44 In (VAR6)) + AND ( NOT 44 In (VAR7)) + AND ( NOT 44 In (VAR8)) + AND ( NOT 44 In (VAR9)) + AND ( NOT 44 In (VAR10)) + AND ( NOT 44 In (VAR11)) + AND ( NOT 44 In (VAR12)) + AND ( NOT 34 In (VAR4)) + AND ( NOT 34 In (VAR5)) + AND ( NOT 34 In (VAR6)) + AND ( NOT 34 In (VAR7)) + AND ( NOT 34 In (VAR8)) + AND ( NOT 34 In (VAR9)) + AND ( NOT 34 In (VAR10)) + AND ( NOT 34 In (VAR11)) + AND ( NOT 34 In (VAR12)) + AND ( NOT 183 In (VAR4)) + AND ( NOT 183 In (VAR5)) + AND ( NOT 183 In (VAR6)) + AND ( NOT 183 In (VAR7)) + AND ( NOT 183 In (VAR8)) + AND ( NOT 183 In (VAR9)) + AND ( NOT 183 In (VAR10)) + AND ( NOT 183 In (VAR11)) + AND ( NOT 183 In (VAR12)) + AND ( NOT 81 In (VAR4)) + AND ( NOT 81 In (VAR5)) + AND ( NOT 81 In (VAR6)) + AND ( NOT 81 In (VAR7)) + AND ( NOT 81 In (VAR8)) + AND ( NOT 81 In (VAR9)) + AND ( NOT 81 In (VAR10)) + AND ( NOT 81 In (VAR11)) + AND ( NOT 81 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( 28 In (VAR4)) + OR ( 28 In (VAR5)) + OR ( 28 In (VAR6)) + OR ( 28 In (VAR7)) + OR ( 28 In (VAR8)) + OR ( 28 In (VAR9)) + OR ( 28 In (VAR10)) + OR ( 28 In (VAR11)) + OR ( 28 In (VAR12)) + OR ( 24 In (VAR4)) + OR ( 24 In (VAR5)) + OR ( 24 In (VAR6)) + OR ( 24 In (VAR7)) + OR ( 24 In (VAR8)) + OR ( 24 In (VAR9)) + OR ( 24 In (VAR10)) + OR ( 24 In (VAR11)) + OR ( 24 In (VAR12)) + OR ( 25 In (VAR4)) + OR ( 25 In (VAR5)) + OR ( 25 In (VAR6)) + OR ( 25 In (VAR7)) + OR ( 25 In (VAR8)) + OR ( 25 In (VAR9)) + OR ( 25 In (VAR10)) + OR ( 25 In (VAR11)) + OR ( 25 In (VAR12)) + ) + AND + ( + ( 44 In (VAR4)) + OR ( 44 In (VAR5)) + OR ( 44 In (VAR6)) + OR ( 44 In (VAR7)) + OR ( 44 In (VAR8)) + OR ( 44 In (VAR9)) + OR ( 44 In (VAR10)) + OR ( 44 In (VAR11)) + OR ( 44 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( NOT 28 In (VAR4)) + AND ( NOT 28 In (VAR5)) + AND ( NOT 28 In (VAR6)) + AND ( NOT 28 In (VAR7)) + AND ( NOT 28 In (VAR8)) + AND ( NOT 28 In (VAR9)) + AND ( NOT 28 In (VAR10)) + AND ( NOT 28 In (VAR11)) + AND ( NOT 28 In (VAR12)) + AND ( NOT 24 In (VAR4)) + AND ( NOT 24 In (VAR5)) + AND ( NOT 24 In (VAR6)) + AND ( NOT 24 In (VAR7)) + AND ( NOT 24 In (VAR8)) + AND ( NOT 24 In (VAR9)) + AND ( NOT 24 In (VAR10)) + AND ( NOT 24 In (VAR11)) + AND ( NOT 24 In (VAR12)) + AND ( NOT 25 In (VAR4)) + AND ( NOT 25 In (VAR5)) + AND ( NOT 25 In (VAR6)) + AND ( NOT 25 In (VAR7)) + AND ( NOT 25 In (VAR8)) + AND ( NOT 25 In (VAR9)) + AND ( NOT 25 In (VAR10)) + AND ( NOT 25 In (VAR11)) + AND ( NOT 25 In (VAR12)) + ) + AND + ( + ( 44 In (VAR4)) + OR ( 44 In (VAR5)) + OR ( 44 In (VAR6)) + OR ( 44 In (VAR7)) + OR ( 44 In (VAR8)) + OR ( 44 In (VAR9)) + OR ( 44 In (VAR10)) + OR ( 44 In (VAR11)) + OR ( 44 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( 28 In (VAR4)) + OR ( 28 In (VAR5)) + OR ( 28 In (VAR6)) + OR ( 28 In (VAR7)) + OR ( 28 In (VAR8)) + OR ( 28 In (VAR9)) + OR ( 28 In (VAR10)) + OR ( 28 In (VAR11)) + OR ( 28 In (VAR12)) + OR ( 24 In (VAR4)) + OR ( 24 In (VAR5)) + OR ( 24 In (VAR6)) + OR ( 24 In (VAR7)) + OR ( 24 In (VAR8)) + OR ( 24 In (VAR9)) + OR ( 24 In (VAR10)) + OR ( 24 In (VAR11)) + OR ( 24 In (VAR12)) + OR ( 25 In (VAR4)) + OR ( 25 In (VAR5)) + OR ( 25 In (VAR6)) + OR ( 25 In (VAR7)) + OR ( 25 In (VAR8)) + OR ( 25 In (VAR9)) + OR ( 25 In (VAR10)) + OR ( 25 In (VAR11)) + OR ( 25 In (VAR12)) + ) + AND + ( + ( 34 In (VAR4)) + OR ( 34 In (VAR5)) + OR ( 34 In (VAR6)) + OR ( 34 In (VAR7)) + OR ( 34 In (VAR8)) + OR ( 34 In (VAR9)) + OR ( 34 In (VAR10)) + OR ( 34 In (VAR11)) + OR ( 34 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( NOT 28 In (VAR4)) + AND ( NOT 28 In (VAR5)) + AND ( NOT 28 In (VAR6)) + AND ( NOT 28 In (VAR7)) + AND ( NOT 28 In (VAR8)) + AND ( NOT 28 In (VAR9)) + AND ( NOT 28 In (VAR10)) + AND ( NOT 28 In (VAR11)) + AND ( NOT 28 In (VAR12)) + AND ( NOT 24 In (VAR4)) + AND ( NOT 24 In (VAR5)) + AND ( NOT 24 In (VAR6)) + AND ( NOT 24 In (VAR7)) + AND ( NOT 24 In (VAR8)) + AND ( NOT 24 In (VAR9)) + AND ( NOT 24 In (VAR10)) + AND ( NOT 24 In (VAR11)) + AND ( NOT 24 In (VAR12)) + AND ( NOT 25 In (VAR4)) + AND ( NOT 25 In (VAR5)) + AND ( NOT 25 In (VAR6)) + AND ( NOT 25 In (VAR7)) + AND ( NOT 25 In (VAR8)) + AND ( NOT 25 In (VAR9)) + AND ( NOT 25 In (VAR10)) + AND ( NOT 25 In (VAR11)) + AND ( NOT 25 In (VAR12)) + ) + AND + ( + ( 34 In (VAR4)) + OR ( 34 In (VAR5)) + OR ( 34 In (VAR6)) + OR ( 34 In (VAR7)) + OR ( 34 In (VAR8)) + OR ( 34 In (VAR9)) + OR ( 34 In (VAR10)) + OR ( 34 In (VAR11)) + OR ( 34 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( 28 In (VAR4)) + OR ( 28 In (VAR5)) + OR ( 28 In (VAR6)) + OR ( 28 In (VAR7)) + OR ( 28 In (VAR8)) + OR ( 28 In (VAR9)) + OR ( 28 In (VAR10)) + OR ( 28 In (VAR11)) + OR ( 28 In (VAR12)) + OR ( 24 In (VAR4)) + OR ( 24 In (VAR5)) + OR ( 24 In (VAR6)) + OR ( 24 In (VAR7)) + OR ( 24 In (VAR8)) + OR ( 24 In (VAR9)) + OR ( 24 In (VAR10)) + OR ( 24 In (VAR11)) + OR ( 24 In (VAR12)) + OR ( 25 In (VAR4)) + OR ( 25 In (VAR5)) + OR ( 25 In (VAR6)) + OR ( 25 In (VAR7)) + OR ( 25 In (VAR8)) + OR ( 25 In (VAR9)) + OR ( 25 In (VAR10)) + OR ( 25 In (VAR11)) + OR ( 25 In (VAR12)) + ) + AND + ( + ( 81 In (VAR4)) + OR ( 81 In (VAR5)) + OR ( 81 In (VAR6)) + OR ( 81 In (VAR7)) + OR ( 81 In (VAR8)) + OR ( 81 In (VAR9)) + OR ( 81 In (VAR10)) + OR ( 81 In (VAR11)) + OR ( 81 In (VAR12)) + OR ( 183 In (VAR4)) + OR ( 183 In (VAR5)) + OR ( 183 In (VAR6)) + OR ( 183 In (VAR7)) + OR ( 183 In (VAR8)) + OR ( 183 In (VAR9)) + OR ( 183 In (VAR10)) + OR ( 183 In (VAR11)) + OR ( 183 In (VAR12)) + ) + ) +) +AND +( + NOT + ( + (VAR1 > 0 + OR VAR2 > 0 + OR VAR3 > 0) + AND + ( + ( NOT 28 In (VAR4)) + AND ( NOT 28 In (VAR5)) + AND ( NOT 28 In (VAR6)) + AND ( NOT 28 In (VAR7)) + AND ( NOT 28 In (VAR8)) + AND ( NOT 28 In (VAR9)) + AND ( NOT 28 In (VAR10)) + AND ( NOT 28 In (VAR11)) + AND ( NOT 28 In (VAR12)) + AND ( NOT 24 In (VAR4)) + AND ( NOT 24 In (VAR5)) + AND ( NOT 24 In (VAR6)) + AND ( NOT 24 In (VAR7)) + AND ( NOT 24 In (VAR8)) + AND ( NOT 24 In (VAR9)) + AND ( NOT 24 In (VAR10)) + AND ( NOT 24 In (VAR11)) + AND ( NOT 24 In (VAR12)) + AND ( NOT 25 In (VAR4)) + AND ( NOT 25 In (VAR5)) + AND ( NOT 25 In (VAR6)) + AND ( NOT 25 In (VAR7)) + AND ( NOT 25 In (VAR8)) + AND ( NOT 25 In (VAR9)) + AND ( NOT 25 In (VAR10)) + AND ( NOT 25 In (VAR11)) + AND ( NOT 25 In (VAR12)) + ) + AND + ( + ( 81 In (VAR4)) + OR ( 81 In (VAR5)) + OR ( 81 In (VAR6)) + OR ( 81 In (VAR7)) + OR ( 81 In (VAR8)) + OR ( 81 In (VAR9)) + OR ( 81 In (VAR10)) + OR ( 81 In (VAR11)) + OR ( 81 In (VAR12)) + OR ( 183 In (VAR4)) + OR ( 183 In (VAR5)) + OR ( 183 In (VAR6)) + OR ( 183 In (VAR7)) + OR ( 183 In (VAR8)) + OR ( 183 In (VAR9)) + OR ( 183 In (VAR10)) + OR ( 183 In (VAR11)) + OR ( 183 In (VAR12)) + ) + ) +) +AND NOT +( + ( 174 In (VAR13)) + OR ( 174 In (VAR14)) + OR ( 174 In (VAR15)) + OR ( 174 In (VAR16)) + OR ( 174 In (VAR17)) + OR ( 174 In (VAR18)) + OR ( 174 In (VAR19)) + OR ( 174 In (VAR20)) + OR ( 174 In (VAR21)) + OR ( 174 In (VAR22)) + OR ( 174 In (VAR23)) + OR ( 174 In (VAR24)) + OR ( 174 In (VAR25)) + OR ( 174 In (VAR26)) + OR ( 174 In (VAR27)) + OR ( 174 In (VAR28)) + OR ( 174 In (VAR29)) + OR ( 174 In (VAR30)) + OR ( 306 In (VAR13)) + OR ( 306 In (VAR14)) + OR ( 306 In (VAR15)) + OR ( 306 In (VAR16)) + OR ( 306 In (VAR17)) + OR ( 306 In (VAR18)) + OR ( 306 In (VAR19)) + OR ( 306 In (VAR20)) + OR ( 306 In (VAR21)) + OR ( 306 In (VAR22)) + OR ( 306 In (VAR23)) + OR ( 306 In (VAR24)) + OR ( 306 In (VAR25)) + OR ( 306 In (VAR26)) + OR ( 306 In (VAR27)) + OR ( 306 In (VAR28)) + OR ( 306 In (VAR29)) + OR ( 306 In (VAR30)) + OR ( 492 In (VAR13)) + OR ( 492 In (VAR14)) + OR ( 492 In (VAR15)) + OR ( 492 In (VAR16)) + OR ( 492 In (VAR17)) + OR ( 492 In (VAR18)) + OR ( 492 In (VAR19)) + OR ( 492 In (VAR20)) + OR ( 492 In (VAR21)) + OR ( 492 In (VAR22)) + OR ( 492 In (VAR23)) + OR ( 492 In (VAR24)) + OR ( 492 In (VAR25)) + OR ( 492 In (VAR26)) + OR ( 492 In (VAR27)) + OR ( 492 In (VAR28)) + OR ( 492 In (VAR29)) + OR ( 492 In (VAR30)) +)"; + private const String SmallExpression = "(4 ^ 3.4 * 18 - VAR1) * (14 / 3) + VAR2"; + private const String SmallBranching = "If(If(23 > 15 AND 3*7 = 21 OR (25/5 > 10 AND 6+8 = 14), If(2.1=2.1,(4 ^ 3.4 * 18 - VAR1),If(2.1=2.1,0,1)), (14 / 3) + VAR2) <> 0 or true, If(2.1 <> 2.1 AND 3.1=3.1 OF 6.2=6.7, 2.1, 3.1), If(2.1=2.1 AND 3.2=3.2 OR 3.1<>3.1 OR 2.1<>2.3,3, 4))"; + + [Test(Description = "Compile complicated expressions")] + public void ProfileCompilationTime() + { + int expectedTime = 1000; + int iterations = 10; + + var context = new ExpressionContext(); + context.Variables.ResolveVariableType += Variables_ResolveVariableType; + context.Variables.ResolveVariableValue += Variables_ResolveVariableValue; + Stopwatch sw; + + + sw = new Stopwatch(); + sw.Start(); + + for (int i = 0; i < iterations - 1; i++) + { + IDynamicExpression e = this.CreateDynamicExpression(BigExpression, context); + + } + sw.Stop(); + this.PrintSpeedMessage("Compile Big", iterations, sw); + Assert.Less(sw.ElapsedMilliseconds, expectedTime, "Test time above expected value"); + + + iterations = 100; + expectedTime = 100; + + sw = new Stopwatch(); + sw.Start(); + + for (int i = 0; i < iterations - 1; i++) + { + IDynamicExpression e = this.CreateDynamicExpression(SmallExpression, context); + + } + sw.Stop(); + this.PrintSpeedMessage("Compile Small", iterations, sw); + Assert.Less(sw.ElapsedMilliseconds, expectedTime, "Test time above expected value"); + + iterations = 100; + expectedTime = 100; + + sw = new Stopwatch(); + sw.Start(); + + for (int i = 0; i < iterations - 1; i++) + { + IDynamicExpression e = this.CreateDynamicExpression(SmallExpression, context); + + } + sw.Stop(); + this.PrintSpeedMessage("Compile Small Branching", iterations, sw); + Assert.Less(sw.ElapsedMilliseconds, expectedTime, "Test time above expected value"); + } + + + private static void Variables_ResolveVariableType(object sender, ResolveVariableTypeEventArgs e) + { + if (e.VariableName.StartsWith("VARBOOL")) + { + e.VariableType = typeof(bool); + } + else + { + e.VariableType = typeof(int); + } + } + + private static void Variables_ResolveVariableValue(object sender, ResolveVariableValueEventArgs e) + { + if (e.VariableType == typeof(bool)) + { + e.VariableValue = false; + } + else + { + e.VariableValue = 0; + } + } + private void PrintSpeedMessage(string title, int iterations, Stopwatch sw) { - this.WriteMessage("{0}: {1:n0} iterations in {2:n2}ms = {3:n2} iterations/sec", title, iterations, sw.ElapsedMilliseconds, iterations / (sw.ElapsedMilliseconds / 10)); + this.WriteMessage("{0}: {1:n0} iterations in {2:n2}ms = {3:n2} iterations/sec", title, iterations, sw.ElapsedMilliseconds, iterations*1000 / (sw.ElapsedMilliseconds)); } } } diff --git a/test/Flee.Test/ExpressionTests/ExpressionBuildingTest.cs b/test/Flee.Test/ExpressionTests/ExpressionBuildingTest.cs index 0a038b9..3405350 100644 --- a/test/Flee.Test/ExpressionTests/ExpressionBuildingTest.cs +++ b/test/Flee.Test/ExpressionTests/ExpressionBuildingTest.cs @@ -77,5 +77,45 @@ public void NullIsNullCheck() Assert.IsTrue((bool)e1.Evaluate()); } + + [TestMethod] + public void CompareLongs() + { + // bug #83 test. + ExpressionContext context = new ExpressionContext(); + IDynamicExpression e1 = context.CompileDynamic("2432696330L = 2432696330L AND 2432696330L > 0 AND 2432696330L < 2432696331L"); + + Assert.IsTrue((bool)e1.Evaluate()); + e1 = context.CompileDynamic("2432696330L / 2"); + + Assert.AreEqual(1216348165L, e1.Evaluate()); + } + + [TestMethod] + public void ArgumentInt_to_DoubleConversion() + { + ExpressionContext context = new ExpressionContext(); + context.Imports.AddType(typeof(Math)); + IDynamicExpression e1 = context.CompileDynamic("sqrt(16)"); + + Assert.AreEqual(4.0, e1.Evaluate()); + } + + + [TestMethod] + public void IN_OperatorTest() + { + ExpressionContext context = new ExpressionContext(); + var e1 = context.CompileGeneric("NOT 15 IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23)"); + + Assert.IsTrue(e1.Evaluate()); + + e1 = context.CompileGeneric("\"a\" IN (\"a\",\"b\",\"c\",\"d\") and true and 5 in (2,4,5)"); + Assert.IsTrue(e1.Evaluate()); + e1 = context.CompileGeneric("\"a\" IN (\"a\",\"b\",\"c\",\"d\") and true and 5 in (2,4,6,7,8,9)"); + Assert.IsFalse(e1.Evaluate()); + } + + } } \ No newline at end of file diff --git a/test/Flee.Test/ExtensionMethodTests/ExtensionMethodTest.cs b/test/Flee.Test/ExtensionMethodTests/ExtensionMethodTest.cs index 76f71de..ce1d1e7 100644 --- a/test/Flee.Test/ExtensionMethodTests/ExtensionMethodTest.cs +++ b/test/Flee.Test/ExtensionMethodTests/ExtensionMethodTest.cs @@ -65,6 +65,23 @@ public void TestExtensionMethodCallOnPropertyWithArgumentsOnOverload() Assert.AreEqual("Hello SubWorld!!!", result); } + /// + /// check that methods are not ambiguous. + /// + [TestMethod] + public void TestExtensionMethodMatchArguments() + { + var result = GetExpressionContext().CompileDynamic("MatchParams(1, 2.3f, 2.3)").Evaluate(); + Assert.AreEqual("FFD", result); + result = GetExpressionContext().CompileDynamic("MatchParams(3.4,4.4,2.3)").Evaluate(); + Assert.AreEqual("DDD", result); + result = GetExpressionContext().CompileDynamic("MatchParams(1,2,3)").Evaluate(); + Assert.AreEqual("III", result); + result = GetExpressionContext().CompileDynamic("MatchParams(1u,2,3)").Evaluate(); + Assert.AreEqual("UII", result); + } + + private static ExpressionContext GetExpressionContext() { var expressionOwner = new TestData { Id = "World" }; diff --git a/test/Flee.Test/ExtensionMethodTests/TestData.cs b/test/Flee.Test/ExtensionMethodTests/TestData.cs index d0202cd..830f50b 100644 --- a/test/Flee.Test/ExtensionMethodTests/TestData.cs +++ b/test/Flee.Test/ExtensionMethodTests/TestData.cs @@ -21,5 +21,31 @@ public string SayHello(int times) return result + Id; } + + /// + /// A bug previous meant a small difference in + /// parameters was not detected and treated + /// as ambiguous + /// + /// + /// + /// + /// + public string MatchParams(uint x, int y, int z) + { + return "UII"; + } + public string MatchParams(int x, int y, int z) + { + return "III"; + } + public string MatchParams(float x, float y, double z) + { + return "FFD"; + } + public string MatchParams(double x, double y, double z) + { + return "DDD"; + } } } diff --git a/test/Flee.Test/Flee.Test.csproj b/test/Flee.Test/Flee.Test.csproj index dbbb54a..de6ffca 100644 --- a/test/Flee.Test/Flee.Test.csproj +++ b/test/Flee.Test/Flee.Test.csproj @@ -39,9 +39,6 @@ 4 - - ..\..\packages\Flee.1.2.1\lib\net45\Flee.Net45.dll - ..\..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll @@ -83,6 +80,12 @@ + + + {8c600bab-128a-4792-8260-469b2dad6681} + Flee.NetStandard20 + + diff --git a/test/Flee.Test/packages.config b/test/Flee.Test/packages.config index e27db3d..31ff652 100644 --- a/test/Flee.Test/packages.config +++ b/test/Flee.Test/packages.config @@ -1,6 +1,6 @@  - +