/// <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> /// The method that represents the 'exit' function in the language. /// Exits from the current function, returning any value passed to the function. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="args">The arguments being passed to the function as a TArgumentList.</param> /// <returns>The argument passed to the function. If no arguments were passed, it returns TNil.</returns> public static TType Exit(Interpreter interpreter, TArgumentList args) { // Get the argument passed (if any) to return TType returnValue = TNil.Instance; if (args.Count > 0) returnValue = args[0]; // Pops from the function stack or kill the interpreter if the stack level is already at it's lowest // When this method returns, the calling method should check the stack level and exit accordingly if (interpreter.Stack.Level <= 1) interpreter.Kill(); else interpreter.Stack.Pop(); return returnValue; }
/// <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 + "'"); }
static void Main(string[] args) { Types.TFunction.Init(); Interpreter interpreter = new Interpreter(); interpreter.Run(args.Length > 0 ? args[0] : ""); }
/// <summary> /// The method that represents the 'load' function in the language. /// Loads a script file with the specified file name (given by a TString) and runs it. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="args">The arguments being passed to the function as a TArgumentList.</param> /// <returns>TNil</returns> public static TType Load(Interpreter interpreter, TArgumentList args) { TString fileName = args[0] as TString; if (fileName == null) return new TException(interpreter, "Name of file to load must be given as a string"); interpreter.LoadFile(fileName.Value); return TNil.Instance; }
/// <summary> /// The method that represents the 'read_string' function in the language. /// Reads in a string returns it as a TString. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="args">The arguments being passed to the function as a TArgumentList.</param> /// <returns>An TString representing the string that was entered.</returns> public static TType ReadString(Interpreter interpreter, TArgumentList args) { return new TString(System.Console.ReadLine()); }
/// <summary> /// The method that represents the 'read' function in the language. /// Reads in a string and converts it into the most suitable TType. /// If the string cannot be converted, it just returns the string as a TString. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="args">The arguments being passed to the function as a TArgumentList.</param> /// <returns>An TType of the type that the entered string represents best.</returns> public static TType Read(Interpreter interpreter, TArgumentList args) { // Read in a string and convert it to a suitable TType with TType.Parse. string str = System.Console.ReadLine(); TType value = TType.Parse(interpreter, str); if (value is TException) value = new TString(str); // If the string can't be parsed, return it as a TString return value; }
/// <summary> /// The method that represents the 'random' function in the language. /// Generates a random TInteger between 0 and the given value - 1. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="args">The arguments being passed to the function as a TArgumentList.</param> /// <returns>An TInteger with a value between 0 and the given value - 1</returns> public static TType Random(Interpreter interpreter, TArgumentList args) { // Check if the argument passed is a number, and return a new TInteger from the random value generated TNumber maximum = args[0] as TNumber; if (maximum == null) return new TException(interpreter, "Arguments of type '" + args[0].TypeName + "' cannot be used by random function"); return new TInteger(randomNumberGenerator.Next((int)maximum.TIntegerValue)); }
/// <summary> /// The method that represents the 'print' function in the language. /// Takes any number of values and writes their string values. /// </summary> /// <param name="interpreter">The interpreter that the method is being called from.</param> /// <param name="args">The arguments being passed to the function as a TArgumentList.</param> /// <returns>TNil</returns> public static TType Print(Interpreter interpreter, TArgumentList args) { // Loop through arguments, append their string values to a StringBuilder, and output the StringBuilder StringBuilder str = new StringBuilder(); for (int i = 0; i < args.Count; ++i) { TString arg = args[i] as TString; if (arg == null) str.Append(args[i].ToCSString()); else str.Append(arg.Value); } System.Console.WriteLine(str); return TNil.Instance; }