From b836674c67e5f9a3fe0a3697f87320dce7a84b44 Mon Sep 17 00:00:00 2001 From: Lukas Diekmann Date: Fri, 31 Aug 2018 17:03:07 +0100 Subject: [PATCH 1/2] Improve cross-language namebinding Previously to enable cross-language scoping, we converted scoping types from one language to another. For example, when embedding a Python method into PHP, we renamed it to `function` so PHP's scoping rules could reference it. This was restricitive as not every scope type has a true representation in the other language. For example, in Python `method` can be referenced by `variables`, which is not possible in PHP. Thus converting `method` to `function` would disallow `method` to be referenced by a PHP `variable`. This commit changes how we treat namebinding information from language boxes. When embedding Python into PHP, for example, Python's scoping rules are merged into PHP's as is, keeping their specific types. We then extend PHP's scoping rules, allowing them to reference Python types. For example, a PHP `FunctionCall` now references `function` (PHP) and `method` (Python). --- lib/eco/astanalyser.py | 47 ++++++++++++---------------------- lib/eco/eco.py | 7 ++--- lib/eco/grammars/grammars.py | 7 +++++ lib/eco/grammars/phppython.nb | Bin 0 -> 1737 bytes lib/eco/grammars/python275.nb | Bin 1316 -> 1668 bytes lib/eco/nodeeditor.py | 6 ++--- lib/eco/treemanager.py | 22 ++++++++++++---- 7 files changed, 48 insertions(+), 41 deletions(-) create mode 100644 lib/eco/grammars/phppython.nb diff --git a/lib/eco/astanalyser.py b/lib/eco/astanalyser.py index 840b29b0..9d35fa0f 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,23 @@ 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 + if len(prev.path) >= 1: + # 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): 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 0000000000000000000000000000000000000000..d88671b449aa6fa8ca430a3b8bc82c5bcd852594 GIT binary patch literal 1737 zcmV;)1~&O0iwFo4Xlz>o|8Qtqw2_(b=pw&uknywHuVH5sn z#jYwhxoIsNJ8L_z72@4DCZMGY6~(1?6aV-`-Cm6y-tDV8Z`tJ~&#(CVzt%s;i|8zz z6!~<0AzdZO`cfK-GR#gwo@aTnzC4eTBCM|`*)&QvmoHt--mI3NjZTxOsMhojMw8x$ z2VXqNq>S=1UnQZ&~{q zF8?!cXPf7tT;*xITb-4g?3|C@8kcdF7Il5yYOAfj*6QX?;b(bvmPd;nIfi#N`rd2h z{X9Jw@Asc<;~V<&=E{p@t+cjU*PI`oX0r=UD4lD_Dekwj*Ty>$P}+fK`lF|#;TnwX4<#BYH&}+j9 z9TlvwvL$N$=1z!ybxVg~eP?+j`z9$SDc({%^6O=s*IA`>Ryr%4wRVVfT069MXzc)s z5zlGu(Apte*4nZ2vL>6Amb&G}j@<#}pr}~w)~r-n4*{xnbSMv6y1t$MfdloZAN*tFlt6b6;VZ0 z5mj%8c&fKUZ-?Fv1O@$^-VVJTvSqy;9aKcwuN2kVJEW)5Q|T%Ef)(u@+B;MU+ol#F zDV7pB+E`(Qr1;y*UVb_u_KPAuOBW$6aknJJB*mn`oR<|sHbr-5g>0X@t@=^7zd+np z-`N=6Z2_2#y*=`Zw^6c^-O6ssiph$}ijfp_8)U^~#q!?W&wDZW_A6O2mPC3gJr#^t zAuGOigv-M`pO8iZQN`=Y_D^#f0T=p!=*9+Hw zM{YrUN_)9fH~=@^i(in#V`lM9AsaH zIT+@^J>&zd80H`{5TCNb3i0X91NY-uNXvL0Q=d|wQlCv!1rkYpOTD#VcF&WZeEn8Z)#PC*Iwl+6|0J6s&5vXArT)+pvWrdaPWrlxCvePJe86k!d z!-!$TFegL|BZd*f>LbK3V%SUzSgV;9+Q#fxGcDlh@N{@OE3ClNuU^)#Zs{fI8B86f zZqz^LWrc23-E7pqQU3xeaud0U+yqu3H<6pjO-V0u6CY#0A~!h`XHwt($m@*LR^I31 zA?N94=Cm1da}w7n6^hkq5&m9U-6u(LHQ!In-x|%h^Zf~(rIO}A0-Ji52OxK-@+cw zU7A(z2+_o;iBsL&Y7-}JQ#{3O;x=)cuqxc9Z!Y!Cr4nD?TzU(M_WTq4DA|^5124pI z;Kj{j>LQfq*$f$PlRG1*bf|O$1}yk$R>A2kPwj&K|YYftgwO{Rw^Th4f?qY zSl2SjqlHO7PAW(j5CGDpoE1;WbjWnbbbwW4I%GOzIuhS*%5=zWxCe5W738pLniW=9 z`B_0UWdDnwM{yb#=abFP4F-+DXwuyC_uieyWhj-;15}9bzJzKT-9Q^|XUFl6zgjm>*N)#i@80vyxqKMLqNs+4 zgAcKdDSj z$Ulo~kPq_v4*9CD?m*Uf8w+N^ENQAVRhrrh%#TTa3G-pTJXN0B8O#cALoCArY=JGX z1-3c@TiNCU#DZ9gYsIzVx)+F_mKDec`I1ygYDXZe3=j)qK`e;X6~tPkbC3nHKo-dA z3S<@MzXhNLw2*~+4FNmc!+WFc@ zl)KEFMYu@wv`UM-PA3MVjjl7gxuzfEGz%yBYAuD{Z5(>g%KLftczSgFw2N=(^P8)# zSGD@+Y{xaX63c2?l~$q5rlD2wu$8$s-3vrHY>8@kb~TGrP z{ov^;iOEv`1BePyAu2>==YXf|9PAwI98M)j=GZydIb_T191iTwR(=kC4#}zHRB|ec zDEu7!9Q+)78Hy4qktm{w(n%HZ=>00rs%3Rlmg!~g)Rvf-n7D(Y;7QNPGBwcN?uz}E zD45vYIRMPA$cR-7WW;chj95-h?!34*_;rA`bNA(pSx zTK=o`c^Q6RhrDXXO7-&bZ1ls+(c}!1h)KjGI%?{@BlZT(>kbV0yd}PMIvS6rqscMK zl{1zzR*h?Uh|(vvnqJDJ#a3gh^|;XELKIPSpU{0m_X*u6bf1uZAyPQ?IQ2|y-6t?l zx=-jn0T*|;`vf8%oH9N$J~KW$FCbUkG2=7)vXmDUK-^}&vM;kQt0M`*2*L=$?0yI- z_GOEQeYwZ>Wz-({7llao{wmlPN!$$Fd`=xppG%+HqRw`Ys#@CsQ36o{QG&{aD1j(} zC;?MPl;Bv#eC?wsK{Y5}#B_@yN-y~I2^J5Pj!H+RTZE`|R5~hMX@p8grE8dvpVTnF zq26#B^h?r3(bEX__o=7RGE`T^0-{1xhze05>fI6b=4Kqio8z_!>Ky7E6ts0KiYVPx z=bS88MXc1w^f~l7E$eeMPP5ez=n&`-=%|heba-8PT`{e^t_VQ$wU=zVb2bU1DpwRy zIz*?Bi}+)rb0j()ozCuR5n|J^>DY9|5jGv0j!nl-V$-qd<|{UxHXWIcOczBIWct^K z9mUJ_vaSE+4wHvYN2l*y3s=>MY6SgkVt0`i%GM4wW&92`9cnt%L{d4_)WcT~Uny+Y zdid(8Yk8n9M7gg+;pWeiRpO@Y%@=*p$}BkOdU;I8VcCfyiVY+bf6H6OX`Yr>vzy-= z1dY+@thwhOzPn1RP_B|KgK;>h;`-CTB)?p5f^R=Pn-{AzzZ^{0Stth&M)Tq#&BDXs O$3FlzhT)At$^igu_d}Qf literal 1316 zcmYktc{mda003}jX+nmj#fTZtv6zOXqD8hx`dI6wSBzfY9GTI`-gt6Uj?ZkaR138| zM`>#0T8?$hG)K;4$>b_`t`XJad*3_0-yc7NG!+#E?*W{`4>FY!7)Co&!6h+9tQp*(`LhUD_{*($)Gnh_!#&7qpVba0WvD;bVXeBTXG+-LgYb++f*!IxU>h1`3czWrw=F=KIg4@9-kz9H5# z?%hgx=Cx-Ld19eYQhpALtUwbBoHm}u3{8%iXBD&OfAk%>Sa4C#>Lrm_?33u3^EI-P zvVJe4thy99wlsXc#+@HR%ewM1r#q|sKT8NF4rR4EY)?ky&0k5Xg@` z9yb#Okd3W9bwrWvPy3Q)4u3L(hkW-3cjpP0s*1!OQnWSWDppsYNWm>i|N(KVA(;7vu!DNjE%z~(IocZ&?4zP1F)P)Q2FePie zSCmB)tcek+aZ-cTX2rxHn4b~^&2(kxodMyy;Tx4VBzd9tY00Atb~nOOz(`nBPP={5ew0_FWwV@W{q z&HWQ$nY^gf(V{84?zkVg~kRIUeH&Fmc4HK+$ zfi;(FF00HYn8w)`ZMzQ+13@+BtHg^%Ttb`wrX6)24he8o(I4$-*r)sHNB1h$CfRKZ z-8rDkQ?9fLyb#4N>^Vj4COHJJ3G;OzH;DL-I8D+M57O9PoMu z?v?AwTFp(hqEDy}v91fI>;?0gh&u?rdR;+m3vw=|+C_psS#1{IEHKpF{^AJUns?&2 zJFOGJdfM(xjc=~ZlgtctNQ-0U73du1gVzwVl-)2wHKMvL&^rvNZmMY@kd-C$e8|(s zB0N@y@z5J<{EI6WkfogU3XyV7r`9|IKl*?%5GpPA8*{5pZq@EV_8^JZ<0BLQg%id{ z+9DmQl>86rDm0-bqt6I{|Dy@Dqb16{#YlnKvXL`nTwo9qEUsE)6R3n~N%DD!6kvw9s`H-`{dk=`a1)?J$9a=o)%%LH z10Tk9%%uP+;1bbDahRfzPc%tY%SkZ!2alar(@uv+QEUoOjWuZB&ROldvIb+#(*4u* zQDsSML{~7zY7ieSqf#pqylVJR4lUNCo{Y^VGMn|oa7+n($*8S?w$7U?#d3Tp!cri2 X5t?_q_SDB9+1oIJKo-zwCNK9F65x1| diff --git a/lib/eco/nodeeditor.py b/lib/eco/nodeeditor.py index 117080f9..ba8d3ffb 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 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]: From ee40c06612ed29ac7146fe0c6ef638ad2787785b Mon Sep 17 00:00:00 2001 From: Lukas Diekmann Date: Tue, 4 Sep 2018 17:46:02 +0100 Subject: [PATCH 2/2] Adjust code completion according to namebinding changes --- lib/eco/astanalyser.py | 15 +++++++++------ lib/eco/nodeeditor.py | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/eco/astanalyser.py b/lib/eco/astanalyser.py index 9d35fa0f..09e9ce83 100644 --- a/lib/eco/astanalyser.py +++ b/lib/eco/astanalyser.py @@ -260,7 +260,15 @@ def merge_lbox_data(self, node, path): def convert_uri(self, newkind, prev, path): uri = prev - if len(prev.path) >= 1: + # 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): @@ -372,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) @@ -404,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/nodeeditor.py b/lib/eco/nodeeditor.py index ba8d3ffb..6d942211 100644 --- a/lib/eco/nodeeditor.py +++ b/lib/eco/nodeeditor.py @@ -1083,6 +1083,7 @@ def action(): def createCCFunc(self, text): def action(): self.tm.pasteCompletion(text) + self.update() return action def selectSubgrammar(self, item):