whole_func_string: function parseLine(line, i, labels, notLineNumber, manager) {
      line = line.trim();
  
      // Is it an empty line?
      if (line === "") return new EmptyStatement('empty');
  
      if (line.indexOf("'") === 0 || line.toUpperCase() === "REM" || line.toUpperCase().indexOf("REM ") === 0) {
          return new CommentStatement(line.substring(line.indexOf(" ")).trim());
      }
  
      var bracketPositions;
      function getPositions(ln) {
          return util.findPositions(ln, [
              { start: '(', end: ')' },
              { start: '"', end: '"' }
          ]);
      }
      bracketPositions = getPositions(line);
  
      // See if there is a comment
      var startCommentIndex = util.indexOfOutside(line, "'", 0, bracketPositions);
      if (startCommentIndex !== -1) {
          line = line.substring(0, startCommentIndex).trim();
          bracketPositions = getPositions(line);
      }
  
      // Is it a label?
      if (line[line.length - 1] === ':') {
          var labelName = line.substring(0, line.length - 1).toLowerCase();
          labels[labelName] = i;
          return new EmptyStatement('label');
      }
  
      if (line.indexOf('END IF') === 0) line = 'ENDIF';
  
      // Find first space, but only outside of brackets
      var spaceIndex = util.indexOfOutside(line, ' ', 0, bracketPositions);
  
      // If the line is only a line number
      if (spaceIndex === -1) {
          var parsedLine = parseInt(line);
          if (!notLineNumber && !isNaN(parseInt(line))) {
              labels[line] = i;
              return new EmptyStatement('label');
          }
      }
  
      var commandSection, argumentSection;
      if (spaceIndex !== -1) {
          commandSection = line.substring(0, spaceIndex).trim();
          argumentSection = line.substring(spaceIndex).trim();
  
          // Is it a line number?
          if (!notLineNumber && !isNaN(parseInt(commandSection))) {
              labels[commandSection] = i;
              return parseLine(argumentSection, i, labels, true, manager);
          }
  
          // If it follows the pattern x = y or x =y, it must be an assignment
          if (argumentSection[0] === '=') {
              return new AssignmentStatement(new VariableStatement(commandSection), new ExpressionStatement(argumentSection.substring(1).trim()));
          }
  
          // If there is an equal sign in the command, it must be an assignment
          var cmdEqualIndex = commandSection.indexOf('=');
          if (cmdEqualIndex !== -1) {
              var equalLine = commandSection + ' ' + argumentSection;
              var varName = equalLine.substring(0, cmdEqualIndex).trim();
              var varExpr = equalLine.substring(cmdEqualIndex + 1).trim();
              return new AssignmentStatement(new VariableStatement(varName), new ExpressionStatement(varExpr));
          }
      } else {
          commandSection = line;
          argumentSection = '';
  
          // If there is an equal sign, it must be an assignment (with no space, e.g. x=y)
          var equalIndex = commandSection.indexOf('=');
          if (equalIndex !== -1) {
              var variableName = commandSection.substring(0, equalIndex);
              var variableExpr = commandSection.substring(equalIndex + 1);
              return new AssignmentStatement(new VariableStatement(variableName), new ExpressionStatement(variableExpr));
          }
  
          // Is it a root-level function call?
          var bracketIndex = commandSection.indexOf('(');
          if (bracketIndex !== -1) {
              var endBracketIndex = commandSection.indexOf(')');
              if (endBracketIndex === -1) return new SyntaxError('Unexpected open bracket');
              var functionName = commandSection.substring(0, bracketIndex);
              if (!isNaN(parseInt(functionName))) return new SyntaxError('Expected function name');
              var args = commandSection.substring(bracketIndex + 1, endBracketIndex);
              return new FunctionStatement(functionName, args);
          }
      }
  
      commandSection = commandSection.toUpperCase();
      return new CommandStatement(commandSection.toLowerCase(), argumentSection, manager, i);
  }
func_code_string: function parseLine(line, i, labels, notLineNumber, manager) {
      line = line.trim();
  
      // Is it an empty line?
      if (line === "") return new EmptyStatement('empty');
  
      if (line.indexOf("'") === 0 || line.toUpperCase() === "REM" || line.toUpperCase().indexOf("REM ") === 0) {
          return new CommentStatement(line.substring(line.indexOf(" ")).trim());
      }
  
      var bracketPositions;
      function getPositions(ln) {
          return util.findPositions(ln, [
              { start: '(', end: ')' },
              { start: '"', end: '"' }
          ]);
      }
      bracketPositions = getPositions(line);
  
      // See if there is a comment
      var startCommentIndex = util.indexOfOutside(line, "'", 0, bracketPositions);
      if (startCommentIndex !== -1) {
          line = line.substring(0, startCommentIndex).trim();
          bracketPositions = getPositions(line);
      }
  
      // Is it a label?
      if (line[line.length - 1] === ':') {
          var labelName = line.substring(0, line.length - 1).toLowerCase();
          labels[labelName] = i;
          return new EmptyStatement('label');
      }
  
      if (line.indexOf('END IF') === 0) line = 'ENDIF';
  
      // Find first space, but only outside of brackets
      var spaceIndex = util.indexOfOutside(line, ' ', 0, bracketPositions);
  
      // If the line is only a line number
      if (spaceIndex === -1) {
          var parsedLine = parseInt(line);
          if (!notLineNumber && !isNaN(parseInt(line))) {
              labels[line] = i;
              return new EmptyStatement('label');
          }
      }
  
      var commandSection, argumentSection;
      if (spaceIndex !== -1) {
          commandSection = line.substring(0, spaceIndex).trim();
          argumentSection = line.substring(spaceIndex).trim();
  
          // Is it a line number?
          if (!notLineNumber && !isNaN(parseInt(commandSection))) {
              labels[commandSection] = i;
              return parseLine(argumentSection, i, labels, true, manager);
          }
  
          // If it follows the pattern x = y or x =y, it must be an assignment
          if (argumentSection[0] === '=') {
              return new AssignmentStatement(new VariableStatement(commandSection), new ExpressionStatement(argumentSection.substring(1).trim()));
          }
  
          // If there is an equal sign in the command, it must be an assignment
          var cmdEqualIndex = commandSection.indexOf('=');
          if (cmdEqualIndex !== -1) {
              var equalLine = commandSection + ' ' + argumentSection;
              var varName = equalLine.substring(0, cmdEqualIndex).trim();
              var varExpr = equalLine.substring(cmdEqualIndex + 1).trim();
              return new AssignmentStatement(new VariableStatement(varName), new ExpressionStatement(varExpr));
          }
      } else {
          commandSection = line;
          argumentSection = '';
  
          // If there is an equal sign, it must be an assignment (with no space, e.g. x=y)
          var equalIndex = commandSection.indexOf('=');
          if (equalIndex !== -1) {
              var variableName = commandSection.substring(0, equalIndex);
              var variableExpr = commandSection.substring(equalIndex + 1);
              return new AssignmentStatement(new VariableStatement(variableName), new ExpressionStatement(variableExpr));
          }
  
          // Is it a root-level function call?
          var bracketIndex = commandSection.indexOf('(');
          if (bracketIndex !== -1) {
              var endBracketIndex = commandSection.indexOf(')');
              if (endBracketIndex === -1) return new SyntaxError('Unexpected open bracket');
              var functionName = commandSection.substring(0, bracketIndex);
              if (!isNaN(parseInt(functionName))) return new SyntaxError('Expected function name');
              var args = commandSection.substring(bracketIndex + 1, endBracketIndex);
              return new FunctionStatement(functionName, args);
          }
      }
  
      commandSection = commandSection.toUpperCase();
      return new CommandStatement(commandSection.toLowerCase(), argumentSection, manager, i);
  }
func_documentation_string: Parses a line and returns the statement
  
  @param {String} line The line to parse
  @param {Number} i The line index
  @param {Object} labels The list of labels
  @param {Boolean} notLineNumber If true, wont see if it starts with a line number
  @param {BlockManager} manager The block manager
  @returns {AssignmentStatement|CommentStatement|CommandStatement|EmptyStatement|FunctionStatement|SyntaxError}