Change the way values work

Change the parsing for values to make them more reusable, in particular
for when I implement extensions.
This commit is contained in:
James Westman 2023-01-12 13:19:15 -06:00
parent 6938267952
commit 1df46b5a06
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
30 changed files with 707 additions and 291 deletions

View file

@ -18,7 +18,7 @@ build:
- ninja -C _build docs/en - ninja -C _build docs/en
- git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git - git clone https://gitlab.gnome.org/jwestman/blueprint-regression-tests.git
- cd blueprint-regression-tests - cd blueprint-regression-tests
- git checkout 58fda9381dac4a9c42c18a4b06149ed59ee702dc - git checkout 59eecfbd73020889410da6cc9f5ce90e5b6f9e24
- ./test.sh - ./test.sh
- cd .. - cd ..
coverage: '/TOTAL.*\s([.\d]+)%/' coverage: '/TOTAL.*\s([.\d]+)%/'

View file

@ -24,6 +24,8 @@ import typing as T
from .errors import * from .errors import *
from .lsp_utils import SemanticToken from .lsp_utils import SemanticToken
TType = T.TypeVar("TType")
class Children: class Children:
"""Allows accessing children by type using array syntax.""" """Allows accessing children by type using array syntax."""
@ -34,6 +36,14 @@ class Children:
def __iter__(self): def __iter__(self):
return iter(self._children) return iter(self._children)
@T.overload
def __getitem__(self, key: T.Type[TType]) -> T.List[TType]:
...
@T.overload
def __getitem__(self, key: int) -> "AstNode":
...
def __getitem__(self, key): def __getitem__(self, key):
if isinstance(key, int): if isinstance(key, int):
return self._children[key] return self._children[key]
@ -41,6 +51,27 @@ class Children:
return [child for child in self._children if isinstance(child, key)] return [child for child in self._children if isinstance(child, key)]
TCtx = T.TypeVar("TCtx")
TAttr = T.TypeVar("TAttr")
class Ctx:
"""Allows accessing values from higher in the syntax tree."""
def __init__(self, node: "AstNode") -> None:
self.node = node
def __getitem__(self, key: T.Type[TCtx]) -> T.Optional[TCtx]:
attrs = self.node._attrs_by_type(Context)
for name, attr in attrs:
if attr.type == key:
return getattr(self.node, name)
if self.node.parent is not None:
return self.node.parent.context[key]
else:
return None
class AstNode: class AstNode:
"""Base class for nodes in the abstract syntax tree.""" """Base class for nodes in the abstract syntax tree."""
@ -62,6 +93,10 @@ class AstNode:
getattr(cls, f) for f in dir(cls) if hasattr(getattr(cls, f), "_validator") getattr(cls, f) for f in dir(cls) if hasattr(getattr(cls, f), "_validator")
] ]
@cached_property
def context(self):
return Ctx(self)
@property @property
def root(self): def root(self):
if self.parent is None: if self.parent is None:
@ -105,7 +140,9 @@ class AstNode:
for child in self.children: for child in self.children:
yield from child._get_errors() yield from child._get_errors()
def _attrs_by_type(self, attr_type): def _attrs_by_type(
self, attr_type: T.Type[TAttr]
) -> T.Iterator[T.Tuple[str, TAttr]]:
for name in dir(type(self)): for name in dir(type(self)):
item = getattr(type(self), name) item = getattr(type(self), name)
if isinstance(item, attr_type): if isinstance(item, attr_type):
@ -217,3 +254,23 @@ def docs(*args, **kwargs):
return Docs(func, *args, **kwargs) return Docs(func, *args, **kwargs)
return decorator return decorator
class Context:
def __init__(self, type: T.Type[TCtx], func: T.Callable[[AstNode], TCtx]) -> None:
self.type = type
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
return self.func(instance)
def context(type: T.Type[TCtx]):
"""Decorator for functions that return a context object, which is passed down to ."""
def decorator(func: T.Callable[[AstNode], TCtx]) -> Context:
return Context(type, func)
return decorator

View file

@ -295,7 +295,7 @@ def decompile_property(
flags += " inverted" flags += " inverted"
if "bidirectional" in bind_flags: if "bidirectional" in bind_flags:
flags += " bidirectional" flags += " bidirectional"
ctx.print(f"{name}: bind {bind_source}.{bind_property}{flags};") ctx.print(f"{name}: bind-property {bind_source}.{bind_property}{flags};")
elif truthy(translatable): elif truthy(translatable):
if context is not None: if context is not None:
ctx.print( ctx.print(

View file

@ -1,4 +1,6 @@
from .attributes import BaseAttribute, BaseTypedAttribute from .attributes import BaseAttribute, BaseTypedAttribute
from .binding import Binding
from .contexts import ValueTypeCtx
from .expression import CastExpr, ClosureExpr, Expr, ExprChain, IdentExpr, LookupOp from .expression import CastExpr, ClosureExpr, Expr, ExprChain, IdentExpr, LookupOp
from .gobject_object import Object, ObjectContent from .gobject_object import Object, ObjectContent
from .gobject_property import Property from .gobject_property import Property
@ -14,16 +16,21 @@ from .gtk_styles import Styles
from .gtkbuilder_child import Child from .gtkbuilder_child import Child
from .gtkbuilder_template import Template from .gtkbuilder_template import Template
from .imports import GtkDirective, Import from .imports import GtkDirective, Import
from .property_binding import PropertyBinding
from .ui import UI from .ui import UI
from .types import ClassName from .types import ClassName
from .values import ( from .values import (
TypeValue,
IdentValue,
TranslatedStringValue,
FlagsValue,
Flag, Flag,
QuotedValue, Flags,
NumberValue, IdentLiteral,
Literal,
NumberLiteral,
ObjectValue,
QuotedLiteral,
Translated,
TranslatedWithContext,
TranslatedWithoutContext,
TypeLiteral,
Value, Value,
) )
@ -43,12 +50,3 @@ OBJECT_CONTENT_HOOKS.children = [
Strings, Strings,
Child, Child,
] ]
VALUE_HOOKS.children = [
TypeValue,
TranslatedStringValue,
FlagsValue,
IdentValue,
QuotedValue,
NumberValue,
]

View file

@ -18,7 +18,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
from .values import Value, TranslatedStringValue from .values import Value, Translated
from .common import * from .common import *

View file

@ -0,0 +1,55 @@
# binding.py
#
# Copyright 2023 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 dataclasses import dataclass
from .common import *
from .expression import ExprChain, LookupOp, IdentExpr
from .contexts import ValueTypeCtx
class Binding(AstNode):
grammar = [
Keyword("bind"),
ExprChain,
]
@property
def expression(self) -> ExprChain:
return self.children[ExprChain][0]
@property
def simple_binding(self) -> T.Optional["SimpleBinding"]:
if isinstance(self.expression.last, LookupOp):
if isinstance(self.expression.last.lhs, IdentExpr):
return SimpleBinding(
self.expression.last.lhs.ident, self.expression.last.property_name
)
return None
@validate("bind")
def not_bindable(self) -> None:
if binding_error := self.context[ValueTypeCtx].binding_error:
raise binding_error
@dataclass
class SimpleBinding:
source: str
property_name: str

View file

@ -19,7 +19,7 @@
from .. import gir from .. import gir
from ..ast_utils import AstNode, validate, docs from ..ast_utils import AstNode, validate, docs, context
from ..errors import ( from ..errors import (
CompileError, CompileError,
MultipleErrors, MultipleErrors,
@ -44,4 +44,3 @@ from ..parse_tree import *
OBJECT_CONTENT_HOOKS = AnyOf() OBJECT_CONTENT_HOOKS = AnyOf()
VALUE_HOOKS = AnyOf()

View file

@ -0,0 +1,28 @@
# contexts.py
#
# Copyright 2023 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
import typing as T
from dataclasses import dataclass
from .common import *
@dataclass
class ValueTypeCtx:
value_type: T.Optional[GirType]
binding_error: T.Optional[CompileError] = None

View file

@ -20,6 +20,7 @@
from .common import * from .common import *
from .types import TypeName from .types import TypeName
from .gtkbuilder_template import Template
expr = Pratt() expr = Pratt()
@ -30,6 +31,10 @@ class Expr(AstNode):
def type(self) -> T.Optional[GirType]: def type(self) -> T.Optional[GirType]:
raise NotImplementedError() raise NotImplementedError()
@property
def type_complete(self) -> bool:
return True
@property @property
def rhs(self) -> T.Optional["Expr"]: def rhs(self) -> T.Optional["Expr"]:
if isinstance(self.parent, ExprChain): if isinstance(self.parent, ExprChain):
@ -53,6 +58,10 @@ class ExprChain(Expr):
def type(self) -> T.Optional[GirType]: def type(self) -> T.Optional[GirType]:
return self.last.type return self.last.type
@property
def type_complete(self) -> bool:
return self.last.type_complete
class InfixExpr(Expr): class InfixExpr(Expr):
@property @property
@ -83,6 +92,13 @@ class IdentExpr(Expr):
else: else:
return None return None
@property
def type_complete(self) -> bool:
if object := self.root.objects_by_id.get(self.ident):
return not isinstance(object, Template)
else:
return True
class LookupOp(InfixExpr): class LookupOp(InfixExpr):
grammar = [".", UseIdent("property")] grammar = [".", UseIdent("property")]
@ -103,17 +119,24 @@ class LookupOp(InfixExpr):
@validate("property") @validate("property")
def property_exists(self): def property_exists(self):
if self.lhs.type is None or isinstance(self.lhs.type, UncheckedType): if (
self.lhs.type is None
or not self.lhs.type_complete
or isinstance(self.lhs.type, UncheckedType)
):
return return
elif not isinstance(self.lhs.type, gir.Class) and not isinstance( elif not isinstance(self.lhs.type, gir.Class) and not isinstance(
self.lhs.type, gir.Interface self.lhs.type, gir.Interface
): ):
raise CompileError( raise CompileError(
f"Type {self.lhs.type.full_name} does not have properties" f"Type {self.lhs.type.full_name} does not have properties"
) )
elif self.lhs.type.properties.get(self.property_name) is None: elif self.lhs.type.properties.get(self.property_name) is None:
raise CompileError( raise CompileError(
f"{self.lhs.type.full_name} does not have a property called {self.property_name}" f"{self.lhs.type.full_name} does not have a property called {self.property_name}",
did_you_mean=(self.property_name, self.lhs.type.properties.keys()),
) )
@ -124,9 +147,13 @@ class CastExpr(InfixExpr):
def type(self) -> T.Optional[GirType]: def type(self) -> T.Optional[GirType]:
return self.children[TypeName][0].gir_type return self.children[TypeName][0].gir_type
@property
def type_complete(self) -> bool:
return True
@validate() @validate()
def cast_makes_sense(self): def cast_makes_sense(self):
if self.lhs.type is None: if self.type is None or self.lhs.type is None:
return return
if not self.type.assignable_to(self.lhs.type): if not self.type.assignable_to(self.lhs.type):

View file

@ -17,51 +17,28 @@
# #
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
from dataclasses import dataclass
from .expression import ExprChain from .expression import ExprChain
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, Translated
from .common import * from .common import *
from .contexts import ValueTypeCtx
from .property_binding import PropertyBinding
from .binding import Binding
class Property(AstNode): class Property(AstNode):
grammar = AnyOf( grammar = [UseIdent("name"), ":", Value, ";"]
[
UseIdent("name"), @property
":", def name(self) -> str:
Keyword("bind"), return self.tokens["name"]
UseIdent("bind_source"),
".", @property
UseIdent("bind_property"), def value(self) -> Value:
ZeroOrMore( return self.children[0]
AnyOf(
["no-sync-create", UseLiteral("no_sync_create", True)],
["inverted", UseLiteral("inverted", True)],
["bidirectional", UseLiteral("bidirectional", True)],
Match("sync-create").warn(
"sync-create is deprecated in favor of no-sync-create"
),
)
),
";",
],
Statement(
UseIdent("name"),
UseLiteral("binding", True),
":",
"bind",
ExprChain,
),
Statement(
UseIdent("name"),
":",
AnyOf(
Object,
VALUE_HOOKS,
).expected("a value"),
),
)
@property @property
def gir_class(self): def gir_class(self):
@ -72,10 +49,29 @@ class Property(AstNode):
if self.gir_class is not None and not isinstance(self.gir_class, UncheckedType): if self.gir_class is not None and not isinstance(self.gir_class, UncheckedType):
return self.gir_class.properties.get(self.tokens["name"]) return self.gir_class.properties.get(self.tokens["name"])
@property @context(ValueTypeCtx)
def value_type(self): def value_type(self) -> ValueTypeCtx:
if (
(
isinstance(self.value.child, PropertyBinding)
or isinstance(self.value.child, Binding)
)
and self.gir_property is not None
and self.gir_property.construct_only
):
binding_error = CompileError(
f"{self.gir_property.full_name} can't be bound because it is construct-only",
hints=["construct-only properties may only be set to a static value"],
)
else:
binding_error = None
if self.gir_property is not None: if self.gir_property is not None:
return self.gir_property.type type = self.gir_property.type
else:
type = None
return ValueTypeCtx(type, binding_error)
@validate("name") @validate("name")
def property_exists(self): def property_exists(self):
@ -95,40 +91,11 @@ class Property(AstNode):
did_you_mean=(self.tokens["name"], self.gir_class.properties.keys()), did_you_mean=(self.tokens["name"], self.gir_class.properties.keys()),
) )
@validate("bind")
def property_bindable(self):
if (
self.tokens["bind"]
and self.gir_property is not None
and self.gir_property.construct_only
):
raise CompileError(
f"{self.gir_property.full_name} can't be bound because it is construct-only",
hints=["construct-only properties may only be set to a static value"],
)
@validate("name") @validate("name")
def property_writable(self): def property_writable(self):
if self.gir_property is not None and not self.gir_property.writable: if self.gir_property is not None and not self.gir_property.writable:
raise CompileError(f"{self.gir_property.full_name} is not writable") raise CompileError(f"{self.gir_property.full_name} is not writable")
@validate()
def obj_property_type(self):
if len(self.children[Object]) == 0:
return
object = self.children[Object][0]
type = self.value_type
if (
object
and type
and object.gir_class
and not object.gir_class.assignable_to(type)
):
raise CompileError(
f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
)
@validate("name") @validate("name")
def unique_in_parent(self): def unique_in_parent(self):
self.validate_unique_in_parent( self.validate_unique_in_parent(

View file

@ -82,10 +82,11 @@ class Signal(AstNode):
@validate("handler") @validate("handler")
def old_extern(self): def old_extern(self):
if not self.tokens["extern"]: if not self.tokens["extern"]:
raise UpgradeWarning( if self.handler is not None:
"Use the '$' extern syntax introduced in blueprint 0.8.0", raise UpgradeWarning(
actions=[CodeAction("Use '$' syntax", "$" + self.tokens["handler"])], "Use the '$' extern syntax introduced in blueprint 0.8.0",
) actions=[CodeAction("Use '$' syntax", "$" + self.handler)],
)
@validate("name") @validate("name")
def signal_exists(self): def signal_exists(self):

View file

@ -21,6 +21,7 @@ from .gobject_object import ObjectContent, validate_parent_type
from .attributes import BaseTypedAttribute from .attributes import BaseTypedAttribute
from .values import Value from .values import Value
from .common import * from .common import *
from .contexts import ValueTypeCtx
def get_property_types(gir): def get_property_types(gir):
@ -108,7 +109,7 @@ class A11yProperty(BaseTypedAttribute):
grammar = Statement( grammar = Statement(
UseIdent("name"), UseIdent("name"),
":", ":",
VALUE_HOOKS.expected("a value"), Value,
) )
@property @property
@ -129,8 +130,12 @@ class A11yProperty(BaseTypedAttribute):
return self.tokens["name"].replace("_", "-") return self.tokens["name"].replace("_", "-")
@property @property
def value_type(self) -> GirType: def value(self) -> Value:
return get_types(self.root.gir).get(self.tokens["name"]) return self.children[0]
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
return ValueTypeCtx(get_types(self.root.gir).get(self.tokens["name"]))
@validate("name") @validate("name")
def is_valid_property(self): def is_valid_property(self):
@ -161,6 +166,10 @@ class A11y(AstNode):
Until(A11yProperty, "}"), Until(A11yProperty, "}"),
] ]
@property
def properties(self) -> T.List[A11yProperty]:
return self.children[A11yProperty]
@validate("accessibility") @validate("accessibility")
def container_is_widget(self): def container_is_widget(self):
validate_parent_type(self, "Gtk", "Widget", "accessibility properties") validate_parent_type(self, "Gtk", "Widget", "accessibility properties")

View file

@ -21,15 +21,22 @@
from .attributes import BaseTypedAttribute from .attributes import BaseTypedAttribute
from .gobject_object import ObjectContent, validate_parent_type from .gobject_object import ObjectContent, validate_parent_type
from .common import * from .common import *
from .contexts import ValueTypeCtx
from .values import Value
class Item(BaseTypedAttribute): class Item(AstNode):
tag_name = "item" @property
attr_name = "id" def name(self) -> str:
return self.tokens["name"]
@property @property
def value_type(self): def value(self) -> Value:
return StringType() return self.children[Value][0]
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
return ValueTypeCtx(StringType())
item = Group( item = Group(
@ -41,7 +48,7 @@ item = Group(
":", ":",
] ]
), ),
VALUE_HOOKS, Value,
], ],
) )

View file

@ -21,15 +21,25 @@
from .attributes import BaseAttribute from .attributes import BaseAttribute
from .gobject_object import ObjectContent, validate_parent_type from .gobject_object import ObjectContent, validate_parent_type
from .common import * from .common import *
from .contexts import ValueTypeCtx
from .values import Value
class LayoutProperty(BaseAttribute): class LayoutProperty(AstNode):
tag_name = "property" tag_name = "property"
@property @property
def value_type(self): def name(self) -> str:
return self.tokens["name"]
@property
def value(self) -> Value:
return self.children[Value][0]
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
# there isn't really a way to validate these # there isn't really a way to validate these
return None return ValueTypeCtx(None)
@validate("name") @validate("name")
def unique_in_parent(self): def unique_in_parent(self):
@ -41,11 +51,7 @@ class LayoutProperty(BaseAttribute):
layout_prop = Group( layout_prop = Group(
LayoutProperty, LayoutProperty,
Statement( Statement(UseIdent("name"), ":", Err(Value, "Expected a value")),
UseIdent("name"),
":",
VALUE_HOOKS.expected("a value"),
),
) )

View file

@ -24,6 +24,7 @@ from blueprintcompiler.language.values import Value
from .attributes import BaseAttribute from .attributes import BaseAttribute
from .gobject_object import Object, ObjectContent from .gobject_object import Object, ObjectContent
from .common import * from .common import *
from .contexts import ValueTypeCtx
class Menu(AstNode): class Menu(AstNode):
@ -49,17 +50,23 @@ class Menu(AstNode):
raise CompileError("Menu requires an ID") raise CompileError("Menu requires an ID")
class MenuAttribute(BaseAttribute): class MenuAttribute(AstNode):
tag_name = "attribute" tag_name = "attribute"
@property @property
def value_type(self): def name(self) -> str:
return None return self.tokens["name"]
@property @property
def value(self) -> Value: def value(self) -> Value:
return self.children[Value][0] return self.children[Value][0]
@context(ValueTypeCtx)
def value_type(self) -> ValueTypeCtx:
return ValueTypeCtx(
None, binding_error=CompileError("Bindings are not permitted in menus")
)
menu_contents = Sequence() menu_contents = Sequence()
@ -78,7 +85,7 @@ menu_attribute = Group(
[ [
UseIdent("name"), UseIdent("name"),
":", ":",
VALUE_HOOKS.expected("a value"), Err(Value, "Expected a value"),
Match(";").expected(), Match(";").expected(),
], ],
) )
@ -102,7 +109,7 @@ menu_item_shorthand = Group(
"(", "(",
Group( Group(
MenuAttribute, MenuAttribute,
[UseLiteral("name", "label"), VALUE_HOOKS], [UseLiteral("name", "label"), Value],
), ),
Optional( Optional(
[ [
@ -111,14 +118,14 @@ menu_item_shorthand = Group(
[ [
Group( Group(
MenuAttribute, MenuAttribute,
[UseLiteral("name", "action"), VALUE_HOOKS], [UseLiteral("name", "action"), Value],
), ),
Optional( Optional(
[ [
",", ",",
Group( Group(
MenuAttribute, MenuAttribute,
[UseLiteral("name", "icon"), VALUE_HOOKS], [UseLiteral("name", "icon"), Value],
), ),
] ]
), ),

View file

@ -20,16 +20,17 @@
from .attributes import BaseTypedAttribute from .attributes import BaseTypedAttribute
from .gobject_object import ObjectContent, validate_parent_type from .gobject_object import ObjectContent, validate_parent_type
from .values import Value, TranslatedStringValue from .values import Value, Translated
from .common import * from .common import *
from .contexts import ValueTypeCtx
class Item(AstNode): class Item(AstNode):
grammar = VALUE_HOOKS grammar = Value
@property @context(ValueTypeCtx)
def value_type(self): def value_type(self) -> ValueTypeCtx:
return StringType() return ValueTypeCtx(StringType())
class Strings(AstNode): class Strings(AstNode):

View file

@ -64,6 +64,9 @@ class GtkDirective(AstNode):
self.gtk_version() self.gtk_version()
if self.tokens["version"] is not None: if self.tokens["version"] is not None:
return gir.get_namespace("Gtk", self.tokens["version"]) return gir.get_namespace("Gtk", self.tokens["version"])
else:
# For better error handling, just assume it's 4.0
return gir.get_namespace("Gtk", "4.0")
class Import(AstNode): class Import(AstNode):

View file

@ -0,0 +1,139 @@
# property_binding.py
#
# Copyright 2023 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 *
from .contexts import ValueTypeCtx
from .gobject_object import Object
from .gtkbuilder_template import Template
class PropertyBindingFlag(AstNode):
grammar = [
AnyOf(
UseExact("flag", "inverted"),
UseExact("flag", "bidirectional"),
UseExact("flag", "no-sync-create"),
UseExact("flag", "sync-create"),
)
]
@property
def flag(self) -> str:
return self.tokens["flag"]
@validate()
def sync_create(self):
if self.flag == "sync-create":
raise UpgradeWarning(
"'sync-create' is now the default. Use 'no-sync-create' if this is not wanted.",
actions=[CodeAction("remove 'sync-create'", "")],
)
class PropertyBinding(AstNode):
grammar = AnyOf(
[
Keyword("bind-property"),
UseIdent("source"),
".",
UseIdent("property"),
ZeroOrMore(PropertyBindingFlag),
],
[
Keyword("bind"),
UseIdent("source"),
".",
UseIdent("property"),
PropertyBindingFlag,
ZeroOrMore(PropertyBindingFlag),
],
)
@property
def source(self) -> str:
return self.tokens["source"]
@property
def source_obj(self) -> T.Optional[Object]:
return self.root.objects_by_id.get(self.source)
@property
def property_name(self) -> str:
return self.tokens["property"]
@property
def flags(self) -> T.List[PropertyBindingFlag]:
return self.children[PropertyBindingFlag]
@property
def inverted(self) -> bool:
return any([f.flag == "inverted" for f in self.flags])
@property
def bidirectional(self) -> bool:
return any([f.flag == "bidirectional" for f in self.flags])
@property
def no_sync_create(self) -> bool:
return any([f.flag == "no-sync-create" for f in self.flags])
@validate("source")
def source_object_exists(self) -> None:
if self.source_obj is None:
raise CompileError(
f"Could not find object with ID {self.source}",
did_you_mean=(self.source, self.root.objects_by_id.keys()),
)
@validate("property")
def property_exists(self) -> None:
if self.source_obj is None:
return
gir_class = self.source_obj.gir_class
if (
isinstance(self.source_obj, Template)
or gir_class is None
or isinstance(gir_class, UncheckedType)
):
# Objects that we have no gir data on should not be validated
# This happens for classes defined by the app itself
return
if (
isinstance(gir_class, gir.Class)
and gir_class.properties.get(self.property_name) is None
):
raise CompileError(
f"{gir_class.full_name} does not have a property called {self.property_name}"
)
@validate("bind-property")
def not_bindable(self) -> None:
if binding_error := self.context[ValueTypeCtx].binding_error:
raise binding_error
@validate("bind")
def old_bind(self):
if self.tokens["bind"]:
raise UpgradeWarning(
"Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags",
actions=[CodeAction("Use 'bind-property'", "bind-property")],
)

View file

@ -94,7 +94,7 @@ class ClassName(TypeName):
@validate("namespace", "class_name") @validate("namespace", "class_name")
def gir_class_exists(self): def gir_class_exists(self):
if ( if (
self.gir_type self.gir_type is not None
and not isinstance(self.gir_type, UncheckedType) and not isinstance(self.gir_type, UncheckedType)
and not isinstance(self.gir_type, Class) and not isinstance(self.gir_type, Class)
): ):

View file

@ -21,41 +21,57 @@ import typing as T
from .common import * from .common import *
from .types import TypeName from .types import TypeName
from .property_binding import PropertyBinding
from .binding import Binding
from .gobject_object import Object
from .contexts import ValueTypeCtx
class Value(AstNode): class TranslatedWithoutContext(AstNode):
pass grammar = ["_", "(", UseQuoted("string"), Optional(","), ")"]
class TranslatedStringValue(Value):
grammar = AnyOf(
[
"_",
"(",
UseQuoted("value").expected("a quoted string"),
Match(")").expected(),
],
[
"C_",
"(",
UseQuoted("context").expected("a quoted string"),
",",
UseQuoted("value").expected("a quoted string"),
Optional(","),
Match(")").expected(),
],
)
@property @property
def string(self) -> str: def string(self) -> str:
return self.tokens["value"] return self.tokens["string"]
class TranslatedWithContext(AstNode):
grammar = [
"C_",
"(",
UseQuoted("context"),
",",
UseQuoted("string"),
Optional(","),
")",
]
@property @property
def context(self) -> T.Optional[str]: def string(self) -> str:
return self.tokens["string"]
@property
def context(self) -> str:
return self.tokens["context"] return self.tokens["context"]
class TypeValue(Value): class Translated(AstNode):
grammar = AnyOf(TranslatedWithoutContext, TranslatedWithContext)
@property
def child(self) -> T.Union[TranslatedWithContext, TranslatedWithoutContext]:
return self.children[0]
@validate()
def validate_for_type(self) -> None:
expected_type = self.context[ValueTypeCtx].value_type
if expected_type is not None and not expected_type.assignable_to(StringType()):
raise CompileError(
f"Cannot convert translated string to {expected_type.full_name}"
)
class TypeLiteral(AstNode):
grammar = [ grammar = [
"typeof", "typeof",
"(", "(",
@ -64,17 +80,17 @@ class TypeValue(Value):
] ]
@property @property
def type_name(self): def type_name(self) -> TypeName:
return self.children[TypeName][0] return self.children[TypeName][0]
@validate() @validate()
def validate_for_type(self): def validate_for_type(self) -> None:
type = self.parent.value_type expected_type = self.context[ValueTypeCtx].value_type
if type is not None and not isinstance(type, gir.TypeType): if expected_type is not None and not isinstance(expected_type, gir.TypeType):
raise CompileError(f"Cannot convert GType to {type.full_name}") raise CompileError(f"Cannot convert GType to {expected_type.full_name}")
class QuotedValue(Value): class QuotedLiteral(AstNode):
grammar = UseQuoted("value") grammar = UseQuoted("value")
@property @property
@ -82,22 +98,22 @@ class QuotedValue(Value):
return self.tokens["value"] return self.tokens["value"]
@validate() @validate()
def validate_for_type(self): def validate_for_type(self) -> None:
type = self.parent.value_type expected_type = self.context[ValueTypeCtx].value_type
if ( if (
isinstance(type, gir.IntType) isinstance(expected_type, gir.IntType)
or isinstance(type, gir.UIntType) or isinstance(expected_type, gir.UIntType)
or isinstance(type, gir.FloatType) or isinstance(expected_type, gir.FloatType)
): ):
raise CompileError(f"Cannot convert string to number") raise CompileError(f"Cannot convert string to number")
elif isinstance(type, gir.StringType): elif isinstance(expected_type, gir.StringType):
pass pass
elif ( elif (
isinstance(type, gir.Class) isinstance(expected_type, gir.Class)
or isinstance(type, gir.Interface) or isinstance(expected_type, gir.Interface)
or isinstance(type, gir.Boxed) or isinstance(expected_type, gir.Boxed)
): ):
parseable_types = [ parseable_types = [
"Gdk.Paintable", "Gdk.Paintable",
@ -111,31 +127,32 @@ class QuotedValue(Value):
"Gsk.Transform", "Gsk.Transform",
"GLib.Variant", "GLib.Variant",
] ]
if type.full_name not in parseable_types: if expected_type.full_name not in parseable_types:
hints = [] hints = []
if isinstance(type, gir.TypeType): if isinstance(expected_type, gir.TypeType):
hints.append( hints.append(f"use the typeof operator: 'typeof({self.value})'")
f"use the typeof operator: 'typeof({self.tokens('value')})'"
)
raise CompileError( raise CompileError(
f"Cannot convert string to {type.full_name}", hints=hints f"Cannot convert string to {expected_type.full_name}", hints=hints
) )
elif type is not None: elif expected_type is not None:
raise CompileError(f"Cannot convert string to {type.full_name}") raise CompileError(f"Cannot convert string to {expected_type.full_name}")
class NumberValue(Value): class NumberLiteral(AstNode):
grammar = UseNumber("value") grammar = [
Optional(AnyOf(UseExact("sign", "-"), UseExact("sign", "+"))),
UseNumber("value"),
]
@property @property
def value(self) -> T.Union[int, float]: def value(self) -> T.Union[int, float]:
return self.tokens["value"] return self.tokens["value"]
@validate() @validate()
def validate_for_type(self): def validate_for_type(self) -> None:
type = self.parent.value_type expected_type = self.context[ValueTypeCtx].value_type
if isinstance(type, gir.IntType): if isinstance(expected_type, gir.IntType):
try: try:
int(self.tokens["value"]) int(self.tokens["value"])
except: except:
@ -143,7 +160,7 @@ class NumberValue(Value):
f"Cannot convert {self.group.tokens['value']} to integer" f"Cannot convert {self.group.tokens['value']} to integer"
) )
elif isinstance(type, gir.UIntType): elif isinstance(expected_type, gir.UIntType):
try: try:
int(self.tokens["value"]) int(self.tokens["value"])
if int(self.tokens["value"]) < 0: if int(self.tokens["value"]) < 0:
@ -153,7 +170,7 @@ class NumberValue(Value):
f"Cannot convert {self.group.tokens['value']} to unsigned integer" f"Cannot convert {self.group.tokens['value']} to unsigned integer"
) )
elif isinstance(type, gir.FloatType): elif isinstance(expected_type, gir.FloatType):
try: try:
float(self.tokens["value"]) float(self.tokens["value"])
except: except:
@ -161,8 +178,8 @@ class NumberValue(Value):
f"Cannot convert {self.group.tokens['value']} to float" f"Cannot convert {self.group.tokens['value']} to float"
) )
elif type is not None: elif expected_type is not None:
raise CompileError(f"Cannot convert number to {type.full_name}") raise CompileError(f"Cannot convert number to {expected_type.full_name}")
class Flag(AstNode): class Flag(AstNode):
@ -174,17 +191,17 @@ class Flag(AstNode):
@property @property
def value(self) -> T.Optional[int]: def value(self) -> T.Optional[int]:
type = self.parent.parent.value_type type = self.context[ValueTypeCtx].value_type
if not isinstance(type, Enumeration): if not isinstance(type, Enumeration):
return None return None
elif member := type.members.get(self.tokens["value"]): elif member := type.members.get(self.name):
return member.value return member.value
else: else:
return None return None
@docs() @docs()
def docs(self): def docs(self):
type = self.parent.parent.value_type type = self.context[ValueTypeCtx].value_type
if not isinstance(type, Enumeration): if not isinstance(type, Enumeration):
return return
if member := type.members.get(self.tokens["value"]): if member := type.members.get(self.tokens["value"]):
@ -192,15 +209,18 @@ class Flag(AstNode):
@validate() @validate()
def validate_for_type(self): def validate_for_type(self):
type = self.parent.parent.value_type expected_type = self.context[ValueTypeCtx].value_type
if isinstance(type, gir.Bitfield) and self.tokens["value"] not in type.members: if (
isinstance(expected_type, gir.Bitfield)
and self.tokens["value"] not in expected_type.members
):
raise CompileError( raise CompileError(
f"{self.tokens['value']} is not a member of {type.full_name}", f"{self.tokens['value']} is not a member of {expected_type.full_name}",
did_you_mean=(self.tokens["value"], type.members.keys()), did_you_mean=(self.tokens["value"], expected_type.members.keys()),
) )
class FlagsValue(Value): class Flags(AstNode):
grammar = [Flag, "|", Delimited(Flag, "|")] grammar = [Flag, "|", Delimited(Flag, "|")]
@property @property
@ -208,57 +228,104 @@ class FlagsValue(Value):
return self.children return self.children
@validate() @validate()
def parent_is_bitfield(self): def validate_for_type(self) -> None:
type = self.parent.value_type expected_type = self.context[ValueTypeCtx].value_type
if type is not None and not isinstance(type, gir.Bitfield): if expected_type is not None and not isinstance(expected_type, gir.Bitfield):
raise CompileError(f"{type.full_name} is not a bitfield type") raise CompileError(f"{expected_type.full_name} is not a bitfield type")
class IdentValue(Value): class IdentLiteral(AstNode):
grammar = UseIdent("value") grammar = UseIdent("value")
@property
def ident(self) -> str:
return self.tokens["value"]
@validate() @validate()
def validate_for_type(self): def validate_for_type(self) -> None:
type = self.parent.value_type expected_type = self.context[ValueTypeCtx].value_type
if isinstance(expected_type, gir.BoolType):
if self.ident not in ["true", "false"]:
raise CompileError(f"Expected 'true' or 'false' for boolean value")
if isinstance(type, gir.Enumeration): elif isinstance(expected_type, gir.Enumeration):
if self.tokens["value"] not in type.members: if self.ident not in expected_type.members:
raise CompileError( raise CompileError(
f"{self.tokens['value']} is not a member of {type.full_name}", f"{self.ident} is not a member of {expected_type.full_name}",
did_you_mean=(self.tokens["value"], type.members.keys()), did_you_mean=(self.ident, list(expected_type.members.keys())),
) )
elif isinstance(type, gir.BoolType): elif expected_type is not None:
if self.tokens["value"] not in ["true", "false"]: object = self.root.objects_by_id.get(self.ident)
raise CompileError(
f"Expected 'true' or 'false' for boolean value",
did_you_mean=(self.tokens["value"], ["true", "false"]),
)
elif type is not None:
object = self.root.objects_by_id.get(self.tokens["value"])
if object is None: if object is None:
raise CompileError( raise CompileError(
f"Could not find object with ID {self.tokens['value']}", f"Could not find object with ID {self.ident}",
did_you_mean=(self.tokens["value"], self.root.objects_by_id.keys()), did_you_mean=(self.ident, self.root.objects_by_id.keys()),
) )
elif object.gir_class and not object.gir_class.assignable_to(type): elif object.gir_class and not object.gir_class.assignable_to(expected_type):
raise CompileError( raise CompileError(
f"Cannot assign {object.gir_class.full_name} to {type.full_name}" f"Cannot assign {object.gir_class.full_name} to {expected_type.full_name}"
) )
@docs() @docs()
def docs(self): def docs(self) -> T.Optional[str]:
type = self.parent.value_type type = self.context[ValueTypeCtx].value_type
if isinstance(type, gir.Enumeration): if isinstance(type, gir.Enumeration):
if member := type.members.get(self.tokens["value"]): if member := type.members.get(self.ident):
return member.doc return member.doc
else: else:
return type.doc return type.doc
elif isinstance(type, gir.GirNode): elif isinstance(type, gir.GirNode):
return type.doc return type.doc
else:
return None
def get_semantic_tokens(self) -> T.Iterator[SemanticToken]: def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
if isinstance(self.parent.value_type, gir.Enumeration): if isinstance(self.parent.value_type, gir.Enumeration):
token = self.group.tokens["value"] token = self.group.tokens["value"]
yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember) yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember)
class Literal(AstNode):
grammar = AnyOf(
TypeLiteral,
QuotedLiteral,
NumberLiteral,
IdentLiteral,
)
@property
def value(
self,
) -> T.Union[TypeLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]:
return self.children[0]
class ObjectValue(AstNode):
grammar = Object
@property
def object(self) -> Object:
return self.children[Object][0]
@validate()
def validate_for_type(self) -> None:
expected_type = self.context[ValueTypeCtx].value_type
if (
expected_type is not None
and self.object.gir_class is not None
and not self.object.gir_class.assignable_to(expected_type)
):
raise CompileError(
f"Cannot assign {self.object.gir_class.full_name} to {expected_type.full_name}"
)
class Value(AstNode):
grammar = AnyOf(PropertyBinding, Binding, Translated, ObjectValue, Flags, Literal)
@property
def child(
self,
) -> T.Union[PropertyBinding, Binding, Translated, ObjectValue, Flags, Literal,]:
return self.children[0]

View file

@ -82,51 +82,57 @@ class XmlOutput(OutputFormat):
xml.end_tag() xml.end_tag()
def _emit_property(self, property: Property, xml: XmlEmitter): def _emit_property(self, property: Property, xml: XmlEmitter):
values = property.children[Value] value = property.value
value = values[0] if len(values) == 1 else None child = value.child
bind_flags = [] props: T.Dict[str, T.Optional[str]] = {
if property.tokens["bind_source"] and not property.tokens["no_sync_create"]: "name": property.name,
bind_flags.append("sync-create")
if property.tokens["inverted"]:
bind_flags.append("invert-boolean")
if property.tokens["bidirectional"]:
bind_flags.append("bidirectional")
bind_flags_str = "|".join(bind_flags) or None
props = {
"name": property.tokens["name"],
"bind-source": property.tokens["bind_source"],
"bind-property": property.tokens["bind_property"],
"bind-flags": bind_flags_str,
} }
if isinstance(value, TranslatedStringValue): if isinstance(child, Translated):
xml.start_tag("property", **props, **self._translated_string_attrs(value)) xml.start_tag("property", **props, **self._translated_string_attrs(child))
xml.put_text(value.string) xml.put_text(child.child.string)
xml.end_tag() xml.end_tag()
elif len(property.children[Object]) == 1: elif isinstance(child, Object):
xml.start_tag("property", **props) xml.start_tag("property", **props)
self._emit_object(property.children[Object][0], xml) self._emit_object(child, xml)
xml.end_tag() xml.end_tag()
elif value is None: elif isinstance(child, Binding):
if property.tokens["binding"]: if simple := child.simple_binding:
xml.start_tag("binding", **props) props["bind-source"] = simple.source
self._emit_expression(property.children[ExprChain][0], xml) props["bind-property"] = simple.property_name
xml.end_tag() props["bind-flags"] = "sync-create"
else:
xml.put_self_closing("property", **props) xml.put_self_closing("property", **props)
else:
xml.start_tag("binding", **props)
self._emit_expression(child.expression, xml)
xml.end_tag()
elif isinstance(child, PropertyBinding):
bind_flags = []
if not child.no_sync_create:
bind_flags.append("sync-create")
if child.inverted:
bind_flags.append("invert-boolean")
if child.bidirectional:
bind_flags.append("bidirectional")
props["bind-source"] = child.source
props["bind-property"] = child.property_name
props["bind-flags"] = "|".join(bind_flags) or None
xml.put_self_closing("property", **props)
else: else:
xml.start_tag("property", **props) xml.start_tag("property", **props)
self._emit_value(value, xml) self._emit_value(value, xml)
xml.end_tag() xml.end_tag()
def _translated_string_attrs( def _translated_string_attrs(
self, translated: TranslatedStringValue self, translated: Translated
) -> T.Dict[str, T.Optional[str]]: ) -> T.Dict[str, T.Optional[str]]:
return { return {
"translatable": "true", "translatable": "true",
"context": translated.context, "context": translated.child.context
if isinstance(translated.child, TranslatedWithContext)
else None,
} }
def _emit_signal(self, signal: Signal, xml: XmlEmitter): def _emit_signal(self, signal: Signal, xml: XmlEmitter):
@ -154,23 +160,30 @@ class XmlOutput(OutputFormat):
xml.end_tag() xml.end_tag()
def _emit_value(self, value: Value, xml: XmlEmitter): def _emit_value(self, value: Value, xml: XmlEmitter):
if isinstance(value, IdentValue): if isinstance(value.child, Literal):
if isinstance(value.parent.value_type, gir.Enumeration): literal = value.child.value
xml.put_text( if isinstance(literal, IdentLiteral):
str(value.parent.value_type.members[value.tokens["value"]].value) value_type = value.context[ValueTypeCtx].value_type
) if isinstance(value_type, gir.BoolType):
xml.put_text(literal.ident)
elif isinstance(value_type, gir.Enumeration):
xml.put_text(str(value_type.members[literal.ident].value))
else:
xml.put_text(literal.ident)
elif isinstance(literal, TypeLiteral):
xml.put_text(literal.type_name.glib_type_name)
else: else:
xml.put_text(value.tokens["value"]) xml.put_text(literal.value)
elif isinstance(value, QuotedValue) or isinstance(value, NumberValue): elif isinstance(value.child, Flags):
xml.put_text(value.value)
elif isinstance(value, FlagsValue):
xml.put_text( xml.put_text(
"|".join([str(flag.value or flag.name) for flag in value.flags]) "|".join([str(flag.value or flag.name) for flag in value.child.flags])
) )
elif isinstance(value, TranslatedStringValue): elif isinstance(value.child, Translated):
raise CompilerBugError("translated values must be handled in the parent") raise CompilerBugError("translated values must be handled in the parent")
elif isinstance(value, TypeValue): elif isinstance(value.child, TypeLiteral):
xml.put_text(value.type_name.glib_type_name) xml.put_text(value.child.type_name.glib_type_name)
elif isinstance(value.child, ObjectValue):
self._emit_object(value.child.object, xml)
else: else:
raise CompilerBugError() raise CompilerBugError()
@ -215,9 +228,9 @@ class XmlOutput(OutputFormat):
): ):
attrs = {attr: name} attrs = {attr: name}
if isinstance(value, TranslatedStringValue): if isinstance(value.child, Translated):
xml.start_tag(tag, **attrs, **self._translated_string_attrs(value)) xml.start_tag(tag, **attrs, **self._translated_string_attrs(value.child))
xml.put_text(value.string) xml.put_text(value.child.child.string)
xml.end_tag() xml.end_tag()
else: else:
xml.start_tag(tag, **attrs) xml.start_tag(tag, **attrs)
@ -227,43 +240,37 @@ class XmlOutput(OutputFormat):
def _emit_extensions(self, extension, xml: XmlEmitter): def _emit_extensions(self, extension, xml: XmlEmitter):
if isinstance(extension, A11y): if isinstance(extension, A11y):
xml.start_tag("accessibility") xml.start_tag("accessibility")
for child in extension.children: for prop in extension.properties:
self._emit_attribute( self._emit_attribute(prop.tag_name, "name", prop.name, prop.value, xml)
child.tag_name, "name", child.name, child.children[Value][0], xml
)
xml.end_tag() xml.end_tag()
elif isinstance(extension, Filters): elif isinstance(extension, Filters):
xml.start_tag(extension.tokens["tag_name"]) xml.start_tag(extension.tokens["tag_name"])
for child in extension.children: for prop in extension.children:
xml.start_tag(child.tokens["tag_name"]) xml.start_tag(prop.tokens["tag_name"])
xml.put_text(child.tokens["name"]) xml.put_text(prop.tokens["name"])
xml.end_tag() xml.end_tag()
xml.end_tag() xml.end_tag()
elif isinstance(extension, Items): elif isinstance(extension, Items):
xml.start_tag("items") xml.start_tag("items")
for child in extension.children: for prop in extension.children:
self._emit_attribute( self._emit_attribute("item", "id", prop.name, prop.value, xml)
"item", "id", child.name, child.children[Value][0], xml
)
xml.end_tag() xml.end_tag()
elif isinstance(extension, Layout): elif isinstance(extension, Layout):
xml.start_tag("layout") xml.start_tag("layout")
for child in extension.children: for prop in extension.children:
self._emit_attribute( self._emit_attribute("property", "name", prop.name, prop.value, xml)
"property", "name", child.name, child.children[Value][0], xml
)
xml.end_tag() xml.end_tag()
elif isinstance(extension, Strings): elif isinstance(extension, Strings):
xml.start_tag("items") xml.start_tag("items")
for child in extension.children: for prop in extension.children:
value = child.children[Value][0] value = prop.children[Value][0]
if isinstance(value, TranslatedStringValue): if isinstance(value.child, Translated):
xml.start_tag("item", **self._translated_string_attrs(value)) xml.start_tag("item", **self._translated_string_attrs(value))
xml.put_text(value.string) xml.put_text(value.child.child.string)
xml.end_tag() xml.end_tag()
else: else:
xml.start_tag("item") xml.start_tag("item")
@ -273,14 +280,14 @@ class XmlOutput(OutputFormat):
elif isinstance(extension, Styles): elif isinstance(extension, Styles):
xml.start_tag("style") xml.start_tag("style")
for child in extension.children: for prop in extension.children:
xml.put_self_closing("class", name=child.tokens["name"]) xml.put_self_closing("class", name=prop.tokens["name"])
xml.end_tag() xml.end_tag()
elif isinstance(extension, Widgets): elif isinstance(extension, Widgets):
xml.start_tag("widgets") xml.start_tag("widgets")
for child in extension.children: for prop in extension.children:
xml.put_self_closing("widget", name=child.tokens["name"]) xml.put_self_closing("widget", name=prop.tokens["name"])
xml.end_tag() xml.end_tag()
else: else:

View file

@ -567,6 +567,19 @@ class UseLiteral(ParseNode):
return True return True
class UseExact(ParseNode):
"""Matches the given identifier and sets it as a named token."""
def __init__(self, key: str, string: str):
self.key = key
self.string = string
def _parse(self, ctx: ParseContext):
token = ctx.next_token()
ctx.set_group_val(self.key, self.string, token)
return str(token) == self.string
class Keyword(ParseNode): class Keyword(ParseNode):
"""Matches the given identifier and sets it as a named token, with the name """Matches the given identifier and sets it as a named token, with the name
being the identifier itself.""" being the identifier itself."""

View file

@ -21,7 +21,7 @@
from .errors import MultipleErrors, PrintableError from .errors import MultipleErrors, PrintableError
from .parse_tree import * from .parse_tree import *
from .tokenizer import TokenType from .tokenizer import TokenType
from .language import OBJECT_CONTENT_HOOKS, VALUE_HOOKS, Template, UI from .language import OBJECT_CONTENT_HOOKS, Template, UI
def parse( def parse(

View file

@ -1 +1 @@
4,3,21,Cannot assign Gtk.Label to Gtk.Adjustment 4,15,8,Cannot assign Gtk.Label to Gtk.Adjustment

View file

@ -0,0 +1,2 @@
4,12,4,Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags
6,12,4,Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags

View file

@ -0,0 +1,11 @@
using Gtk 4.0;
Box {
visible: bind-property box2.visible inverted;
orientation: bind box2.orientation;
spacing: bind-property box2.spacing no-sync-create;
}
Box box2 {
spacing: 6;
}

View file

@ -0,0 +1,11 @@
using Gtk 4.0;
Box {
visible: bind-property box2.visible inverted;
orientation: bind-property box2.orientation;
spacing: bind-property box2.spacing no-sync-create;
}
Box box2 {
spacing: 6;
}

View file

@ -145,7 +145,6 @@ class TestSamples(unittest.TestCase):
def test_samples(self): def test_samples(self):
self.assert_sample("accessibility") self.assert_sample("accessibility")
self.assert_sample("action_widgets") self.assert_sample("action_widgets")
self.assert_sample("binding")
self.assert_sample("child_type") self.assert_sample("child_type")
self.assert_sample("combo_box_text") self.assert_sample("combo_box_text")
self.assert_sample("comments") self.assert_sample("comments")
@ -163,6 +162,7 @@ class TestSamples(unittest.TestCase):
"parseable", skip_run=True "parseable", skip_run=True
) # The image resource doesn't exist ) # The image resource doesn't exist
self.assert_sample("property") self.assert_sample("property")
self.assert_sample("property_binding")
self.assert_sample("signal", skip_run=True) # The callback doesn't exist self.assert_sample("signal", skip_run=True) # The callback doesn't exist
self.assert_sample("size_group") self.assert_sample("size_group")
self.assert_sample("string_list") self.assert_sample("string_list")
@ -235,12 +235,12 @@ class TestSamples(unittest.TestCase):
self.assert_sample_error("two_templates") self.assert_sample_error("two_templates")
self.assert_sample_error("uint") self.assert_sample_error("uint")
self.assert_sample_error("using_invalid_namespace") self.assert_sample_error("using_invalid_namespace")
self.assert_sample_error("warn_old_bind")
self.assert_sample_error("warn_old_extern") self.assert_sample_error("warn_old_extern")
self.assert_sample_error("widgets_in_non_size_group") self.assert_sample_error("widgets_in_non_size_group")
def test_decompiler(self): def test_decompiler(self):
self.assert_decompile("accessibility_dec") self.assert_decompile("accessibility_dec")
self.assert_decompile("binding")
self.assert_decompile("child_type") self.assert_decompile("child_type")
self.assert_decompile("file_filter") self.assert_decompile("file_filter")
self.assert_decompile("flags") self.assert_decompile("flags")
@ -248,6 +248,7 @@ class TestSamples(unittest.TestCase):
self.assert_decompile("layout_dec") self.assert_decompile("layout_dec")
self.assert_decompile("menu_dec") self.assert_decompile("menu_dec")
self.assert_decompile("property") self.assert_decompile("property")
self.assert_decompile("property_binding_dec")
self.assert_decompile("placeholder_dec") self.assert_decompile("placeholder_dec")
self.assert_decompile("signal") self.assert_decompile("signal")
self.assert_decompile("strings") self.assert_decompile("strings")