Exemplo n.º 1
0
 /// <summary>
 /// Copies the data from another TFunction into this TFunction.
 /// </summary>
 /// <param name="function">The existing TFunction whose data is to be copied into the new TFunction.</param>
 public void CopyFrom(TFunction function)
 {
     Name = function.Name;
     HardCodedFunction = function.HardCodedFunction;
     CustomFunction    = function.CustomFunction;
     Block             = function.Block;
     CopyArguments(function.ArgNames, function.DefaultArgs);
 }
Exemplo n.º 2
0
 /// <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);
 }
Exemplo n.º 3
0
 /// <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 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>
        /// Executes the code contained inside the TBlock.
        /// </summary>
        /// <param name="interpreter">The interpreter that the method is being called from.</param>
        /// <param name="exitFromFunction">
        /// A value that will be set to whether 'exit()' was called during the block execution.
        /// </param>
        /// <param name="breakUsed">Whethe the 'break' keyword was used in the block.</param>
        /// <param name="beforeElse">Whether to execute the code before the 'else', if any.</param>
        /// <returns>A TType value of the result of the last executed statement in the block.</returns>
        public TType Execute(Interpreter interpreter, out bool exitFromFunction, out bool breakUsed,
                             bool beforeElse = true)
        {
            exitFromFunction = breakUsed = false;
            if (!beforeElse && (elseLocation < 0))
            {
                return(new TException(interpreter, "No else statement found"));
            }

            int start, end;

            // Decide which section of code should be executed
            if (elseAllowed)
            {
                if (beforeElse)
                {
                    start = 0;
                    end   = (elseLocation > 0 ? elseLocation : statements.Count) - 1;
                }
                else
                {
                    start = elseLocation + 1;
                    end   = statements.Count - 1;
                }
            }
            else
            {
                start = 0;
                end   = statements.Count - 1;
            }

            int stackLevel = interpreter.Stack.Level;
            // Keep track of the previous block (if any). Used when there are blocks within blocks
            TBlock previousCurrentBlock = interpreter.CurrentBlock;

            interpreter.CurrentBlock = this;

            TType returnValue = null, previousReturnValue = null;

            for (currentLine = start; currentLine <= end; ++currentLine)
            {
                previousReturnValue = returnValue;
                returnValue         = interpreter.Interpret(statements[currentLine], true);

                if (interpreter.Stack.Level < stackLevel) // if 'exit()' was called
                {
                    exitFromFunction = true;
                    break;
                }

                // Need to check for TBreak so that it can be used to break from a loop (if any)
                if (returnValue is TException)
                {
                    break;
                }
                if (returnValue is TBreak)
                {
                    returnValue = previousReturnValue ?? TNil.Instance;
                    breakUsed   = true;
                    break;
                }
            }

            // Now that the block has finished executing its code, restore control to the outer block
            interpreter.CurrentBlock = previousCurrentBlock;

            return(returnValue ?? TNil.Instance);
        }
        /// <summary>
        /// Parses an if statement.
        /// </summary>
        /// <param name="group">The group to parse.</param>
        /// <returns>
        /// If a statement or block of code is executed, then the result of that code is returned, otherwise the
        /// boolean result of the logical expression is returned. A TException is returned when there is an error.
        /// </returns>
        TType ParseIfGroup(Group group)
        {
            /* BNF for if statement:
             *      <if-statement> ::= 'if' <condition> ',' ( <statements> | <block-no-end> 'else' <statements> )
             *      <block-no-end> ::= <new-line> (<statement> <new-line>)*
             * The 'if' is assumed to have already been checked for
             */
            if (group.Count < 2)
                return new TException(this, "Statement could not be evaluated",
                    "if statement must be given a condition");

            int commaIndex = group.IndexOf(",");
            if (commaIndex < 0)
                return new TException(this, "if statment invalid", "comma required after condition");

            Group conditionGroup = new Group(null);
            conditionGroup.AddRange(group.GetRange(1, commaIndex - 1));
            TType value = ParseGroup(conditionGroup);
            if (value is TException) return value;

            TBoolean result = value as TBoolean;
            if (result == null)
            {
                bool success = false;
                TVariable variable = value as TVariable;
                if (variable != null)
                {
                    result = variable.Value as TBoolean;
                    if (result != null) success = true;
                }
                if (!success)
                    return new TException(this, "Condition does not evaluate to a boolean value", "yes or no");
            }

            if (group.Count > commaIndex + 1) // If there is no block after the 'if' <condition> ','
            {
                // If there is an else and it's at the end of the line, get a block for the else
                int elseIndex = group.IndexOf("else", commaIndex + 1);
                TBlock elseBlock = null;
                if (elseIndex == group.Count - 1)
                {
                    TException exception;
                    elseBlock = new TBlock(this, out exception, false);
                    if (exception != null) return exception;
                }

                if (result.Value)
                {
                    // Execute the code between the comma and the end of the statement or the else (if any).
                    // If there isn't anything to execute, then a block is created (near the end of this method)
                    Group statementGroup = new Group(null);
                    if (elseIndex < 0)
                        statementGroup.AddRange(group.GetRange(commaIndex + 1, group.Count - (commaIndex + 1)));
                    else
                        statementGroup.AddRange(group.GetRange(commaIndex + 1, elseIndex - (commaIndex + 1)));

                    if (statementGroup.Count > 0) return ParseGroup(statementGroup);
                }
                else if (elseIndex >= 0)
                {
                    if (elseBlock == null)
                    {
                        Group statementGroup = new Group(null);
                        statementGroup.AddRange(group.GetRange(elseIndex + 1, group.Count - (elseIndex + 1)));
                        if (statementGroup.Count > 0) return ParseGroup(statementGroup);
                    }
                    else
                    {
                        bool exitFromFunction, breakUsed;
                        return elseBlock.Execute(this, out exitFromFunction, out breakUsed);
                    }
                }
                else return result;
            }

            { // This code is executed if there is a block after the 'if' <condition> ',' instead of a single statement
                TException exception;
                TBlock block = new TBlock(this, out exception, true);
                if (exception != null) return exception;

                bool exitFromFunction, breakUsed;
                if (result.Value) return block.Execute(this, out exitFromFunction, out breakUsed, true);
                if (block.HasElse()) return block.Execute(this, out exitFromFunction, out breakUsed, false);
            }

            return result;
        }
 /// <summary>
 /// Parses a 'begin', indicating the start of a block of code.
 /// </summary>
 /// <returns>A TBlock on success, otherwise a TException.</returns>
 TType ParseBegin()
 {
     TException exception;
     TType block = new TBlock(this, out exception, false);
     return exception ?? block;
 }
        /// <summary>
        /// Parses an assignment operation.
        /// </summary>
        /// <param name="group">The group to parse.</param>
        /// <returns>The assigned variable or function on success, otherwise a TException.</returns>
        TType ParseAssignmentGroup(Group group)
        {
            /* BNF for assignment:
             *      <assignment>       ::= 'let' ( <variable-assignment> | <function-declaration> )
             *      <var-assignment>   ::= <identifier> '=' <expression>
             *      <func-declaration> ::= <identifier> '(' { <parameters> } ')' '=' <statements>
             *      <parameters>       ::= <identifier> { ',' <identifier> }*
             *      <statements>       ::= <block> | <statement>
             *      <block>            ::= <new-line> (<statement> <new-line>)* 'end'
             * You can probably guess what <identifier>, <statement> and <new-line> are
             * The 'let' is assumed to have already been checked for
             */
            int equalsIndex = group.IndexOf("=");
            if (equalsIndex < 0) return new TException(this, "Variable or function could not be assigned a value");

            // Could be assigning a dereferenced variable
            TException exception = ParseReferencesOfGroup(group, equalsIndex);
            if (exception != null) return exception;

            if (group.Count == 1) return new TException(this, "Could not assign variable", "no variable name given");

            string variableName = group[1] as string;
            TVariable existingVariable = null;

            if (variableName == null)
            {
                exception = new TException(this, "Could not assign variable", "invalid variable name given");

                // Check if an existing variable or function is being assigned
                Group groupToParse = group[1] as Group;
                TType value = group[1] as TType;
                if (groupToParse != null) value = ParseGroup(groupToParse);
                if (value == null) return exception;

                TVariable variable = value as TVariable;
                if (variable != null) existingVariable = variable;
                else
                {
                    TFunction function = value as TFunction;
                    if (function != null) variableName = function.Name;
                    else return exception;
                }
            }

            if (group.Count == 2) return new TException(this, "Variable could not be assigned a value");
            string assignmentOperator = group[2] as string;
            if (assignmentOperator == null) // Now we assume that we're dealing with a function declaration
            {
                Group paramGroup = group[2] as Group;
                if (paramGroup == null) // The user probably just wanted to assign a variable but made a typo
                    return new TException(this, "Variable could not be assigned a value",
                        "value to assign to variable must be given");

                // Get the identifiers of all the parameters within the brackets, keeping strict watch on comma usage
                TParameterList paramList = new TParameterList();
                bool commaExpected = false;
                for (int i = 0; i < paramGroup.Count; ++i)
                {
                    if (commaExpected && (i == paramGroup.Count - 1))
                        return new TException(this, "Parameters could not be parsed", "last parameter missing");

                    string paramName = paramGroup[i] as string;

                    if (commaExpected && (paramName != ",")) paramName = null;

                    if (paramName == null) return new TException(this, "Parameters could not be parsed",
                        "invalid parameter name given");

                    if (!commaExpected) paramList.Add(paramName);
                    commaExpected = !commaExpected;
                }

                exception = new TException(this, "Function could not be given a body", "function body must be given");
                if (group.Count == 3) return exception;
                assignmentOperator = group[3] as string;
                if (assignmentOperator == null) return exception;
                else if (assignmentOperator != "=") return exception;

                TFunction function;
                if (group.Count == 4) // statement is just 'let <i><params> =', so get a block
                {
                    TBlock block = new TBlock(this, out exception, false);
                    if (exception != null) return exception;
                    function = new TFunction(variableName ?? existingVariable.Identifier, block,
                        paramList.ParameterNames, null);
                }
                else // Create a single line function
                {
                    Group funcBody = new Group(null);
                    funcBody.AddRange(group.GetRange(4, group.Count - 4));
                    function = new TFunction(variableName ?? existingVariable.Identifier, funcBody.ToString(),
                        paramList.ParameterNames, null);
                }

                exception = TFunction.AddFunction(this, function);
                if (exception != null) return exception;

                return function;
            }
            { // Assume that we're dealing with a variable assigment
                exception = new TException(this, "Variable could not be assigned a value",
                    "value to assign to variable must be given");

                if (assignmentOperator != "=") return exception;
                if (group.Count == 3) return exception;

                // Parse the expression on the right hand side of the assigment operator, and if the result is a
                // TVariable, then get it's value (we don't want to assign the TVariable itself to the new TVariable)
                Group valueGroup = new Group(null);
                valueGroup.AddRange(group.GetRange(3, group.Count - 3));
                TType value = ParseGroup(valueGroup);
                if (value is TException) return value;

                TVariable variable = value as TVariable;
                if (variable != null) value = variable.Value;

                // Make sure we don't get any circular references; they cause stack overflows when outputting them
                variable = existingVariable ?? Stack.FindVariable(variableName);
                if (value == variable) return new TException(this, "Illegal assignment attempted",
                    "variables cannot reference themselves");

                TVariable circularRefCheckVar = value as TVariable;
                while (circularRefCheckVar != null)
                {
                    TVariable variableValue = circularRefCheckVar.Value as TVariable;
                    if (variableValue != null)
                    {
                        if (variableValue == variable)
                            return new TException(this, "Illegal assignment attempted",
                                "circular reference detected");
                        else circularRefCheckVar = variableValue;
                    }
                    else circularRefCheckVar = null;
                }

                if (variable == null) // If the variable doesn't exist already, add it to the stack variable dictionary
                {
                    variable = new TVariable(variableName, value);
                    Stack.AddVariable(variable);
                }
                else variable.Value = value;

                return variable;
            }
        }
        /// <summary>
        /// Parses a while loop statement.
        /// </summary>
        /// <param name="group">The group to parse.</param>
        /// <returns>TNil on success, otherwise a TException.</returns>
        TType ParseWhileGroup(Group group)
        {
            /* BNF for while loop:
             *      <while-loop> ::= 'while' <condition> ',' ( <statement> | <block> )
             * The 'while' is assumed to have already been checked for
             */
            if (group.Count == 1) return new TException(this, "Statement could not be evaluated",
                "while loop must be given a condition");

            int commaIndex = group.IndexOf(",");
            if (commaIndex < 0) return new TException(this, "while loop invalid", "comma required after condition");

            if (group.Count == 1)
                return new TException(this,
                    "Statement could not be evaluated", "while loop must be given a condition");

            Group conditionGroup = new Group(null);
            conditionGroup.AddRange(group.GetRange(1, commaIndex - 1));

            Group statementGroup = null;
            TBlock block = null;
            if (group.Count > commaIndex + 1) // If there is a statement after the comma, use that statement
            {
                statementGroup = new Group(null);
                statementGroup.AddRange(group.GetRange(commaIndex + 1, group.Count - (commaIndex + 1)));
            }
            else // Otherwise get a block
            {
                TException exception;
                block = new TBlock(this, out exception, false);
                if (exception != null) return exception;
            }

            TType returnValue = TNil.Instance;
            while (true)
            {
                // Parse the condition, and if it's true run the block or single statement, otherwise return MNil
                TType value = ParseGroup((Group)conditionGroup.Clone());
                if (value is TException) return value;

                TBoolean result = value as TBoolean;
                if (result == null)
                {
                    bool success = false;
                    TVariable variable = value as TVariable;
                    if (variable != null)
                    {
                        result = variable.Value as TBoolean;
                        if (result != null) success = true;
                    }
                    if (!success)
                        return new TException(this, "Condition does not evaluate to a boolean value", "yes or no");
                }

                if (result.Value)
                {
                    bool breakUsed = false;
                    if (statementGroup != null) returnValue = ParseGroup((Group)statementGroup.Clone());
                    else
                    {
                        bool exitFromFunction;
                        returnValue = block.Execute(this, out exitFromFunction, out breakUsed);
                        if (exitFromFunction) return returnValue;
                    }
                    if ((returnValue is TException) || breakUsed) return returnValue;
                }
                else return returnValue;

                if (!alive) return TNil.Instance;
            }
        }