private static ExpressionBase WrapInMeasured(ExpressionBase expression)
 {
     return(new FunctionCallExpression("measured", new ExpressionBase[]
     {
         expression,
         AlwaysTrueFunction.CreateAlwaysTrueFunctionCall(),
         new StringConstantExpression("raw")
     }));
 }
        public void TestIsTrueAlwaysTrueFunction()
        {
            var expr  = AlwaysTrueFunction.CreateAlwaysTrueFunctionCall();
            var scope = AchievementScriptInterpreter.GetGlobalScope();

            ParseErrorExpression error;

            Assert.That(expr.IsTrue(scope, out error), Is.True);
            Assert.That(error, Is.Null);
        }
        private ParseErrorExpression ExecuteAchievementExpression(ExpressionBase expression, InterpreterScope scope)
        {
            ExpressionBase operand;

            switch (expression.Type)
            {
            case ExpressionType.FunctionCall:
                return(ExecuteAchievementFunction((FunctionCallExpression)expression, scope));

            case ExpressionType.Conditional:
                return(ExecuteAchievementConditional((ConditionalExpression)expression, scope));

            case ExpressionType.Comparison:
                return(ExecuteAchievementComparison((ComparisonExpression)expression, scope));

            case ExpressionType.Mathematic:
                return(ExecuteAchievementMathematic((MathematicExpression)expression, scope));

            case ExpressionType.Variable:
                if (!expression.ReplaceVariables(scope, out operand))
                {
                    return(new ParseErrorExpression(operand, expression));
                }

                if (expression is FunctionReferenceExpression)
                {
                    return(new ParseErrorExpression("Function used like a variable", expression));
                }

                return(ExecuteAchievementExpression(operand, scope));

            case ExpressionType.BooleanConstant:
            {
                var context = scope.GetContext <TriggerBuilderContext>();
                if (((BooleanConstantExpression)expression).Value == true)
                {
                    context.Trigger.Add(AlwaysTrueFunction.CreateAlwaysTrueRequirement());
                }
                else
                {
                    context.Trigger.Add(AlwaysFalseFunction.CreateAlwaysFalseRequirement());
                }
                return(null);
            }
            }

            return(new ParseErrorExpression("Cannot generate trigger from " + expression.Type, expression));
        }
        private ParseErrorExpression ExecuteAchievementComparison(ComparisonExpression comparison, InterpreterScope scope)
        {
            var left  = comparison.Left;
            var right = comparison.Right;

            if (left.Type == ExpressionType.Comparison || right.Type == ExpressionType.Comparison)
            {
                return(new ParseErrorExpression("comparison did not evaluate to a valid comparison", comparison));
            }

            var context = scope.GetContext <TriggerBuilderContext>();
            var op      = GetRequirementOperator(comparison.Operation);

            var leftField = CreateFieldFromExpression(left);

            if (leftField.Type != FieldType.None)
            {
                var rightField = CreateFieldFromExpression(right);
                if (rightField.Type != FieldType.None)
                {
                    // comparing two constants, convert to always_true or always_false
                    var requirement = new Requirement
                    {
                        Left     = leftField,
                        Operator = op,
                        Right    = rightField,
                    };

                    if (requirement.Evaluate() == true)
                    {
                        context.Trigger.Add(AlwaysTrueFunction.CreateAlwaysTrueRequirement());
                    }
                    else
                    {
                        context.Trigger.Add(AlwaysFalseFunction.CreateAlwaysFalseRequirement());
                    }

                    return(null);
                }

                // swap the operands and operator so the constant is on the right
                var temp = left;
                left  = right;
                right = temp;

                op = Requirement.GetReversedRequirementOperator(op);
            }

            var error = ExecuteAchievementExpression(left, scope);

            if (error != null)
            {
                return(ParseErrorExpression.WrapError(error, "Invalid value", left));
            }

            if (right.Type == ExpressionType.IntegerConstant || right.Type == ExpressionType.FloatConstant)
            {
                var lastRequirement = context.LastRequirement;
                if (lastRequirement.Operator != RequirementOperator.None)
                {
                    // try to rearrange so the last requirement doesn't have a modifier
                    for (int i = context.Trigger.Count - 2; i >= 0; --i)
                    {
                        var requirement = context.Trigger.ElementAt(i);
                        if (requirement.Type == RequirementType.AddSource && requirement.Operator == RequirementOperator.None)
                        {
                            context.Trigger.Remove(lastRequirement);
                            context.Trigger.Add(new Requirement {
                                Left = requirement.Left
                            });
                            requirement.Left     = lastRequirement.Left;
                            requirement.Operator = lastRequirement.Operator;
                            requirement.Right    = lastRequirement.Right;
                            lastRequirement      = context.LastRequirement;
                            break;
                        }

                        if (requirement.Type != RequirementType.AddSource &&
                            requirement.Type != RequirementType.SubSource &&
                            requirement.Type != RequirementType.AddAddress)
                        {
                            break;
                        }
                    }

                    if (lastRequirement.Operator != RequirementOperator.None)
                    {
                        // last requirement still has a modifier, have to add a dummy condition.
                        lastRequirement.Type = RequirementType.AddSource;
                        context.Trigger.Add(new Requirement {
                            Left = new Field {
                                Size = lastRequirement.Left.Size, Type = FieldType.Value, Value = 0
                            }
                        });
                        lastRequirement = context.LastRequirement;
                    }
                }

                lastRequirement.Operator = op;
                lastRequirement.Right    = CreateFieldFromExpression(right);
            }
            else
            {
                error = ExecuteAchievementExpression(right, scope);
                if (error != null)
                {
                    return(ParseErrorExpression.WrapError(error, "Invalid value", right));
                }

                var extraRequirement = context.LastRequirement;
                ((IList <Requirement>)context.Trigger).RemoveAt(context.Trigger.Count - 1);

                var requirement = context.LastRequirement;
                if (requirement != null)
                {
                    if (requirement.Type == RequirementType.AddAddress)
                    {
                        return(HandleAddAddressComparison(comparison, (IList <Requirement>)context.Trigger, op, extraRequirement));
                    }
                    else if (context.Trigger.Count > 1 && context.Trigger.ElementAt(context.Trigger.Count - 2).Type == RequirementType.AddAddress)
                    {
                        // if left side is an AddAddress chain, but right side is a not, we have to keep the
                        // dummy condition to prevent the AddAddress from modifying the memory address on the
                        // right side. integers are handled above.
                        requirement.Type       = RequirementType.AddSource;
                        extraRequirement.Right = extraRequirement.Left;
                        extraRequirement.Left  = new Field {
                            Type = FieldType.Value, Value = 0
                        };
                        extraRequirement.Operator = op;
                        context.Trigger.Add(extraRequirement);
                    }
                    else if (requirement.Operator != RequirementOperator.None)
                    {
                        // if left side is a complex expression (a * 4), add a new dummy condition for the comparison
                        requirement.Type       = RequirementType.AddSource;
                        extraRequirement.Right = extraRequirement.Left;
                        extraRequirement.Left  = new Field {
                            Type = FieldType.Value, Value = 0
                        };
                        extraRequirement.Operator = op;
                        context.Trigger.Add(extraRequirement);
                    }
                    else
                    {
                        // no AddAddress on either side, just merge the conditions
                        requirement.Operator = op;
                        requirement.Right    = extraRequirement.Left;
                    }
                }
            }

            return(null);
        }
        internal static ExpressionBase InvertExpression(ExpressionBase expression)
        {
            // logical inversion
            var condition = expression as ConditionalExpression;

            if (condition != null)
            {
                var newConditions = new List <ExpressionBase>(condition._conditions.Count);
                foreach (var oldCondition in condition._conditions)
                {
                    newConditions.Add(InvertExpression(oldCondition));
                }

                switch (condition.Operation)
                {
                case ConditionalOperation.Not:
                    // !(!A) => A
                    return(newConditions[0]);

                case ConditionalOperation.And:
                    // !(A && B) => !A || !B
                    return(new ConditionalExpression(ConditionalOperation.Or, newConditions));

                case ConditionalOperation.Or:
                    // !(A || B) => !A && !B
                    return(new ConditionalExpression(ConditionalOperation.And, newConditions));

                default:
                    throw new NotImplementedException("Unsupported condition operation");
                }
            }

            // comparative inversion
            var comparison = expression as ComparisonExpression;

            if (comparison != null)
            {
                // !(A == B) => A != B, !(A < B) => A >= B, ...
                return(new ComparisonExpression(
                           comparison.Left,
                           ComparisonExpression.GetOppositeComparisonOperation(comparison.Operation),
                           comparison.Right));
            }

            // boolean constant
            var boolean = expression as BooleanConstantExpression;

            if (boolean != null)
            {
                return(new BooleanConstantExpression(!boolean.Value));
            }

            // special handling for built-in functions
            var function = expression as FunctionCallExpression;

            if (function != null && function.Parameters.Count() == 0)
            {
                if (function.FunctionName.Name == "always_true")
                {
                    return(AlwaysFalseFunction.CreateAlwaysFalseFunctionCall());
                }

                if (function.FunctionName.Name == "always_false")
                {
                    return(AlwaysTrueFunction.CreateAlwaysTrueFunctionCall());
                }
            }

            // unsupported inversion
            return(new ParseErrorExpression("! operator cannot be applied to " + expression.Type, expression));
        }
Exemple #6
0
        private ParseErrorExpression ExecuteAchievementComparison(ComparisonExpression comparison, InterpreterScope scope)
        {
            _equalityModifiers.Clear();

            var context     = scope.GetContext <TriggerBuilderContext>();
            var insertIndex = context.Trigger.Count;

            var left  = comparison.Left;
            var right = comparison.Right;

            var op = GetRequirementOperator(comparison.Operation);

            if (left.Type == ExpressionType.IntegerConstant)
            {
                if (right.Type == ExpressionType.IntegerConstant)
                {
                    // comparing two constants, convert to always_true or always_false
                    var requirement = new Requirement
                    {
                        Left = new Field {
                            Type = FieldType.Value, Value = (uint)((IntegerConstantExpression)left).Value
                        },
                        Operator = op,
                        Right    = new Field {
                            Type = FieldType.Value, Value = (uint)((IntegerConstantExpression)right).Value
                        },
                    };

                    if (Evaluate(requirement))
                    {
                        context.Trigger.Add(AlwaysTrueFunction.CreateAlwaysTrueRequirement());
                    }
                    else
                    {
                        context.Trigger.Add(AlwaysFalseFunction.CreateAlwaysFalseRequirement());
                    }

                    return(null);
                }

                // swap the operands and operator so the constant is on the right
                var temp = left;
                left  = right;
                right = temp;

                op = GetReversedRequirementOperator(op);
            }

            var error = ExecuteAchievementExpression(left, scope);

            if (error != null)
            {
                return(error);
            }

            var integerRight = right as IntegerConstantExpression;

            if (integerRight != null)
            {
                int newValue = integerRight.Value;

                // SubSource of a memory accessor may cause overflow - if comparing for less than,
                // modifiers should not be merged into the compare target
                if (_equalityModifiers.Count > 0 &&
                    (op == RequirementOperator.LessThan || op == RequirementOperator.LessThanOrEqual))
                {
                    bool hasSubSource = false;
                    for (int i = context.Trigger.Count - 2; i >= 0; i++)
                    {
                        var requirementType = context.Trigger.ElementAt(i).Type;
                        if (requirementType == RequirementType.SubSource)
                        {
                            hasSubSource = true;
                            break;
                        }
                        else if (requirementType != RequirementType.AddSource &&
                                 requirementType != RequirementType.AddHits)
                        {
                            break;
                        }
                    }

                    if (hasSubSource)
                    {
                        var last = context.Trigger.Last();
                        context.Trigger.Remove(last);
                        foreach (var modifier in _equalityModifiers)
                        {
                            if (modifier.Operation != MathematicOperation.Subtract && modifier.Operation != MathematicOperation.Add)
                            {
                                return(new ParseErrorExpression("Cannot normalize expression containing SubSource", left));
                            }

                            context.Trigger.Add(new Requirement
                            {
                                Type = (modifier.Operation == MathematicOperation.Subtract) ? RequirementType.AddSource : RequirementType.SubSource,
                                Left = new Field {
                                    Type = FieldType.Value, Value = (uint)modifier.Amount
                                },
                                Operator = RequirementOperator.None,
                                Right    = new Field()
                            });
                        }
                        context.Trigger.Add(last);

                        _equalityModifiers.Clear();
                    }
                }

                while (_equalityModifiers.Count > 0)
                {
                    var originalValue = newValue;
                    var modifier      = _equalityModifiers.Pop();
                    newValue = modifier.Apply(newValue);

                    var restoredValue = modifier.Remove(newValue);
                    switch (op)
                    {
                    case RequirementOperator.Equal:
                        if (restoredValue != originalValue)
                        {
                            return(new ParseErrorExpression("Result can never be true using integer math", comparison));
                        }
                        break;

                    case RequirementOperator.NotEqual:
                        if (restoredValue != originalValue)
                        {
                            return(new ParseErrorExpression("Result is always true using integer math", comparison));
                        }
                        break;

                    case RequirementOperator.GreaterThanOrEqual:
                        if (restoredValue != originalValue)
                        {
                            op = RequirementOperator.GreaterThan;
                        }
                        break;
                    }
                }

                var requirement = context.LastRequirement;
                requirement.Operator = op;
                requirement.Right    = new Field {
                    Size = requirement.Left.Size, Type = FieldType.Value, Value = (uint)newValue
                };
            }
            else
            {
                var leftModifiers = new Stack <ValueModifier>(_equalityModifiers.Reverse());
                _equalityModifiers.Clear();

                error = ExecuteAchievementExpression(right, scope);
                if (error != null)
                {
                    return(error);
                }

                if (leftModifiers.Count > 0 || _equalityModifiers.Count > 0)
                {
                    var rightValue = 1234567;
                    var leftValue  = rightValue;
                    while (leftModifiers.Count > 0)
                    {
                        var modifier = leftModifiers.Pop();
                        leftValue = ValueModifier.Apply(leftValue, MathematicExpression.GetOppositeOperation(modifier.Operation), modifier.Amount);
                    }

                    while (_equalityModifiers.Count > 0)
                    {
                        var modifier = _equalityModifiers.Pop();
                        rightValue = ValueModifier.Apply(rightValue, MathematicExpression.GetOppositeOperation(modifier.Operation), modifier.Amount);
                    }

                    var diff = leftValue - rightValue;
                    if (diff != 0)
                    {
                        var modifier = new Requirement();

                        if (diff < 0)
                        {
                            modifier.Left = new Field {
                                Type = FieldType.Value, Value = (uint)(-diff)
                            };
                            modifier.Type = RequirementType.SubSource;
                        }
                        else
                        {
                            modifier.Left = new Field {
                                Type = FieldType.Value, Value = (uint)diff
                            };
                            modifier.Type = RequirementType.AddSource;
                        }

                        ((IList <Requirement>)context.Trigger).Insert(insertIndex, modifier);
                    }
                }

                var extraRequirement = context.LastRequirement;
                context.Trigger.Remove(extraRequirement);

                var requirement = context.LastRequirement;
                if (requirement != null)
                {
                    requirement.Operator = op;
                    requirement.Right    = extraRequirement.Left;
                }
            }

            return(null);
        }