diff --git a/lib/eco/astanalyser.py b/lib/eco/astanalyser.py index 840b29b0..09e9ce83 100644 --- a/lib/eco/astanalyser.py +++ b/lib/eco/astanalyser.py @@ -239,25 +239,12 @@ def merge_lbox_data(self, node, path): analyser = self.get_lboxanalyser(root) analyser.analyse(root, self.parsers) - # convert variables, functions - if node.symbol.name == "": - # merge python into php - original = "variable" - dest = "function" - max_path = 1 - elif node.symbol.name == "": - # merge php into python - original = "function" - dest = "variable" - max_path = 1 - else: - return - for method in analyser.data.get(original,[]): - if len(method.path) > max_path: + for kind in analyser.data: + if kind in ["File", "reference"]: continue - uri = self.convert_uri(dest, method, path) - uri.nbrule = NBRule("none", [], {}) - self.add_uri(uri) + for obj in analyser.data[kind]: + uri = self.convert_uri(kind, obj, path) + self.add_uri(uri) # convert references for ref in analyser.data.get("reference",[]): @@ -265,23 +252,31 @@ def merge_lbox_data(self, node, path): if ref.node in analyser.errors: del analyser.errors[ref.node] uri = self.convert_uri("reference", ref, path) - if uri.name.startswith("$"): + if uri.name.startswith("$"): # XXX PHP hack uri.name = uri.name[1:] - else: - uri.name = "$" + uri.name - uri.nbrule = NBRule("none", [], {"references":(["variable"], ["function"])}) + uri.nbrule = NBRule("none", [], {"references":(["variable", "function"], ["name"])}) self.add_uri(uri) return def convert_uri(self, newkind, prev, path): - uri = URI() - uri.kind = newkind - uri.astnode = prev.astnode - uri.node = prev.node - uri.path = path - uri.name = prev.name - uri.vartype = prev.vartype - uri.index = self.index + uri = prev + # There is currently no easy way to find out if a namebinding rule + # belongs to the top-level grammar rule. However, the top-level + # namebinding rule typically doesn't have a name as there is no need for + # it to have one. So we can use this to determine which rule is the + # top-level and then remove it when merging a sublanguage's rules into + # the outer language. However, if a user decides to give the top-level + # namebinding rule a name, cross-language namebinding won't work, and + # we currently can't guard against that. + if len(prev.path) > 0 and prev.path[0].name is None: + # Replace language box's top level with current location + uri.path.pop(0) + for p in reversed(path): + uri.path.insert(0, p) + else: + # Language box has no top-level (subgrammar) + for p in reversed(path): + uri.path.insert(0, p) return uri def add_uri(self, uri): @@ -385,8 +380,6 @@ def get_completion(self, scope): lbox = root.get_magicterminal() analyser = self.get_lboxanalyser(root) lboxresults = [] - if analyser and analyser is not self: - lboxresults = analyser.get_completion(scope) if analyser is self: lbox = None astnode = self.get_correct_astnode(scope, analyser, lbox) @@ -417,9 +410,6 @@ def get_correct_astnode(self, scope, analyser=None, lbox=None): # astnode must have a corresponding entry in self.data if nbrule: deftype = nbrule.get_deftype() - if lbox and lbox.symbol.name == "": - if deftype == "variable": # convert to find it in data - deftype = "function" if self.data.has_key(deftype): for e in self.data[deftype]: if e.astnode is astnode: diff --git a/lib/eco/eco.py b/lib/eco/eco.py index 9198c00f..135d7484 100644 --- a/lib/eco/eco.py +++ b/lib/eco/eco.py @@ -1385,9 +1385,10 @@ def btReparse(self, selected_node=[]): def updateASTOutline(self): self.ui.tw_astoutline.clear() aa = self.getEditor().tm.parsers[0][3] - for uri in aa.data["class"]: - if uri.path[0].name is None and len(uri.path) == 1: - self.addToASTOutline(aa, uri, self.ui.tw_astoutline) + if aa.data.has_key("class"): + for uri in aa.data["class"]: + if not uri.path or (uri.path and uri.path[0].name is None and len(uri.path) == 1): + self.addToASTOutline(aa, uri, self.ui.tw_astoutline) self.ui.tw_astoutline.expandAll() def addToASTOutline(self, aa, uri, parent): diff --git a/lib/eco/grammars/grammars.py b/lib/eco/grammars/grammars.py index 6f81616e..ca5ede9e 100644 --- a/lib/eco/grammars/grammars.py +++ b/lib/eco/grammars/grammars.py @@ -19,6 +19,8 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. +import os + class Language(object): def __init__(self, name, grammar, priorities, base=""): @@ -51,6 +53,7 @@ def __init__(self, name, filename, base=""): self.extract = None self.auto_include = None self.auto_exclude = None + self.nb_file = os.path.splitext(filename)[0] + ".nb" def load(self, buildlexer=True): from grammar_parser.bootstrap import BootstrapParser @@ -118,6 +121,9 @@ def set_auto_exclude(self, lang, tokentype): self.auto_exclude = {} self.auto_exclude[lang] = tokentype + def set_custom_nb(self, basename): + self.nb_file = "{}/{}".format(os.path.dirname(self.filename), basename) + def auto_allows(self, lang, tokentype): if self.auto_include and self.auto_include.has_key(lang): return tokentype in self.auto_include[lang] @@ -194,6 +200,7 @@ def __hash__(self): python_class.change_start("classdef") phppython = EcoFile("PHP + Python", "grammars/php.eco", "Php") +phppython.set_custom_nb("phppython.nb") pythonphp = EcoFile("Python + PHP", "grammars/python275.eco", "Python") phppython.add_alternative("top_statement", pythonphp) phppython.add_alternative("class_statement", pythonphp) diff --git a/lib/eco/grammars/phppython.nb b/lib/eco/grammars/phppython.nb new file mode 100644 index 00000000..d88671b4 Binary files /dev/null and b/lib/eco/grammars/phppython.nb differ diff --git a/lib/eco/grammars/python275.nb b/lib/eco/grammars/python275.nb index b936039c..2efff658 100644 Binary files a/lib/eco/grammars/python275.nb and b/lib/eco/grammars/python275.nb differ diff --git a/lib/eco/nodeeditor.py b/lib/eco/nodeeditor.py index 117080f9..6d942211 100644 --- a/lib/eco/nodeeditor.py +++ b/lib/eco/nodeeditor.py @@ -146,9 +146,9 @@ def resizeEvent(self, event): def analysis_timer(self): if self.getWindow().show_namebinding(): - self.tm.analyse() - self.update() - self.getWindow().updateASTOutline() + if self.tm.analyse() is not False: + self.update() + self.getWindow().updateASTOutline() self.timer.stop() # save swap @@ -1083,6 +1083,7 @@ def action(): def createCCFunc(self, text): def action(): self.tm.pasteCompletion(text) + self.update() return action def selectSubgrammar(self, item): diff --git a/lib/eco/treemanager.py b/lib/eco/treemanager.py index 97f06e5d..6c93ab85 100644 --- a/lib/eco/treemanager.py +++ b/lib/eco/treemanager.py @@ -544,10 +544,12 @@ def load_analyser(self, language): return if isinstance(lang, EcoFile): import os - filename = os.path.splitext(lang.filename)[0] + ".nb" - if os.path.exists(filename): + if os.path.exists(lang.nb_file): from astanalyser import AstAnalyser - return AstAnalyser(filename) + return AstAnalyser(lang.nb_file) + else: + print("Namebinding file '%s' not found." % (lang.nb_file)) + def get_languagebox(self, node): root = node.get_root() @@ -604,10 +606,20 @@ def get_error_presentation(self, node): return None def analyse(self): - if self.parsers[0][2] == "PHP + Python": - self.parsers[0][3].analyse(self.parsers[0][0].previous_version.parent, self.parsers) + # for now only do cross-scope analysing for certain grammars + crossscope = ["PHP + Python", "Java + Python"] + lang = self.parsers[0][2] + parser = self.parsers[0][0] + analyser = self.parsers[0][3] + + if not analyser: + return False + + if lang in crossscope: + analyser.analyse(parser.previous_version.parent, self.parsers) return + # analyse all parsers individually for p in self.parsers: if p[0].last_status: if p[3]: