NO_INLINE bool jspeFunctionDefinitionInternal(JsVar *funcVar, bool expressionOnly) {
  bool forcePretokenise = false;

  if (expressionOnly) {
    if (funcVar)
      funcVar->flags = (funcVar->flags & ~JSV_VARTYPEMASK) | JSV_FUNCTION_RETURN;
  } else {
    JSP_MATCH('{');
  #ifndef SAVE_ON_FLASH
    if (lex->tk==LEX_STR) {
      if (!strcmp(jslGetTokenValueAsString(), "compiled"))
        jsWarn("Function marked with \"compiled\" uploaded in source form");
      if (lex->tk==LEX_STR && !strcmp(jslGetTokenValueAsString(), "ram")) {
        JSP_ASSERT_MATCH(LEX_STR);
        forcePretokenise = true;
      }
    }
  #endif

    /* If the function starts with return, treat it specially -
     * we don't want to store the 'return' part of it
     */
    if (funcVar && lex->tk==LEX_R_RETURN) {
      funcVar->flags = (funcVar->flags & ~JSV_VARTYPEMASK) | JSV_FUNCTION_RETURN;
      JSP_ASSERT_MATCH(LEX_R_RETURN);
    }
  }
#ifndef ESPR_NO_LINE_NUMBERS
  // Get the line number (if needed)
  JsVarInt lineNumber = 0;
  if (funcVar && lex->lineNumberOffset && !(forcePretokenise||jsfGetFlag(JSF_PRETOKENISE))) {
    // jslGetLineNumber is slow, so we only do it if we have debug info
    lineNumber = (JsVarInt)jslGetLineNumber() + (JsVarInt)lex->lineNumberOffset - 1;
  }
#endif
  // Get the code - parse it and figure out where it stops
  JslCharPos funcBegin;
  jslSkipWhiteSpace();
  jslCharPosNew(&funcBegin, lex->sourceVar, lex->tokenStart);
  int lastTokenEnd = -1;
  lex->hadThisKeyword = lex->tk == LEX_R_THIS;
  if (!expressionOnly) {
    int brackets = 0;
    while (lex->tk && (brackets || lex->tk != '}')) {
      if (lex->tk == '{') brackets++;
      if (lex->tk == '}') brackets--;
      lastTokenEnd = (int)jsvStringIteratorGetIndex(&lex->it)-1;
      JSP_ASSERT_MATCH(lex->tk);
    }
    // FIXME: we might be including whitespace after the last token
  } else {
    JsExecFlags oldExec = execInfo.execute;
    execInfo.execute = EXEC_NO;
    jsvUnLock(jspeAssignmentExpression());
    execInfo.execute = oldExec;
    lastTokenEnd = (int)lex->tokenStart;
  }
  bool hadThisKeyword = lex->hadThisKeyword;
  // Then create var and set (if there was any code!)
  if (funcVar && lastTokenEnd>0) {
    // code var
    JsVar *funcCodeVar;
    if (!forcePretokenise && jsvIsNativeString(lex->sourceVar)) {
      /* If we're parsing from a Native String (eg. E.memoryArea, E.setBootCode) then
      use another Native String to load function code straight from flash */
      int s = (int)jsvStringIteratorGetIndex(&funcBegin.it) - 1;
      funcCodeVar = jsvNewNativeString(lex->sourceVar->varData.nativeStr.ptr + s, (unsigned int)(lastTokenEnd - s));
#ifdef SPIFLASH_BASE
    } else if (!forcePretokenise && jsvIsFlashString(lex->sourceVar)) {
        /* If we're parsing from a Flash String (eg. loaded from Storage on Bangle.js) then
      use another Flash String to load function code straight from flash*/
        int s = (int)jsvStringIteratorGetIndex(&funcBegin.it) - 1;
        funcCodeVar = jsvNewFlashString(lex->sourceVar->varData.nativeStr.ptr + s, (unsigned int)(lastTokenEnd - s));
#endif
    } else {
      if (jsfGetFlag(JSF_PRETOKENISE) || forcePretokenise) {
        funcCodeVar = jslNewTokenisedStringFromLexer(&funcBegin, (size_t)lastTokenEnd);
      } else {
        funcCodeVar = jslNewStringFromLexer(&funcBegin, (size_t)lastTokenEnd);
      }
    }
    jsvUnLock2(jsvAddNamedChild(funcVar, funcCodeVar, JSPARSE_FUNCTION_CODE_NAME), funcCodeVar);
    // scope var
    JsVar *funcScopeVar = jspeiGetScopesAsVar();
    if (funcScopeVar) {
      jsvUnLock2(jsvAddNamedChild(funcVar, funcScopeVar, JSPARSE_FUNCTION_SCOPE_NAME), funcScopeVar);
    }
#ifndef ESPR_NO_LINE_NUMBERS
    // If we've got a line number, add a var for it
    if (lineNumber) {
      JsVar *funcLineNumber = jsvNewFromInteger(lineNumber);
      if (funcLineNumber) {
        jsvUnLock2(jsvAddNamedChild(funcVar, funcLineNumber, JSPARSE_FUNCTION_LINENUMBER_NAME), funcLineNumber);
      }
    }
#endif
  }

  jslCharPosFree(&funcBegin);
  if (!expressionOnly) JSP_MATCH('}');
  return hadThisKeyword;
}