diff --git a/apps/code/python_toolbox.cpp b/apps/code/python_toolbox.cpp index 1313a7932..efc1241db 100644 --- a/apps/code/python_toolbox.cpp +++ b/apps/code/python_toolbox.cpp @@ -438,11 +438,13 @@ PythonToolbox::PythonToolbox() : } const ToolboxMessageTree * PythonToolbox::moduleChildren(const char * name, int * numberOfNodes) const { - assert(numberOfNodes != nullptr); for (ToolboxMessageTree t : modulesChildren) { if (strcmp(I18n::translate(t.label()), name) == 0) { - *numberOfNodes = t.numberOfChildren(); - assert(*numberOfNodes > 0); + const int childrenCount = t.numberOfChildren(); + if (numberOfNodes != nullptr) { + *numberOfNodes = childrenCount; + } + assert(childrenCount > 0); return static_cast(t.children(0)); } } diff --git a/apps/code/script.h b/apps/code/script.h index fc10352f3..37a204bd6 100644 --- a/apps/code/script.h +++ b/apps/code/script.h @@ -23,7 +23,7 @@ public: static bool DefaultName(char buffer[], size_t bufferSize); static bool nameCompliant(const char * name); - Script(Ion::Storage::Record r) : Record(r) {} + Script(Ion::Storage::Record r = Ion::Storage::Record()) : Record(r) {} bool importationStatus() const; void toggleImportationStatus(); const char * scriptContent() const; diff --git a/apps/code/variable_box_controller.cpp b/apps/code/variable_box_controller.cpp index 823c5f29e..a69fa535a 100644 --- a/apps/code/variable_box_controller.cpp +++ b/apps/code/variable_box_controller.cpp @@ -13,6 +13,7 @@ extern "C" { #include "py/lexer.h" #include "py/nlr.h" +#include "py/objmodule.h" } namespace Code { @@ -651,7 +652,7 @@ void VariableBoxController::loadGlobalAndImportedVariablesInScriptAsImported(con uint structKind = (uint)MP_PARSE_NODE_STRUCT_KIND(pns); if (structKind == PN_funcdef || structKind == PN_expr_stmt) { // The script is only a single function or variable definition - addImportStruct(pns, structKind, scriptName, textToAutocomplete, textToAutocompleteLength); + addImportStructFromScript(pns, structKind, scriptName, textToAutocomplete, textToAutocompleteLength); } else if (addNodesFromImportMaybe(pns, textToAutocomplete, textToAutocompleteLength)) { // The script is is only an import, handled in addNodesFromImportMaybe } else if (structKind == PN_file_input_2) { @@ -666,7 +667,7 @@ void VariableBoxController::loadGlobalAndImportedVariablesInScriptAsImported(con mp_parse_node_struct_t *child_pns = (mp_parse_node_struct_t*)(child); structKind = (uint)MP_PARSE_NODE_STRUCT_KIND(child_pns); if (structKind == PN_funcdef || structKind == PN_expr_stmt) { - addImportStruct(child_pns, structKind, scriptName, textToAutocomplete, textToAutocompleteLength); + addImportStructFromScript(child_pns, structKind, scriptName, textToAutocomplete, textToAutocompleteLength); } else { addNodesFromImportMaybe(child_pns, textToAutocomplete, textToAutocompleteLength); } @@ -701,9 +702,25 @@ bool VariableBoxController::addNodesFromImportMaybe(mp_parse_node_struct_t * par for (size_t i = 0; i < childNodesCount; i++) { mp_parse_node_t child = parseNode->nodes[i]; if (MP_PARSE_NODE_IS_LEAF(child) && MP_PARSE_NODE_LEAF_KIND(child) == MP_PARSE_NODE_ID) { - // Parsing something like "import math" + // Parsing something like "import xyz" const char * id = qstr_str(MP_PARSE_NODE_LEAF_ARG(child)); - checkAndAddNode(textToAutocomplete, textToAutocompleteLength, ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, id, -1, "math", "desc" /*TODO LEA*/); + + /* xyz might be: + * - a module name -> in which case we want no importation source on the + * node. The node will not be added if it is already in the builtins. + * - a script name -> we want to have xyz.py as the importation source + * - a non-existing identifier -> we want no source */ + const char * sourceId = nullptr; + if (!importationSourceIsModule(id)) { + /* If a module and a script have the same name, the micropython + * importation algorithm first looks for a module then for a script. We + * should thus check that the id is not a module name before retreiving + * a script name to put it as source. + * TODO Should importationSourceIsModule be called in + * importationSourceIsScript?*/ + importationSourceIsScript(id, &sourceId); + } + checkAndAddNode(textToAutocomplete, textToAutocompleteLength, ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, id, -1, sourceId); } else if (MP_PARSE_NODE_IS_STRUCT(child)) { // Parsing something like "from math import sin" addNodesFromImportMaybe((mp_parse_node_struct_t *)child, textToAutocomplete, textToAutocompleteLength); @@ -717,68 +734,97 @@ bool VariableBoxController::addNodesFromImportMaybe(mp_parse_node_struct_t * par // Fetch a script / module content if needed if (loadAllContent) { assert(childNodesCount > 0); - mp_parse_node_t importationSource = parseNode->nodes[0]; - const char * importationSourceName = nullptr; - if (MP_PARSE_NODE_IS_LEAF(importationSource) - && MP_PARSE_NODE_LEAF_KIND(importationSource) == MP_PARSE_NODE_ID) - { - // The importation source is "simple", for instance: from math import * - importationSourceName = qstr_str(MP_PARSE_NODE_LEAF_ARG(importationSource)); - } else if (MP_PARSE_NODE_IS_STRUCT(importationSource)) { - mp_parse_node_struct_t * importationSourcePNS = (mp_parse_node_struct_t *)importationSource; - uint importationSourceStructKind = MP_PARSE_NODE_STRUCT_KIND(importationSourcePNS); - if (importationSourceStructKind == PN_dotted_name) { - /* The importation source is "complex", for instance: - * from matplotlib.pyplot import * - * FIXME The solution would be to build a single qstr for this name, - * such as in python/src/compile.c, function do_import_name, from line - * 1117 (found by searching PN_dotted_name). - * We might do this later, for now the only dotted name we might want to - * find is matplolib.pyplot, so we do a very specific search. */ - int numberOfSplitNames = MP_PARSE_NODE_STRUCT_NUM_NODES(importationSourcePNS); - if (numberOfSplitNames != 2) { - return true; + const char * importationSourceName = importationSourceNameFromNode(parseNode->nodes[0]); + int numberOfModuleChildren = 0; + const ToolboxMessageTree * moduleChildren = nullptr; + if (importationSourceIsModule(importationSourceName, &moduleChildren, &numberOfModuleChildren)) { + if (moduleChildren != nullptr) { + /* The importation source is a module that we display in the toolbox: + * get the nodes from the toolbox + * We skip the 3 first nodes, which are "import ...", "from ... import *" + * and "....function". */ + constexpr int numberOfNodesToSkip = 3; + assert(numberOfModuleChildren > numberOfNodesToSkip); + for (int i = numberOfNodesToSkip; i < numberOfModuleChildren; i++) { + const char * name = I18n::translate((moduleChildren + i)->label()); + checkAndAddNode(textToAutocomplete, textToAutocompleteLength, ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, name, -1, importationSourceName, I18n::translate((moduleChildren + i)->text()) /*TODO LEA text or label?*/); } - const char * importationSourceSubName = qstr_str(MP_PARSE_NODE_LEAF_ARG(importationSourcePNS->nodes[0])); - if (strcmp(importationSourceSubName, "matplotlib") != 0) { //TODO LEA once rebased - - return true; - } - importationSourceSubName = qstr_str(MP_PARSE_NODE_LEAF_ARG(importationSourcePNS->nodes[1])); - if (strcmp(importationSourceSubName, "pyplot") != 0) { //TODO LEA once rebased - - return true; - } - importationSourceName = "matplotlib.pyplot"; //TODO LEA once rebased } else { - assert(false); //TODO LEA can we indeed assert? - } - } - int numberOfChildren = 0; - const ToolboxMessageTree * moduleChildren = static_cast(App::app()->toolboxForInputEventHandler(nullptr))->moduleChildren(importationSourceName, &numberOfChildren); - if (moduleChildren != nullptr) { - /* If the importation source is a module, get the nodes from the toolbox - * We skip the 3 first nodes, which are "import ...", "from ... import *" - * and "....function". */ - constexpr int numberOfNodesToSkip = 3; - assert(numberOfChildren > numberOfNodesToSkip); - for (int i = numberOfNodesToSkip; i < numberOfChildren; i++) { - const char * name = I18n::translate((moduleChildren + i)->label()); - checkAndAddNode(textToAutocomplete, textToAutocompleteLength, ScriptNode::Type::WithoutParentheses, NodeOrigin::Importation, name, -1, importationSourceName, I18n::translate((moduleChildren + i)->text()) /*TODO LEA text or label?*/); + //TODO LEA get module variables } } else { // Try fetching the nodes from a script - Script importedScript = ScriptStore::ScriptBaseNamed(importationSourceName); - if (!importedScript.isNull()) { + Script importedScript; + const char * scriptFullName; + if (importationSourceIsScript(importationSourceName, &scriptFullName, &importedScript)) { const char * scriptContent = importedScript.scriptContent(); assert(scriptContent != nullptr); - loadGlobalAndImportedVariablesInScriptAsImported(importationSourceName, scriptContent, textToAutocomplete, textToAutocompleteLength); + loadGlobalAndImportedVariablesInScriptAsImported(scriptFullName, scriptContent, textToAutocomplete, textToAutocompleteLength); } } } return true; } +const char * VariableBoxController::importationSourceNameFromNode(mp_parse_node_t & node) { + if (MP_PARSE_NODE_IS_LEAF(node) && MP_PARSE_NODE_LEAF_KIND(node) == MP_PARSE_NODE_ID) { + // The importation source is "simple", for instance: from math import * + return qstr_str(MP_PARSE_NODE_LEAF_ARG(node)); + } + if (MP_PARSE_NODE_IS_STRUCT(node)) { + mp_parse_node_struct_t * nodePNS = (mp_parse_node_struct_t *)node; + uint nodeStructKind = MP_PARSE_NODE_STRUCT_KIND(nodePNS); + if (nodeStructKind != PN_dotted_name) { + return nullptr; + } + /* The importation source is "complex", for instance: + * from matplotlib.pyplot import * + * TODO LEA + * FIXME The solution would be to build a single qstr for this name, + * such as in python/src/compile.c, function do_import_name, from line + * 1117 (found by searching PN_dotted_name). + * We might do this later, for now the only dotted name we might want to + * find is matplolib.pyplot, so we do a very specific search. */ + int numberOfSplitNames = MP_PARSE_NODE_STRUCT_NUM_NODES(nodePNS); + if (numberOfSplitNames != 2) { + return nullptr; + } + const char * nodeSubName = qstr_str(MP_PARSE_NODE_LEAF_ARG(nodePNS->nodes[0])); + if (strcmp(nodeSubName, qstr_str(MP_QSTR_matplotlib)) == 0) { + nodeSubName = qstr_str(MP_PARSE_NODE_LEAF_ARG(nodePNS->nodes[1])); + if (strcmp(nodeSubName, qstr_str(MP_QSTR_pyplot)) == 0) { + qstr_str(MP_QSTR_matplotlib_dot_pyplot); + } + } + } + return nullptr; +} + +bool VariableBoxController::importationSourceIsModule(const char * sourceName, const ToolboxMessageTree * * moduleChildren, int * numberOfModuleChildren) { + const ToolboxMessageTree * children = static_cast(App::app()->toolboxForInputEventHandler(nullptr))->moduleChildren(sourceName, numberOfModuleChildren); + if (moduleChildren != nullptr) { + *moduleChildren = children; + } + if (children != nullptr) { + return true; + } + // The sourceName might be a module that is not in the toolbox + return mp_module_get(qstr_from_str(sourceName)) != MP_OBJ_NULL; +} + +bool VariableBoxController::importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript) { + // Try fetching the nodes from a script + Script importedScript = ScriptStore::ScriptBaseNamed(sourceName); + if (importedScript.isNull()) { + return false; + } + *scriptFullName = importedScript.fullName(); + if (retreivedScript != nullptr) { + *retreivedScript = importedScript; + } + return true; +} + const char * structName(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); @@ -795,7 +841,7 @@ const char * structName(mp_parse_node_struct_t * structNode) { return nullptr; } -void VariableBoxController::addImportStruct(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength) { +void VariableBoxController::addImportStructFromScript(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength) { assert(structKind == PN_funcdef || structKind == PN_expr_stmt); // Find the id child node, which stores the struct's name const char * name = structName(pns); diff --git a/apps/code/variable_box_controller.h b/apps/code/variable_box_controller.h index 22ea76937..7e21eff8d 100644 --- a/apps/code/variable_box_controller.h +++ b/apps/code/variable_box_controller.h @@ -3,6 +3,7 @@ #include #include +#include #include "script_node.h" #include "script_node_cell.h" #include "script_store.h" @@ -91,7 +92,10 @@ private: void loadGlobalAndImportedVariablesInScriptAsImported(const char * scriptName, const char * scriptContent, const char * textToAutocomplete, int textToAutocompleteLength); // Returns true if this was an import structure bool addNodesFromImportMaybe(mp_parse_node_struct_t * parseNode, const char * textToAutocomplete, int textToAutocompleteLength); - void addImportStruct(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength); + const char * importationSourceNameFromNode(mp_parse_node_t & node); + bool importationSourceIsModule(const char * sourceName, const ToolboxMessageTree * * moduleChildren = nullptr, int * numberOfModuleChildren = nullptr); + bool importationSourceIsScript(const char * sourceName, const char * * scriptFullName, Script * retreivedScript = nullptr); + void addImportStructFromScript(mp_parse_node_struct_t * pns, uint structKind, const char * scriptName, const char * textToAutocomplete, int textToAutocompleteLength); /* Add a node if it completes the text to autocomplete and if it is not * already contained in the variable box. */ void checkAndAddNode(const char * textToAutocomplete, int textToAutocompleteLength, ScriptNode::Type type, NodeOrigin origin, const char * name, int nameLength, const char * nodeSourceName = nullptr, const char * description = nullptr);