/// <summary>
 /// A TFunction constructor.
 /// </summary>
 /// <param name="name">The name of the new Toast function.</param>
 /// <param name="function">A TBlock to be executed when the Toast function is called.</param>
 /// <param name="argNames">
 /// The argument names to use in the function. Pass null for the function to use a variable number of
 /// arguments.
 /// </param>
 /// <param name="defaultArgs">
 /// The default values of arguments, as TTypes. Use null in the array to specify that no default value should
 /// be used for a particular argument, or pass null to indicate that no arguments should have default values.
 /// </param>
 public TFunction(string name, TBlock block, string[] argNames, TType[] defaultArgs)
 {
     Name = name;
     Block = block;
     HardCodedFunction = null;
     CustomFunction = "";
     CopyArguments(argNames, defaultArgs);
 }
 /// <summary>
 /// A TFunction constructor.
 /// </summary>
 /// <param name="name">The name of the new Toast function.</param>
 /// <param name="function">A string of Toast code to be executed when the Toast function is called.</param>
 /// <param name="argNames">
 /// The argument names to use in the function. Pass null for the function to use a variable number of
 /// arguments.
 /// </param>
 /// <param name="defaultArgs">
 /// The default values of arguments, as TTypes. Use null in the array to specify that no default value should
 /// be used for a particular argument, or pass null to indicate that no arguments should have default values.
 /// </param>
 public TFunction(string name, string function, string[] argNames, TType[] defaultArgs)
 {
     Name = name;
     HardCodedFunction = null;
     CustomFunction = function;
     Block = null;
     CopyArguments(argNames, defaultArgs);
 }
        /// <summary>
        /// Does an equality comparison of one TType with another.
        /// </summary>
        /// <param name="interpreter">The interpreter that the method is being called from.</param>
        /// <param name="a">The left hand operand of the comparison.</param>
        /// <param name="b">The right hand operand of the comparison.</param>
        /// <param name="strict">
        /// Whether the equality should be approximate or not. Give true for exact equality comparisons, and false for
        /// approximate equality comparisons of TNumbers.
        /// </param>
        /// <returns>
        /// An TBoolean containing the result of the comparison. Returns a TException or null when there is an error.
        /// </returns>
        public static TType Equal(Interpreter interpreter, TType a, TType b, bool strict)
        {
            // If arguments are TVariable, get their value. The values of the TVariable need to be compared,
            // not the TVariable objects themselves
            TVariable variable = a as TVariable;
            if (variable != null) a = variable.Value;
            variable = b as TVariable;
            if (variable != null) b = variable.Value;

            // Make sure that each operand is of the same type. TNumbers are an exception; any TNumber derivative can
            // be compared with any other TNumber derivative
            if ((a.TypeName != b.TypeName) && !((a is TNumber) && (b is TNumber)))
                return new TException(interpreter,
                    "Type '" + a.TypeName + "' cannot be compared with type '" + b.TypeName + "'");

            // Using 'as' syntax instead of '()' for casting, because it looks cleaner. The result of the 'as' will not
            // return null because we've done the necessary check beforehand (i.e. if 'a' is a TNumber, then 'b' must
            // also be a TNumber)
            if (a is TNumber)
            {
                bool result;
                if (strict) result =
                    (System.Math.Abs((a as TNumber).TRealValue - (b as TNumber).TRealValue) < 0.000001);
                else
                {
                    double aVal = (a as TNumber).TRealValue, bVal = (b as TNumber).TRealValue;
                    if ((System.Math.Round(aVal) == bVal) || (System.Math.Round(bVal) == aVal)) result = true;
                    else result = (System.Math.Abs(aVal - bVal) < 0.5);
                }
                return new TBoolean(result);
            }
            else if (a is TBoolean)
            {
                return new TBoolean((a as TBoolean).Value == (b as TBoolean).Value);
            }
            else if (a is TString) {
                return new TBoolean((a as TString).Value == (b as TString).Value);
            }
            else if (a is TVariable) // i.e. if argument 'a' is a reference
            {
                return new TBoolean(a == b);
            }
            else if (a is TFunction)
            {
                TFunction funcA = a as TFunction, funcB = b as TFunction;
                return new TBoolean(
                    (funcA.HardCodedFunction == funcB.HardCodedFunction) &&
                    (funcA.CustomFunction == funcB.CustomFunction) &&
                    (funcA.Block == funcB.Block));
            }
            else if (a is TNil)
            {
                return new TBoolean(b is TNil);
            }

            return null;
        }
 /// <summary>
 /// Appends a TType to this TArgumentList. If a TArgumentList is passed, then the values contained in that
 /// TArgumentList are appended to this TArgumentList. If the values to be appended are TVariables, then the
 /// value of the TVariable will be appended instead of the TVariable itself (although if TVariable A
 /// containing TVariable B is passed, then TVariable B will be appended, as opposed to TVariable B's value).
 /// </summary>
 /// <param name="argument">The TType to append.</param>
 public void Add(TType argument)
 {
     TArgumentList argList = argument as TArgumentList;
     if (argList == null)
     {
         TVariable variable = argument as TVariable;
         if (variable == null) arguments.Add(argument);
         else arguments.Add(variable.Value);
     }
     else
     {
         for (int i = 0; i < argList.Count; ++i) arguments.Add(argList[i]);
     }
 }
 /// <summary>
 /// Does a 'not equal to' inequality comparison of one TType with another. Always does a strict comparison,
 /// unlike the Operations.Equal method where strictness can be specified.
 /// </summary>
 /// <param name="interpreter">The interpreter that the method is being called from.</param>
 /// <param name="a">The left hand operand of the comparison.</param>
 /// <param name="b">The right hand operand of the comparison.</param>
 /// <returns>
 /// An TBoolean containing the result of the comparison. Returns a TException or null when there is an error.
 /// </returns>
 public static TType NotEqual(Interpreter interpreter, TType a, TType b)
 {
     // Do an equality comparison of the arguments, and return the result if it's a TException or null.
     // If the result is a TBoolean then invert its value
     TType value = Equal(interpreter, a, b, true);
     TBoolean result = value as TBoolean;
     if (result == null) return value;
     result.Value = !result.Value;
     return result;
 }
            /// <summary>
            /// Takes two TNumbers and subtracts one from the other.
            /// </summary>
            /// <param name="interpreter">The interpreter that the method is being called from.</param>
            /// <param name="a">The left hand operand of the operation.</param>
            /// <param name="b">The right hand operand of the operation.</param>
            /// <returns>
            /// The TType resulting from the operation. An MExcpetion or null is returned when there is an error.
            /// </returns>
            public static TType Subtract(Interpreter interpreter, TType a, TType b)
            {
                // Try to get TNumber values from the TType arguments
                TNumber numberA, numberB;
                TException exception = AssignNumberValue(interpreter, out numberA, a);
                if (exception != null) return exception;
                exception = AssignNumberValue(interpreter, out numberB, b);
                if (exception != null) return exception;

                // No errors, but one or both of the arguments could be a TString; check them
                if ((numberA == null) || (numberB == null))
                    return new TException(interpreter, "Strings cannot be used in subtraction operations");

                switch (numberA.TypeName)
                {
                    case TType.T_INTEGER_TYPENAME:
                        {
                            // If the other operand is a fraction, treat this integer as a fraction (i.e. value/1)
                            TFraction rhsFraction = numberB as TFraction;
                            if (rhsFraction != null)
                            {
                                // Order of fractions matters in this case
                                TFraction lhsFraction = new TFraction(numberA.TIntegerValue, 1);
                                lhsFraction.Subtract(rhsFraction.Numerator, rhsFraction.Denominator);
                                return lhsFraction;
                            }
                            return new TInteger(numberA.TIntegerValue - numberB.TIntegerValue);
                        }

                    case TType.T_REAL_TYPENAME:
                        return new TReal(numberA.TRealValue - numberB.TRealValue);

                    case TType.T_FRACTION_TYPENAME:
                        {
                            // Create a copy of the left hand fraction
                            TFraction fraction = numberA as TFraction;
                            fraction = new TFraction(fraction.Numerator, fraction.Denominator);

                            // Convert the right hand operand to a fraction
                            long numerator, denominator;
                            TFraction otherFraction = numberB as TFraction;
                            if (otherFraction != null) // If it's a fraction, simply copy the values
                            {
                                numerator = otherFraction.Numerator;
                                denominator = otherFraction.Denominator;
                            }
                            else
                            {
                                // Check if it's a TInteger first. It might not need to use DoubleToFraction
                                if (numberB is TInteger)
                                {
                                    numerator = numberB.TIntegerValue;
                                    denominator = 1;
                                }
                                else Operations.Misc.DoubleToFraction(numberB.TRealValue, out numerator,
                                    out denominator);
                            }

                            fraction.Subtract(numerator, denominator);
                            return fraction;
                        }
                }

                return null;
            }
            /// <summary>
            /// Takes two TNumbers and returns the first one to the power of the other.
            /// </summary>
            /// <param name="interpreter">The interpreter that the method is being called from.</param>
            /// <param name="a">The left hand operand of the operation.</param>
            /// <param name="b">The right hand operand of the operation.</param>
            /// <returns>
            /// The TType resulting from the operation. An MExcpetion or null is returned when there is an error.
            /// </returns>
            public static TType Pow(Interpreter interpreter, TType a, TType b)
            {
                // Try to get TNumber values from the TType arguments
                TNumber numberA, numberB;
                TException exception = AssignNumberValue(interpreter, out numberA, a);
                if (exception != null) return exception;
                exception = AssignNumberValue(interpreter, out numberB, b);
                if (exception != null) return exception;

                // No errors, but one or both of the arguments could be a TString; check them
                if ((numberA == null) || (numberB == null))
                    return new TException(interpreter, "Strings cannot be used in exponentiation operations");

                switch (numberA.TypeName)
                {
                    case TType.T_INTEGER_TYPENAME:
                        return new TInteger(
                            (long)System.Math.Round(System.Math.Pow(numberA.TRealValue, numberB.TRealValue)));

                    case TType.T_REAL_TYPENAME:
                        return new TReal(System.Math.Pow(numberA.TRealValue, numberB.TRealValue));

                    case TType.T_FRACTION_TYPENAME:
                        TFraction fraction = numberA as TFraction;
                        long numerator =
                            (long)System.Math.Round(System.Math.Pow(fraction.Numerator, numberB.TRealValue));
                        long denominator =
                            (long)System.Math.Round(System.Math.Pow(fraction.Denominator, numberB.TRealValue));
                        return new TFraction(numerator, denominator);
                }

                return null;
            }
            /// <summary>
            /// Takes a TNumber and returns its absolute value.
            /// </summary>
            /// <param name="interpreter">The interpreter that the method is being called from.</param>
            /// <param name="value">The TNumber to get the absolute value of.</param>
            /// <returns>
            /// The TType resulting from the operation. An MExcpetion or null is returned when there is an error.
            /// </returns>
            public static TType Modulus(Interpreter interpreter, TType value)
            {
                TNumber number;
                TException exception = AssignNumberValue(interpreter, out number, value);
                if (exception != null) return exception;

                // No errors yet, but make sure it's not a string
                if (number == null) return new TException(interpreter, "Strings cannot be used in modulus operations");

                switch (number.TypeName)
                {
                    case TType.T_INTEGER_TYPENAME:
                        return new TInteger(System.Math.Abs(number.TIntegerValue));
                    case TType.T_REAL_TYPENAME:
                        return new TReal(System.Math.Abs(number.TRealValue));
                    case TType.T_FRACTION_TYPENAME:
                        TFraction fraction = number as TFraction;
                        return new TFraction(System.Math.Abs(fraction.Numerator), fraction.Denominator);
                        // No need to abs denominator as TFraction denominators are automatically kept positive
                }

                return null;
            }
            /// <summary>
            /// Compares one TNumber with another based on a given inequality operator.
            /// </summary>
            /// <param name="interpreter">The interpreter that the method is being called from.</param>
            /// <param name="a">The left hand operand of the comparison.</param>
            /// <param name="b">The right hand operand of the comparison.</param>
            /// <param name="inequality">The inequality operator to use in the comparison.</param>
            /// <returns>
            /// An TBoolean containing the result of the comparison. Returns a TException when there is an error.
            /// </returns>
            public static TType Inequality(Interpreter interpreter, TType a, TType b, string inequality)
            {
                // Try to get TNumber values from the TType arguments
                TNumber numberA, numberB;
                TException exception = AssignNumberValue(interpreter, out numberA, a);
                if (exception != null) return exception;
                exception = AssignNumberValue(interpreter, out numberB, b);
                if (exception != null) return exception;

                // No errors, but one or both of the arguments could be a TString; check them
                if ((numberA == null) || (numberB == null))
                    return new TException(interpreter, "Strings cannot be used in inequality comparisons");

                bool result;
                switch (inequality)
                {
                    case ">":
                        result = numberA.TRealValue > numberB.TRealValue;
                        break;
                    case ">=":
                        result = numberA.TRealValue >= numberB.TRealValue;
                        break;
                    case "<":
                        result = numberA.TRealValue < numberB.TRealValue;
                        break;
                    case "<=":
                        result = numberA.TRealValue <= numberB.TRealValue;
                        break;
                    default:
                        return new TException(interpreter, "Invalid inequality operator given");
                }

                return new TBoolean(result);
            }
            /// <summary>
            /// Takes two TNumbers or two TStrings (or up to two TVariables containing TNumbers or TStrings) and
            /// adds them together.
            /// </summary>
            /// <param name="interpreter">The interpreter that the method is being called from.</param>
            /// <param name="a">The left hand operand of the operation.</param>
            /// <param name="b">The right hand operand of the operation.</param>
            /// <returns>
            /// The TType resulting from the operation. An MExcpetion or null is returned when there is an error.
            /// </returns>
            public static TType Add(Interpreter interpreter, TType a, TType b)
            {
                // Convert arguments 'a' and 'b' into either a TNumber or a TString
                TNumber numberA, numberB;
                TString strA = null, strB = null;

                TException exception = AssignNumberValue(interpreter, out numberA, a);
                if (exception != null) return exception;

                if (numberA == null)
                {
                    // No errors yet, and numberA is null, so argument 'a' could be a TString or a TVariable
                    // containing a TString
                    strA = a as TString;
                    if (strA == null)
                    {
                        TVariable variable = a as TVariable;
                        if (variable != null) strA = variable.Value as TString;
                    }
                }
                if ((numberA == null) && (strA == null)) // Nothing useful, return a TException
                    return new TException(interpreter, "Value is not a number or string");

                // Same procedure for argument 'b'
                exception = AssignNumberValue(interpreter, out numberB, b);
                if (exception != null) return exception;
                if (numberB == null)
                {
                    strB = b as TString;
                    if (strB == null)
                    {
                        TVariable variable = b as TVariable;
                        if (variable != null) strB = variable.Value as TString;
                    }
                }
                if ((numberB == null) && (strB == null))
                    return new TException(interpreter, "Value is not a number or string");

                // Attempt addition if both operands are the same type, otherwise return a TException
                if ((numberB == null) && (strA == null))
                    return new TException(interpreter, "Attempted addition of a string to a number");
                else if ((numberA == null) && (strB == null))
                    return new TException(interpreter, "Attempted addition of a number to a string");
                else if ((numberA == null) && (numberB == null))
                {
                    return new TString(strA.Value + strB.Value);
                }
                else
                {
                    //The left hand operand decides the type of the returned value
                    switch (numberA.TypeName)
                    {
                        case TType.T_INTEGER_TYPENAME:
                            {
                                // If the other operand is a fraction, treat this integer as a fraction (i.e. value/1)
                                TFraction fraction = numberB as TFraction;
                                if (fraction != null)
                                {
                                    // Copy the right hand fraction and add the left hand integer to it
                                    fraction = new TFraction(fraction.Numerator, fraction.Denominator);
                                    fraction.Add(numberA.TIntegerValue, 1);
                                    return fraction;
                                }
                                return new TInteger(numberA.TIntegerValue + numberB.TIntegerValue);
                            }

                        case TType.T_REAL_TYPENAME:
                            return new TReal(numberA.TRealValue + numberB.TRealValue);

                        case TType.T_FRACTION_TYPENAME:
                            {
                                // Create a copy of the left hand fraction
                                TFraction fraction = numberA as TFraction;
                                fraction = new TFraction(fraction.Numerator, fraction.Denominator);

                                // Convert the right hand operand to a fraction
                                long numerator, denominator;
                                TFraction otherFraction = numberB as TFraction;
                                if (otherFraction != null) // If it's a fraction, simply copy the values
                                {
                                    numerator = otherFraction.Numerator;
                                    denominator = otherFraction.Denominator;
                                }
                                else
                                {
                                    // Check if it's a TInteger first. It might not need to use DoubleToFraction
                                    if (numberB is TInteger)
                                    {
                                        numerator = numberB.TIntegerValue;
                                        denominator = 1;
                                    }
                                    else Operations.Misc.DoubleToFraction(numberB.TRealValue, out numerator,
                                        out denominator);
                                }

                                fraction.Add(numerator, denominator);
                                return fraction;
                            }
                    }
                }

                return null;
            }
        /// <summary>
        /// Attempts to find a TNumber value for the TType given. If there is no error, but the TNumber reference
        /// given is still null after calling, then the TType is a TString (i.e. some arithmetic will work (+)).
        /// </summary>
        /// <param name="interpreter">The interpreter that the method is being called from.</param>
        /// <param name="number">The TNumber reference to assign the result to.</param>
        /// <param name="value">The TType to get a TNumber out of.</param>
        /// <returns>An exception if there was an error, otherwise null.</returns>
        private static TException AssignNumberValue(Interpreter interpreter, out TNumber number, TType value)
        {
            // Attempt to cast the TType 'value' argument to a TNumber. Failing that, check if it's a TString.
            // If the value is a TNumber or a TString, return null (i.e. no exception). If it's a TVariable then
            // work on the value of the TVariable, otherwise return an exception.

            number = value as TNumber;
            if ((number != null) || (value is TString)) return null;

            TVariable variable = value as TVariable;
            if (variable != null)
            {
                value = variable.Value;
                number = value as TNumber;
                if ((number != null) || (value is TString)) return null;
                return new TException(interpreter, "Value of '" + variable.Identifier + "' is not a number",
                    "it is of type '" + value.TypeName + "'");
            }

            return new TException(interpreter, "'" + value.ToCSString() + "' is not a number",
                "it is of type '" + value.TypeName + "'");
        }
 public TVariable(string name, TType value)
 {
     Identifier = name;
     Value = value;
 }
        /// <summary>
        /// A helper method used by TFunction constructors to copy argument names and argument default values passed
        /// to them.
        /// </summary>
        void CopyArguments(string[] argNames, TType[] defaultArgs)
        {
            if (argNames == null)
            {
                ArgNames = null;
                DefaultArgs = null;
            }
            else
            {
                ArgNames = new string[argNames.Length];
                if (argNames.Length > 0) argNames.CopyTo(ArgNames, 0);

                if (defaultArgs == null) DefaultArgs = new TType[argNames.Length];
                else
                {
                    DefaultArgs = new TType[argNames.Length];
                    if (defaultArgs.Length > 0) defaultArgs.CopyTo(DefaultArgs, 0);
                }
            }
        }
        /// <summary>
        /// Calls the TFunction with the specified arguments.
        /// </summary>
        /// <param name="interpreter">The interpreter that the method is being called from.</param>
        /// <param name="value">
        /// The argument to pass to the function. When passing multiple arguments, use a TArgumentList.
        /// </param>
        /// <returns></returns>
        public TType Call(Interpreter interpreter, TType value)
        {
            // If value is already a TArgumentList, then simply copy the reference, otherwise create a new
            // TArgumentList and add the value to it. This TArgument list is to be passed to the function.
            TArgumentList argList = value as TArgumentList;
            if (argList == null)
            {
                argList = new TArgumentList();
                argList.Add(value);
            }

            // If the function takes a fixed number of arguments...
            if (ArgNames != null)
            {
                // Occupy the argument list with the default arguments. If there is no default argument in a place
                // where an argument should have been given, return a TException.
                if (argList.Count < ArgNames.Length)
                {
                    for (int i = argList.Count; i < DefaultArgs.Length; ++i)
                    {
                        if (DefaultArgs[i] == null) break;
                        else argList.Add(DefaultArgs[i]);
                    }
                }

                if (argList.Count != ArgNames.Length)
                {
                    return new TException(interpreter, "Incorrect number of arguments for function '" + Name + "'",
                        argList.Count.ToString() + " out of " + ArgNames.Length.ToString() + " given");
                }
            }

            interpreter.Stack.Push();
            // Keep a track of the new stack level so that if a function calls 'exit()', which pops from the stack,
            // this will be able to be detected and the function call can be terminated properly
            int stackLevel = interpreter.Stack.Level;

            // Put the arguments on the current stack 'frame'
            if (ArgNames == null)
            {
                for (int i = 0; i < argList.Count; ++i)
                    interpreter.Stack.AddVariable(new TVariable("arg" + i.ToString(), argList[i]));
            }
            else
            {
                for (int i = 0; i < argList.Count; ++i)
                    interpreter.Stack.AddVariable(new TVariable(ArgNames[i], argList[i]));
            }

            TType returnValue = null;
            bool dontPop = false; // Set to true if the stack is popped during the call, i.e. if 'exit()' is called

            // Call the function
            if (HardCodedFunction != null) returnValue = HardCodedFunction.Invoke(interpreter, argList);
            else if (Block != null)
            {
                bool breakUsed;
                returnValue = Block.Execute(interpreter, out dontPop, out breakUsed);
            }
            else if (CustomFunction != "")
            {
                returnValue = interpreter.Interpret(CustomFunction, true);
                if (interpreter.Stack.Level < stackLevel) dontPop = true;
            }

            // If returnValue is a TVariable, then return the value of the TVariable (e.g. we want to return 5, as
            // opposed to the variable X which contains 5)
            TVariable variable = returnValue as TVariable;
            if (variable != null) returnValue = variable.Value;

            if (!dontPop) interpreter.Stack.Pop();

            return returnValue ?? TNil.Instance;
        }