0

I am doing an attempt to create a custom-interpreted programming language. It works by going over every line of code that the user has written in a script. But that is done in a function. The problem is that at the end of the function it calls itself to run the next line, and it keeps doing that until the interpreter reaches the end of the script. This is fine for small programs but if it reaches around 430 lines of code, the interpreter throws a StackOverflowException because the Execute() function was called too much. Currently, the interpreter supports variable declaration + assignment (boolean and float), if statements, comparisons, and calculations. So basically my question is how to prevent that StackOverflowException? Or is there any other way to do this interpreter thing without needing to rewrite the whole thing? This is done in C#-Dotnet 6.0 If someone could take a look at my code, that would be very nice.

Preprocess() is called first:

//Get links in code, get function indexes
private void PreprocessCode()
{
    var conditions = new List<ConditionBlock>();
    int depth = 0;
    //If statements
    for (int i = 0; i < lines.Count; i++)
    {
        if (string.IsNullOrEmpty(lines[i]) || string.IsNullOrWhiteSpace(lines[i]))
            continue;
        Lexer lexer = new Lexer(lines[i]);
        var tokens = lexer.MakeTokens();
        var condition = new ConditionBlock(i, depth);

        //Create if statement blockinfo
        if(tokens[0].type == TokenTypes.KEYWORD)
        {
            if(tokens[0].ValueMatches("if"))
            {
                condition.type = ConditionBlock.Type.If;
                conditions.Add(condition);
            }
            else if (tokens[0].ValueMatches("elif"))
            {
                condition.type = ConditionBlock.Type.Elseif;
                conditions.Add(condition);
            }
            else if (tokens[0].ValueMatches("else"))
            {
                condition.type = ConditionBlock.Type.Else;
                conditions.Add(condition);
            }
        }
        else if(tokens[0].type == TokenTypes.RCURLY)
        {
            depth--;
        }
        else if (tokens[0].type == TokenTypes.LCURLY)
        {
            depth++;
        }
    }
    for(int i = 0; i < conditions.Count; i++)
    {
        var condition = conditions[i];
        //Try find an elif|else block attached to a non-else block
        if(condition.type != ConditionBlock.Type.Else)
        {
            //Get next conditionindex
            for(int j = i + 1; j < conditions.Count; j++)
            {
                var nextCondition = conditions[j];
                if (condition.depth == nextCondition.depth)
                {
                    //Start of new if statement, so is end of current one
                    if (nextCondition.type == ConditionBlock.Type.If)
                        break;
                    condition.nextInChain = nextCondition;
                    nextCondition.previousInChain = condition;
                    break;
                }
                //Out of scope, so is end of if statement
                else if (nextCondition.depth < condition.depth)
                    break;
            }
        }
        conditionByLineIndex.Add(condition.start, condition);
    }
    //Start run
    Execute(0, lines.Count);
    return;
}

Execute() Function:

private void Execute(int lineIndex, int stop)
{
    if (lineIndex >= lines.Count || lineIndex == stop)
        return;
    Lexer lexer = new Lexer(lines[lineIndex]);
    var tokens = lexer.MakeTokens(); //Returns list of tokens in line
    this.tokens = tokens;
    tokenIndex = -1;
    Advance();

    //Check if current line is an if statement
    if (conditionByLineIndex.ContainsKey(lineIndex))
    {
        var toks = ToksBetweenIdx(0, tokens.Count);
        Expression expression = new Expression(toks);
        var condition = conditionByLineIndex[lineIndex];
        bool runCondition = false;
        if (toks.Count > 0 && condition.type != ConditionBlock.Type.Else)
            runCondition = expression.Evaluate();
        else if (condition.type == ConditionBlock.Type.Else)
            runCondition = true;


        if (condition.type == ConditionBlock.Type.Else)
            runCondition = true;
        if (condition.type == ConditionBlock.Type.If)
            hasRunChain = false;

        if (condition.type != ConditionBlock.Type.If)
        {
            var previousInChain = condition.previousInChain;
            while (previousInChain != null)
            {
                if (previousInChain.lastEvaluation)
                {
                    runCondition = false;
                    break;
                }
                previousInChain = previousInChain.previousInChain;
            }
        }
        conditionByLineIndex[lineIndex].lastEvaluation = runCondition;
        HandleCondition(runCondition, lineIndex, stop);
        return;
    }

    while (tokenIndex < tokens.Count)
    {
        //Skip over { and }
        if (currentToken.type == TokenTypes.RCURLY || currentToken.type == TokenTypes.LCURLY)
            Advance();
        if (currentToken.type == TokenTypes.KEYWORD)
        {
            if (currentToken.ValueMatches("var"))
            {
                Advance();
                if (currentToken.type == TokenTypes.IDENTIFIER)
                {
                    Token identifier = currentToken;
                    Advance();
                    if (currentToken.type == TokenTypes.EQUAL)
                    {
                        List<Token> toks = ToksAfterIdx(tokenIndex);
                        Expression expression = new Expression(toks);
                                              // identifier.value -> TYPE:IDENTIFIER|VALUE:thisIsAVariableName
                        currentVariables.CreateVariable(identifier.value, expression.Evaluate());
                        break;
                    }
                }
            }

        }
        else if (currentToken.type == TokenTypes.IDENTIFIER)
        {
            Token identifier = currentToken;
            if (tokenIndex + 1 == tokens.Count)
            {
                //Print out the value of the variable
                Console.WriteLine($"Print: {currentVariables.AccesVariable(identifier.value)}");
                break;
            }
            else
            {
                Advance();
                if (currentToken.type == TokenTypes.EQUAL)
                {
                    List<Token> toks = ToksAfterIdx(tokenIndex);
                    Expression expression = new Expression(toks);
                    currentVariables.AssignVariable(identifier.value, expression.Evaluate());
                    break;
                }
                else if (currentToken.type == TokenTypes.INCREMENT)
                {
                    float value = currentVariables.AccesVariable(identifier.value);
                    currentVariables.AssignVariable(identifier.value, value + 1);
                    break;

                }
                else if (currentToken.type == TokenTypes.DECREMENT)
                {
                    float value = currentVariables.AccesVariable(identifier.value);
                    currentVariables.AssignVariable(identifier.value, value - 1);
                    break;
                }
                else if (currentToken.type == TokenTypes.PLUSEQ)
                {
                    float value = currentVariables.AccesVariable(identifier.value);
                    List<Token> toks = ToksAfterIdx(tokenIndex);
                    Expression expression = new Expression(toks);
                    currentVariables.AssignVariable(identifier.value, value + expression.Evaluate());
                    break;
                }
                else if (currentToken.type == TokenTypes.MINEQ)
                {
                    float value = currentVariables.AccesVariable(identifier.value);
                    List<Token> toks = ToksAfterIdx(tokenIndex);
                    Expression expression = new Expression(toks);
                    currentVariables.AssignVariable(identifier.value, value - expression.Evaluate());
                    break;
                }
                else if (currentToken.type == TokenTypes.MULEQ)
                {
                    float value = currentVariables.AccesVariable(identifier.value);
                    List<Token> toks = ToksAfterIdx(tokenIndex);
                    Expression expression = new Expression(toks);
                    currentVariables.AssignVariable(identifier.value, value * expression.Evaluate());
                    break;
                }
                else if (currentToken.type == TokenTypes.DIVEQ)
                {
                    float value = currentVariables.AccesVariable(identifier.value);
                    List<Token> toks = ToksAfterIdx(tokenIndex);
                    Expression expression = new Expression(toks);
                    currentVariables.AssignVariable(identifier.value, value / expression.Evaluate());
                    break;
                }
            }
        }
    }
    if (lineIndex + 1 < stop && lineIndex + 1 < lines.Count)
    {
        //Run next line
        Execute(lineIndex + 1, stop);
    }
}

Greetings

Olivier Jacot-Descombes
  • 93,432
  • 11
  • 126
  • 171
MomoVR
  • 7
  • 4
  • 3
    You shouldn't use recursion for this problem. Look into [converting recursive algorithms to loops](https://stackoverflow.com/questions/159590/way-to-go-from-recursion-to-iteration). – Kirk Woll Jan 28 '22 at 16:18
  • Where specifically is the exception thrown? A stack trace would be useful. – John Glenn Jan 28 '22 at 16:21
  • @JohnGlenn The stack trace is going to be 430 calls deep lol – Nigel Jan 28 '22 at 16:22
  • Heh heh... yes, but having to copy the whole thing may be an enlightening experience. Was that a mean question? :) – John Glenn Jan 28 '22 at 16:24
  • 1
    There is too much code in these methods. There is also too much code duplication. Split up into smaller methods, and create more methods to remove code duplication. – Bent Tranberg Jan 28 '22 at 16:25
  • Haha yes, it is just a style of coding. Don't really like it when there are much methods in my code. But i'm gonna clean it up a bit more and use more methods. – MomoVR Jan 28 '22 at 18:03
  • @MomoVR Like it or not, more methods is easier to maintain, and makes the bite-sized chunks often re-useable. Plus it will keep you away from DRY violations, which are the ultimate symptom of bad code. Save yourself time now and learn to code effectively while you are still a beginner. – Nigel Jan 28 '22 at 19:25

1 Answers1

3

You are going to have to change your logic to not use recursion for reading each line. If you use recursion you will add a new function call to the stack for every layer of recursion, and the prior function calls will not be removed until the exit condition is met. If you make it 500 or so calls deep you can expect a stackoverflow exception.

Now, I don't have time to read over your code, but I can tell you what you need to do: Turn your recursive call into a loop.

Your code can probably be broken down into something like this:

void ExecuteProgramLine(int lineNumber)
{
   InterpretAndRunLine(lineNumber);
   ExecuteProgramLine(lineNumber + 1);
}

You will want to convert that to this:

for(int lineNumber = 0; lineNumber < fileLines) // (foreach loop is probably better)
{
    InterpretAndRunLine(lineNumber);
}
Nigel
  • 2,192
  • 1
  • 8
  • 23