mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Add lookup expressions
This commit is contained in:
parent
c094743e84
commit
4fefa0bd73
8 changed files with 177 additions and 5 deletions
|
@ -33,7 +33,10 @@ class Children:
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self._children)
|
return iter(self._children)
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return [child for child in self._children if isinstance(child, key)]
|
if isinstance(key, int):
|
||||||
|
return self._children[key]
|
||||||
|
else:
|
||||||
|
return [child for child in self._children if isinstance(child, key)]
|
||||||
|
|
||||||
|
|
||||||
class AstNode:
|
class AstNode:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
templates. """
|
templates. """
|
||||||
|
|
||||||
from .attributes import BaseAttribute, BaseTypedAttribute
|
from .attributes import BaseAttribute, BaseTypedAttribute
|
||||||
|
from .expression import Expr
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .gobject_property import Property
|
from .gobject_property import Property
|
||||||
from .gobject_signal import Signal
|
from .gobject_signal import Signal
|
||||||
|
|
63
blueprintcompiler/language/expression.py
Normal file
63
blueprintcompiler/language/expression.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# expressions.py
|
||||||
|
#
|
||||||
|
# Copyright 2022 James Westman <james@jwestman.net>
|
||||||
|
#
|
||||||
|
# This file is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU Lesser General Public License as
|
||||||
|
# published by the Free Software Foundation; either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This file is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
# Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
from .common import *
|
||||||
|
|
||||||
|
|
||||||
|
expr = Pratt()
|
||||||
|
|
||||||
|
|
||||||
|
class Expr(AstNode):
|
||||||
|
grammar = expr
|
||||||
|
|
||||||
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
self.children[-1].emit_xml(xml)
|
||||||
|
|
||||||
|
|
||||||
|
class InfixExpr(AstNode):
|
||||||
|
@property
|
||||||
|
def lhs(self):
|
||||||
|
children = list(self.parent_by_type(Expr).children)
|
||||||
|
return children[children.index(self) - 1]
|
||||||
|
|
||||||
|
|
||||||
|
class IdentExpr(AstNode):
|
||||||
|
grammar = UseIdent("ident")
|
||||||
|
|
||||||
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
xml.start_tag("constant")
|
||||||
|
xml.put_text(self.tokens["ident"])
|
||||||
|
xml.end_tag()
|
||||||
|
|
||||||
|
|
||||||
|
class LookupOp(InfixExpr):
|
||||||
|
grammar = [".", UseIdent("property")]
|
||||||
|
|
||||||
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
|
xml.start_tag("lookup", name=self.tokens["property"])
|
||||||
|
self.lhs.emit_xml(xml)
|
||||||
|
xml.end_tag()
|
||||||
|
|
||||||
|
|
||||||
|
expr.children = [
|
||||||
|
Prefix(IdentExpr),
|
||||||
|
Prefix(["(", Expr, ")"]),
|
||||||
|
Infix(10, LookupOp),
|
||||||
|
]
|
|
@ -18,6 +18,7 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
from .expression import Expr
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
from .values import Value, TranslatedStringValue
|
from .values import Value, TranslatedStringValue
|
||||||
|
@ -26,19 +27,27 @@ from .common import *
|
||||||
|
|
||||||
class Property(AstNode):
|
class Property(AstNode):
|
||||||
grammar = AnyOf(
|
grammar = AnyOf(
|
||||||
Statement(
|
[
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
":",
|
":",
|
||||||
Keyword("bind"),
|
Keyword("bind"),
|
||||||
UseIdent("bind_source").expected("the ID of a source object to bind from"),
|
UseIdent("bind_source"),
|
||||||
".",
|
".",
|
||||||
UseIdent("bind_property").expected("a property name to bind from"),
|
UseIdent("bind_property"),
|
||||||
ZeroOrMore(AnyOf(
|
ZeroOrMore(AnyOf(
|
||||||
["no-sync-create", UseLiteral("no_sync_create", True)],
|
["no-sync-create", UseLiteral("no_sync_create", True)],
|
||||||
["inverted", UseLiteral("inverted", True)],
|
["inverted", UseLiteral("inverted", True)],
|
||||||
["bidirectional", UseLiteral("bidirectional", True)],
|
["bidirectional", UseLiteral("bidirectional", True)],
|
||||||
Match("sync-create").warn("sync-create is deprecated in favor of no-sync-create"),
|
Match("sync-create").warn("sync-create is deprecated in favor of no-sync-create"),
|
||||||
)),
|
)),
|
||||||
|
";",
|
||||||
|
],
|
||||||
|
Statement(
|
||||||
|
UseIdent("name"),
|
||||||
|
UseLiteral("binding", True),
|
||||||
|
":",
|
||||||
|
"bind",
|
||||||
|
Expr,
|
||||||
),
|
),
|
||||||
Statement(
|
Statement(
|
||||||
UseIdent("name"),
|
UseIdent("name"),
|
||||||
|
@ -154,7 +163,13 @@ class Property(AstNode):
|
||||||
self.children[Object][0].emit_xml(xml)
|
self.children[Object][0].emit_xml(xml)
|
||||||
xml.end_tag()
|
xml.end_tag()
|
||||||
elif value is None:
|
elif value is None:
|
||||||
xml.put_self_closing("property", **props)
|
if self.tokens["binding"]:
|
||||||
|
xml.start_tag("binding", **props)
|
||||||
|
for x in self.children:
|
||||||
|
x.emit_xml(xml)
|
||||||
|
xml.end_tag()
|
||||||
|
else:
|
||||||
|
xml.put_self_closing("property", **props);
|
||||||
else:
|
else:
|
||||||
xml.start_tag("property", **props)
|
xml.start_tag("property", **props)
|
||||||
value.emit_xml(xml)
|
value.emit_xml(xml)
|
||||||
|
|
|
@ -98,6 +98,7 @@ class ParseContext:
|
||||||
def __init__(self, tokens, index=0):
|
def __init__(self, tokens, index=0):
|
||||||
self.tokens = list(tokens)
|
self.tokens = list(tokens)
|
||||||
|
|
||||||
|
self.binding_power = 0
|
||||||
self.index = index
|
self.index = index
|
||||||
self.start = index
|
self.start = index
|
||||||
self.group = None
|
self.group = None
|
||||||
|
@ -118,6 +119,7 @@ class ParseContext:
|
||||||
ctx = ParseContext(self.tokens, self.index)
|
ctx = ParseContext(self.tokens, self.index)
|
||||||
ctx.errors = self.errors
|
ctx.errors = self.errors
|
||||||
ctx.warnings = self.warnings
|
ctx.warnings = self.warnings
|
||||||
|
ctx.binding_power = self.binding_power
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def apply_child(self, other):
|
def apply_child(self, other):
|
||||||
|
@ -544,6 +546,64 @@ class Keyword(ParseNode):
|
||||||
return str(token) == self.kw
|
return str(token) == self.kw
|
||||||
|
|
||||||
|
|
||||||
|
class Prefix(ParseNode):
|
||||||
|
def __init__(self, child):
|
||||||
|
self.child = to_parse_node(child)
|
||||||
|
|
||||||
|
def _parse(self, ctx: ParseContext):
|
||||||
|
return self.child.parse(ctx).succeeded()
|
||||||
|
|
||||||
|
|
||||||
|
class Infix(ParseNode):
|
||||||
|
def __init__(self, binding_power: int, child):
|
||||||
|
self.binding_power = binding_power
|
||||||
|
self.child = to_parse_node(child)
|
||||||
|
|
||||||
|
def _parse(self, ctx: ParseContext):
|
||||||
|
ctx.binding_power = self.binding_power
|
||||||
|
return self.child.parse(ctx).succeeded()
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.binding_power < other.binding_power
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.binding_power == other.binding_power
|
||||||
|
|
||||||
|
|
||||||
|
class Pratt(ParseNode):
|
||||||
|
""" Basic Pratt parser implementation. """
|
||||||
|
|
||||||
|
def __init__(self, *children):
|
||||||
|
self.children = children
|
||||||
|
|
||||||
|
@property
|
||||||
|
def children(self):
|
||||||
|
return self._children
|
||||||
|
@children.setter
|
||||||
|
def children(self, children):
|
||||||
|
self._children = children
|
||||||
|
self.prefixes = [child for child in children if isinstance(child, Prefix)]
|
||||||
|
self.infixes = sorted([child for child in children if isinstance(child, Infix)], reverse=True)
|
||||||
|
|
||||||
|
def _parse(self, ctx: ParseContext) -> bool:
|
||||||
|
for prefix in self.prefixes:
|
||||||
|
if prefix.parse(ctx).succeeded():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# none of the prefixes could be parsed
|
||||||
|
return False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
succeeded = False
|
||||||
|
for infix in self.infixes:
|
||||||
|
if infix.binding_power <= ctx.binding_power:
|
||||||
|
break
|
||||||
|
if infix.parse(ctx).succeeded():
|
||||||
|
succeeded = True
|
||||||
|
break
|
||||||
|
if not succeeded:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def to_parse_node(value) -> ParseNode:
|
def to_parse_node(value) -> ParseNode:
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return Match(value)
|
return Match(value)
|
||||||
|
|
9
tests/samples/expr_lookup.blp
Normal file
9
tests/samples/expr_lookup.blp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
Overlay {
|
||||||
|
Label label {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
label: bind (label.parent).child.label;
|
||||||
|
}
|
20
tests/samples/expr_lookup.ui
Normal file
20
tests/samples/expr_lookup.ui
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<object class="GtkOverlay">
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label"></object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<binding name="label">
|
||||||
|
<lookup name="label">
|
||||||
|
<lookup name="child">
|
||||||
|
<lookup name="parent">
|
||||||
|
<constant>label</constant>
|
||||||
|
</lookup>
|
||||||
|
</lookup>
|
||||||
|
</lookup>
|
||||||
|
</binding>
|
||||||
|
</object>
|
||||||
|
</interface>
|
|
@ -136,6 +136,7 @@ class TestSamples(unittest.TestCase):
|
||||||
self.assert_sample("combo_box_text")
|
self.assert_sample("combo_box_text")
|
||||||
self.assert_sample("comments")
|
self.assert_sample("comments")
|
||||||
self.assert_sample("enum")
|
self.assert_sample("enum")
|
||||||
|
self.assert_sample("expr_lookup")
|
||||||
self.assert_sample("file_filter")
|
self.assert_sample("file_filter")
|
||||||
self.assert_sample("flags")
|
self.assert_sample("flags")
|
||||||
self.assert_sample("id_prop")
|
self.assert_sample("id_prop")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue