0

I am creating a miniature computer using an Arduino, and I am creating a full-scale interpreter for it. Everything works, and the code begins to run after calling the runCode method, but once the run is finished it doesn't return to the calling function. I have Serial outputs to tell me where the code reaches.

My code contains an "Function" object with a name and a function pointer, an object to run the code (with a working Lexer object that splits the inputted code by spaces), and an in-computer editor (which completely works except the one function) to input programs to be run.

The function that calls the runCode method for my code-running object (called Joker) is in the editor. The problem is, once the runCode finishes, it does not return. The system halts. I will post the Function class, runCode Method, and calling method.

P.S. There are Serial prints to determine where the code reaches. It outputs "Function End!", but not "Failed!"/"Succeeded".

P.P.S. It uses a parallax 4x20 Back-lit Single-pin Serial LCD which completely 100% works.

P.P.P.S It uses the QueueList library which is basically a dynamic list that works like a stack.

One more note: The code heavliy uses dynamic lists, function pointers, and lambda expressions, so.... be prepared....


Code for Function class (no it does not have any actual functions):

class Function {
public:
  String f_name;
  bool (*f_run) (QueueList<float> &stack, SoftwareSerial &lcd);
};

Code for runCode method:

bool Joker::runCode(QueueList<String> lineTable, SoftwareSerial &lcd) {
  // Set up lexer
  Lexer lexer;
  // Get all lexer words
  lexer.getWords(lineTable);

  String cur_word = "";
  float num_val = 0.0f;
  bool quit = false;

  while(!quit) {
    cur_word = lexer.nextWord();
    if(cur_word == "")
      quit = true;
    else {
      // Get word in uppercase
      cur_word.toUpperCase();

      Serial.println(cur_word);

      // Parse the first number from it
      num_val = cur_word.toFloat();

      // Check if we have a function
      if(queueListContains(cur_word, dictionary)) {
        Function theFunc = getDictionaryFunction(cur_word, dictionary);
        if(!theFunc.f_run(stack, lcd)) {
          error(F("Unable to process word!!"), lcd);
          return false;
        }
      } else if(num_val != 0) {         // check if the parse was good
        stack.push(num_val);            // if it was: push num to the stack
      } else if(num_val == 0) {         // if zero it could be parsing a zero or it could have failed
        if(cur_word.indexOf("0") != -1) // if it didn't fail:
          stack.push(num_val);          // push to the stack
        else {                          // otherwise:
          error(F("Unknown word!!"), lcd);   // Unknown word error
          return false;
        }
      } else {
        error(F("Unknown word!!"), lcd);
        return false;
      }
    }
  }

  // Test printing the stack
  /*while(!stack.isEmpty()) {
    lcd.print(stack.pop());
    lcd.print(' ');
  }*/

  Serial.print(F("Function end!"));
  return true;
}

And the caller method:

void line_editor::RunProgram(SoftwareSerial &lcd) {
  Serial.println("Running program!!");
  // Move to top line
  currentLine = 0;
  while(!lineTableB.isEmpty()) {
    lineTableA.push(lineTableB.pop());
    // Serial.print(lineTableB.peek());
  }

  // Clear the string
  lcd.write(12);
  delay(5);
  lcd.write(128);

  // Joker language object
  Joker joker;

  QueueList<Function> PrintingWords;

  // Create the printing functions
  Function printWord;
  printWord.f_name = "BC"; // print "Beginning Card"
  printWord.f_run = [] (QueueList<float> &stack, SoftwareSerial &lcd) {
    if(stack.isEmpty()) {
      return false;
    }

    float num = stack.pop();
    lcd.print(num);
    lcd.write(13);

    return true;
  };
  PrintingWords.push(printWord);

  // Print the whole stack
  Function printStack;
  printStack.f_name = "AC"; // print "All Cards"
  printStack.f_run = [] (QueueList<float> &stack, SoftwareSerial &lcd) {
    while(!stack.isEmpty()) {
      lcd.print(stack.pop());
      lcd.print(' ');
    }

    lcd.write(13);

    return true;
  };
  PrintingWords.push(printStack);

  // Add all the functions
  joker.addWords(PrintingWords);

  // Run Program here...
  if(joker.runCode(lineTableA, lcd))
    Serial.println("Success!!");
  else
    Serial.println("Failed!!");

  // lineTableA.push(lineTableB.pop());

  Serial.println("Here!!");

  return;
}

Sorry if I confused anyone. If there are any questions, then leave a comment, and I'll try to clear up the confusion. Any help would be appreciated.

EDIT::ADDING FULL CODE: Here is a LINK to the rest of the code on github.

Also, here is the rest of the Joker.cpp:

void Joker::addWords(QueueList<Function> new_dict) {
  while(!new_dict.isEmpty()) {
    dictionary.push(new_dict.pop());
  }
}

bool Joker::queueListContains(String theWord, QueueList<Function> &list) {
  Serial.println("Finding word:");

  QueueList<Function> temp;

  while(!list.isEmpty()) {
    Function r = list.pop();
    temp.push(r);

    Serial.print(">> ");
    Serial.println(theWord);
    Serial.print(">> equals? ");
    Serial.println(r.f_name);
    delay(5);

    if(r.f_name.equals(theWord)) {
      while(!temp.isEmpty())
        list.push(temp.pop());
      return true;
    }
  }

  while(!temp.isEmpty())
    list.push(temp.pop());

  return false;
}

Function Joker::getDictionaryFunction(String theWord, QueueList<Function> &list) {
  QueueList<Function> temp;

  while(!list.isEmpty()) {
    Function theFunction = list.pop();
    temp.push(theFunction);

    if(theFunction.f_name.equals(theWord)) {
      while(!temp.isEmpty()) {
        list.push(temp.pop());
      }
      return theFunction;
    }
  }
}

void Joker::error(String message, SoftwareSerial &lcd) {
  lcd.write(12);
  delay(5);
  lcd.write(128);

  lcd.print(message);

  delay(2000);
}

And the header:

#pragma once

class Joker {
private:
  bool queueListContains(String theWord, QueueList<Function> &list);
  Function getDictionaryFunction(String theWord, QueueList<Function> &list);

public:
  QueueList<float> stack;
  QueueList<Function> dictionary;

  void error(String message, SoftwareSerial &lcd);
  void addWords(QueueList<Function> new_dict);
  bool runCode(QueueList<String> lineTable, SoftwareSerial &lcd);
};
6
  • Please post a Minimal, Complete, and Verifiable example.
    – Nick Gammon
    Commented Feb 16, 2016 at 2:19
  • What Arduino board and IDE version are you using? Most boards have very limited amount of memory. Commented Feb 16, 2016 at 6:41
  • He tagged it Mega.
    – Nick Gammon
    Commented Feb 16, 2016 at 6:43
  • In principle, I would avoid the String class, because that could fragment memory. However in this particular case, it is probably something more sinister.
    – Nick Gammon
    Commented Feb 16, 2016 at 6:44
  • The full code link was a very very very bad way to upload code. Commented Feb 16, 2016 at 13:30

1 Answer 1

1

The short answer here is - It does not return because you have corrupted the stack.
The return address is pushed onto the stack as part of the function call. If you overwrite (corrupt) that address the program cannot jump back to where it was (return).

So where is the cause of the problem?

Does getDictionaryFunction(cur_word, dictionary) ever return NULL or an invalid pointer? The code tries to execute it anyway. EDIT: What happens below when theWord is not matched or list is empty? Actually I'm surprised this compiles, since there's a few paths where no value is returned. (I'm no expert on the GCC AVR compiler, but what happens if we don't assign a return value at all?)

Function Joker::getDictionaryFunction(String theWord, QueueList < Function > &list)
{
    QueueList < Function > temp;

    while (!list.isEmpty())
    {
        Function theFunction = list.pop();
        temp.push(theFunction);

        if (theFunction.f_name.equals(theWord))
        {
            while (!temp.isEmpty())
            {
                list.push(temp.pop());
            }
            return theFunction;
        }
    }
    // BAD-JUJU HAPPENS IF WE GET HERE
    assert(false);
}

Coding style tip: except in trivial cases, it's good to only return from the bottom of your function. Then the above never happens, and supposedly it's easier for the compiler to optimise. NOTE: I'm unsure if assert() will even do anything on Arduino. It's left as a marker to mean Handle Error Here.

Function Joker::getDictionaryFunction(String theWord, QueueList < Function > &list)
{
    Function dict_func = NULL;
    QueueList < Function > temp;

    while (!list.isEmpty())
    {
        Function theFunction = list.pop();
        temp.push(theFunction);

        if (theFunction.f_name.equals(theWord))
        {
            while (!temp.isEmpty())
                list.push(temp.pop());
            dict_func = theFunction;
            break;
        }
    }
    assert(dict_func != NULL);  // This should never happen
    return dict_func;
}

Coding style tip: WoRd CasE ~ lower_case, mixedCase, CapitalCase, UPPER_CASE. I use lower case for all_variable_names, mixed case for functionNames(), capital case for ClassNames and StructureNames, and upper case for MACROS and CONSTANT_DEFINITIONS. Pick a system, and stick to it absolutely.

Have you run out of memory - the stack could have hit the heap. What sort of Arduino are you using?

On an Uno, with only 2k of RAM, you might need to make some scope-cuts. Even on an Arduino Mega, you still only have 8k.

Maybe you can make use of a freeMemory() function. This will give you an indication of the free space between the stack and the heap. The AdaFruit version is below, but there are others too.

int freeRam () 
{
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

It may be worthwhile reading their page on Arduino memory usage.

So after having a browse through your code, I can't see any obvious problems. Fix the getDictionaryFunction() so you know if it's failing. Check to see if you're running out of RAM - you are making heavy use of it copying strings around, especially with that template class.

Maybe consider re-compiling the code on a workstation in GCC - it wouldn't take much, just change your serial outputs to printf(), and hard-code your input data (or read from a file). That will enable easier debugging.

4
  • He has a Mega, from the tags. However I agree in principle that the stack is probably corrupted. Why, it is hard to say from that snippet.
    – Nick Gammon
    Commented Feb 16, 2016 at 6:42
  • @NickGammon I will add the rest of the code. Commented Feb 16, 2016 at 12:18
  • I was hoping for a minimal example that demonstrates the issue.
    – Nick Gammon
    Commented Feb 16, 2016 at 20:28
  • Agreed - dude is clobbering the stack. Not surprising, considering what he is trying to make his arduino do. Commented Feb 18, 2016 at 4:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.