script_store.cpp 8.73 KB
#include "script_store.h"
#include "string.h"
#include <stddef.h>

extern "C" {
#include "py/lexer.h"
#include "py/nlr.h"
}

namespace Code {

constexpr char ScriptStore::k_scriptExtension[];
constexpr char ScriptStore::k_defaultScriptName[];

ScriptStore::ScriptStore() :
  m_accordion(m_scriptData, k_scriptDataSize)
{
  addScriptFromTemplate(ScriptTemplate::UartChat());
  addScriptFromTemplate(ScriptTemplate::Factorial());
  addScriptFromTemplate(ScriptTemplate::Mandelbrot());
  addScriptFromTemplate(ScriptTemplate::Polynomial());
}

const Script ScriptStore::scriptAtIndex(int index, EditableZone zone) {
  assert(index >= 0 && index < numberOfScripts());
  size_t nameBufferSize = 0;
  size_t contentBufferSize = 0;
  int accordionIndex;

  // Move the Free Space at the end of the correct string.
  switch (zone) {
    case EditableZone::None:
      break;
    case EditableZone::Name:
      accordionIndex = accordionIndexOfNameOfScriptAtIndex(index);
      nameBufferSize = m_accordion.sizeOfEditableBufferAtIndex(accordionIndex);
      break;
    case EditableZone::Content:
      accordionIndex = accordionIndexOfContentOfScriptAtIndex(index);
      contentBufferSize = m_accordion.sizeOfEditableBufferAtIndex(accordionIndex);
      break;
  }

  // Compute the positions and lengths of the Script Marker, Name and Content.
  const char * marker = m_accordion.bufferAtIndex(accordionIndexOfMarkersOfScriptAtIndex(index));
  const char * name = m_accordion.bufferAtIndex(accordionIndexOfNameOfScriptAtIndex(index));
  if (nameBufferSize == 0) {
    nameBufferSize = strlen(name);
  }
  const char * content = m_accordion.bufferAtIndex(accordionIndexOfContentOfScriptAtIndex(index));
  if (contentBufferSize == 0) {
    contentBufferSize = strlen(content);
  }
  return Script(marker, name, nameBufferSize, content, contentBufferSize);
}

const Script ScriptStore::scriptNamed(const char * name) {
  for (int i = 0; i < numberOfScripts(); i++) {
    int accordionIndex = accordionIndexOfNameOfScriptAtIndex(i);
    const char * currentScriptName = m_accordion.bufferAtIndex(accordionIndex);
    if (strcmp(currentScriptName, name) == 0) {
      return scriptAtIndex(i);
    }
  }
  return Script();
}

int ScriptStore::numberOfScripts() {
  return (m_accordion.numberOfBuffers())/Script::NumberOfStringsPerScript;
}

bool ScriptStore::addNewScript() {
  addScriptFromTemplate(ScriptTemplate::UartChat());
  addScriptFromTemplate(ScriptTemplate::Factorial());
  addScriptFromTemplate(ScriptTemplate::Mandelbrot());
  addScriptFromTemplate(ScriptTemplate::Polynomial());
  return addScriptFromTemplate(ScriptTemplate::Empty());
}

bool ScriptStore::renameScriptAtIndex(int index, const char * newName) {
  assert (index >= 0 && index < numberOfScripts());
  int accordionIndex = accordionIndexOfNameOfScriptAtIndex(index);
  return m_accordion.replaceBufferAtIndex(accordionIndex, newName);
}

void ScriptStore::switchAutoImportAtIndex(int index) {
  assert(index >= 0 && index < numberOfScripts());
  Script script = scriptAtIndex(index);
  bool autoImportation = script.autoImport();
  int accordionIndex = accordionIndexOfMarkersOfScriptAtIndex(index);
  if (autoImportation) {
    const char autoImportationString[2] = {Script::NoAutoImportationMarker, 0};
    m_accordion.replaceBufferAtIndex(accordionIndex, autoImportationString);
    return;
  }
  const char autoImportationString[2] = {Script::AutoImportationMarker, 0};
  m_accordion.replaceBufferAtIndex(accordionIndex, autoImportationString);
}

void ScriptStore::deleteScriptAtIndex(int index) {
  assert (index >= 0 && index < numberOfScripts());
  int accordionIndex = accordionIndexOfContentOfScriptAtIndex(index);
  // We delete in reverse order because we want the indexes to stay true.
  m_accordion.deleteBufferAtIndex(accordionIndex);
  m_accordion.deleteBufferAtIndex(accordionIndex-1);
  m_accordion.deleteBufferAtIndex(accordionIndex-2);
}

void ScriptStore::deleteAllScripts() {
  m_accordion.deleteAll();
}

bool ScriptStore::isFull() {
  return (numberOfScripts() >= k_maxNumberOfScripts || m_accordion.freeSpaceSize() < k_fullFreeSpaceSizeLimit);
}

void ScriptStore::scanScriptsForFunctionsAndVariables(void * context, ScanCallback storeFunction, ScanCallback storeVariable) {
  for (int scriptIndex = 0; scriptIndex < numberOfScripts(); scriptIndex++) {
    // Handle lexer or parser errors with nlr.
    nlr_buf_t nlr;
    if (nlr_push(&nlr) == 0) {
      const char * scriptContent = scriptAtIndex(scriptIndex).content();
      if (scriptContent == nullptr) {
        continue;
      }
      mp_lexer_t *lex = mp_lexer_new_from_str_len(0, scriptContent, strlen(scriptContent), false);
      mp_parse_tree_t parseTree = mp_parse(lex, MP_PARSE_FILE_INPUT);
      mp_parse_node_t pn = parseTree.root;

      if (!MP_PARSE_NODE_IS_STRUCT(pn)) {
        mp_parse_tree_clear(&parseTree);
        nlr_pop();
        continue;
      }

      mp_parse_node_struct_t *pns = (mp_parse_node_struct_t*)pn;

      // The script is only a single function definition.
      if (((uint)(MP_PARSE_NODE_STRUCT_KIND(pns))) == k_functionDefinitionParseNodeStructKind) {
        const char * id = structID(pns);
        if (id == nullptr) {
          continue;
        }
        storeFunction(context, id, scriptIndex);
        mp_parse_tree_clear(&parseTree);
        nlr_pop();
        continue;
      }

      // The script is only a single global variable definition.
      if (((uint)(MP_PARSE_NODE_STRUCT_KIND(pns))) == k_expressionStatementParseNodeStructKind) {
        const char * id = structID(pns);
        if (id == nullptr) {
          continue;
        }
        storeVariable(context, id, scriptIndex);
        mp_parse_tree_clear(&parseTree);
        nlr_pop();
        continue;
      }

      if (((uint)(MP_PARSE_NODE_STRUCT_KIND(pns))) != k_fileInput2ParseNodeStructKind) {
        // The script node is not of type "file_input_2", thus it will not have main
        // structures of the wanted type.
        mp_parse_tree_clear(&parseTree);
        nlr_pop();
        continue;
      }

      // Count the number of structs in child nodes.

      size_t n = MP_PARSE_NODE_STRUCT_NUM_NODES(pns);
      for (size_t i = 0; i < n; i++) {
        mp_parse_node_t child = pns->nodes[i];
        if (MP_PARSE_NODE_IS_STRUCT(child)) {
          mp_parse_node_struct_t *child_pns = (mp_parse_node_struct_t*)(child);
          if (((uint)(MP_PARSE_NODE_STRUCT_KIND(child_pns))) == k_functionDefinitionParseNodeStructKind) {
            const char * id = structID(child_pns);
            if (id == nullptr) {
              continue;
            }
            storeFunction(context, id, scriptIndex);
          } else if (((uint)(MP_PARSE_NODE_STRUCT_KIND(child_pns))) == k_expressionStatementParseNodeStructKind) {
            const char * id = structID(child_pns);
            if (id == nullptr) {
              continue;
            }
            storeVariable(context, id, scriptIndex);
          }
        }
      }

      mp_parse_tree_clear(&parseTree);
      nlr_pop();
    }
  }
}

const char * ScriptStore::contentOfScript(const char * name) {
  Script script = scriptNamed(name);
  if (script.isNull()) {
    return nullptr;
  }
  return script.content();
}

bool ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) {
  const char autoImportationString[2] = {Script::DefaultAutoImportationMarker, 0};
  if (!m_accordion.appendBuffer(autoImportationString)) {
    return false;
  }

  if (!m_accordion.appendBuffer(scriptTemplate->name())) {
    // Delete the Auto Importation Marker
    m_accordion.deleteLastBuffer();
    return false;
  }

  if (copyStaticScriptOnFreeSpace(scriptTemplate)) {
    return true;
  }
  // Delete the Auto Importation Marker and the Name Of the Script
  m_accordion.deleteLastBuffer();
  m_accordion.deleteLastBuffer();
  return false;
}

bool ScriptStore::copyStaticScriptOnFreeSpace(const ScriptTemplate * scriptTemplate) {
  return m_accordion.appendBuffer(scriptTemplate->content());
}

int ScriptStore::accordionIndexOfMarkersOfScriptAtIndex(int index) const {
  return index * Script::NumberOfStringsPerScript;
}

int ScriptStore::accordionIndexOfNameOfScriptAtIndex(int index) const {
  return index * Script::NumberOfStringsPerScript + 1;
}

int ScriptStore::accordionIndexOfContentOfScriptAtIndex(int index) const {
  return index * Script::NumberOfStringsPerScript + 2;
}

const char * ScriptStore::structID(mp_parse_node_struct_t *structNode) {
  // Find the id child node, which stores the struct's name
  size_t childNodesCount = MP_PARSE_NODE_STRUCT_NUM_NODES(structNode);
  if (childNodesCount < 1) {
    return nullptr;
  }
  mp_parse_node_t child = structNode->nodes[0];
  if (MP_PARSE_NODE_IS_LEAF(child)
      && MP_PARSE_NODE_LEAF_KIND(child) == MP_PARSE_NODE_ID)
  {
    uintptr_t arg = MP_PARSE_NODE_LEAF_ARG(child);
    return qstr_str(arg);
  }
  return nullptr;
}

}