mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
WIP: Add gir.PartialClass
This commit is contained in:
parent
632e9d7df6
commit
6fdb12fd5d
17 changed files with 231 additions and 147 deletions
|
@ -81,7 +81,7 @@ class AstNode:
|
||||||
def _get_errors(self):
|
def _get_errors(self):
|
||||||
for validator in self.validators:
|
for validator in self.validators:
|
||||||
try:
|
try:
|
||||||
validator(self)
|
validator.validate(self)
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
yield e
|
yield e
|
||||||
if e.fatal:
|
if e.fatal:
|
||||||
|
@ -149,43 +149,61 @@ class AstNode:
|
||||||
|
|
||||||
def validate(token_name=None, end_token_name=None, skip_incomplete=False):
|
def validate(token_name=None, end_token_name=None, skip_incomplete=False):
|
||||||
""" Decorator for functions that validate an AST node. Exceptions raised
|
""" Decorator for functions that validate an AST node. Exceptions raised
|
||||||
during validation are marked with range information from the tokens. """
|
during validation are marked with range information from the tokens.
|
||||||
|
The return value of the function can also be used as a property, and if
|
||||||
|
the function raises a CompileError, the property will be None. """
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
def inner(self):
|
class Validator:
|
||||||
if skip_incomplete and self.incomplete:
|
_validator = True
|
||||||
|
|
||||||
|
def __get__(self, instance, owner):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.validate(instance)
|
||||||
|
except Exception as e:
|
||||||
|
# Ignore errors and return None. This way, a diagnostic
|
||||||
|
# is only emitted once. Don't ignore CompilerBugErrors
|
||||||
|
# though, they should never happen.
|
||||||
|
if isinstance(e, CompilerBugError):
|
||||||
|
raise e
|
||||||
|
return None
|
||||||
|
|
||||||
|
def validate(self, node):
|
||||||
|
if skip_incomplete and node.incomplete:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
func(self)
|
return func(node)
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
# If the node is only partially complete, then an error must
|
# If the node is only partially complete, then an error must
|
||||||
# have already been reported at the parsing stage
|
# have already been reported at the parsing stage
|
||||||
if self.incomplete:
|
if node.incomplete:
|
||||||
return
|
return
|
||||||
|
|
||||||
# This mess of code sets the error's start and end positions
|
# This mess of code sets the error's start and end positions
|
||||||
# from the tokens passed to the decorator, if they have not
|
# from the tokens passed to the decorator, if they have not
|
||||||
# already been set
|
# already been set
|
||||||
if e.start is None:
|
if e.start is None:
|
||||||
if token := self.group.tokens.get(token_name):
|
if token := node.group.tokens.get(token_name):
|
||||||
e.start = token.start
|
e.start = token.start
|
||||||
else:
|
else:
|
||||||
e.start = self.group.start
|
e.start = node.group.start
|
||||||
|
|
||||||
if e.end is None:
|
if e.end is None:
|
||||||
if token := self.group.tokens.get(end_token_name):
|
if token := node.group.tokens.get(end_token_name):
|
||||||
e.end = token.end
|
e.end = token.end
|
||||||
elif token := self.group.tokens.get(token_name):
|
elif token := node.group.tokens.get(token_name):
|
||||||
e.end = token.end
|
e.end = token.end
|
||||||
else:
|
else:
|
||||||
e.end = self.group.end
|
e.end = node.group.end
|
||||||
|
|
||||||
# Re-raise the exception
|
# Re-raise the exception
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
inner._validator = True
|
return Validator()
|
||||||
return inner
|
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ def gtk_object_completer(ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def property_completer(ast_node, match_variables):
|
def property_completer(ast_node, match_variables):
|
||||||
if ast_node.gir_class:
|
if ast_node.gir_class and hasattr(ast_node.gir_class, "properties"):
|
||||||
for prop in ast_node.gir_class.properties:
|
for prop in ast_node.gir_class.properties:
|
||||||
yield Completion(prop, CompletionItemKind.Property, snippet=f"{prop}: $0;")
|
yield Completion(prop, CompletionItemKind.Property, snippet=f"{prop}: $0;")
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ def prop_value_completer(ast_node, match_variables):
|
||||||
matches=new_statement_patterns,
|
matches=new_statement_patterns,
|
||||||
)
|
)
|
||||||
def signal_completer(ast_node, match_variables):
|
def signal_completer(ast_node, match_variables):
|
||||||
if ast_node.gir_class:
|
if ast_node.gir_class and hasattr(ast_node.gir_class, "signals"):
|
||||||
for signal in ast_node.gir_class.signals:
|
for signal in ast_node.gir_class.signals:
|
||||||
if not isinstance(ast_node.parent, language.Object):
|
if not isinstance(ast_node.parent, language.Object):
|
||||||
name = "on"
|
name = "on"
|
||||||
|
|
|
@ -96,6 +96,9 @@ class GirType:
|
||||||
def full_name(self) -> str:
|
def full_name(self) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def lookup_property(self, property: str):
|
||||||
|
raise CompileError(f"Type {self.full_name} does not have properties")
|
||||||
|
|
||||||
|
|
||||||
class BasicType(GirType):
|
class BasicType(GirType):
|
||||||
name: str = "unknown type"
|
name: str = "unknown type"
|
||||||
|
@ -314,6 +317,17 @@ class Interface(GirNode, GirType):
|
||||||
result.append(self.get_containing(Repository)._resolve_dir_entry(entry))
|
result.append(self.get_containing(Repository)._resolve_dir_entry(entry))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def lookup_property(self, property: str):
|
||||||
|
if prop := self.properties.get(property):
|
||||||
|
return prop
|
||||||
|
elif self.is_partial:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise CompileError(
|
||||||
|
f"Interface {self.full_name} does not have a property called {property}",
|
||||||
|
did_you_mean=(property, self.properties.keys())
|
||||||
|
)
|
||||||
|
|
||||||
def assignable_to(self, other) -> bool:
|
def assignable_to(self, other) -> bool:
|
||||||
if self == other:
|
if self == other:
|
||||||
return True
|
return True
|
||||||
|
@ -323,7 +337,82 @@ class Interface(GirNode, GirType):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Class(GirNode, GirType):
|
class GirClass(GirType):
|
||||||
|
@property
|
||||||
|
def abstract(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parent(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_partial(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def implements(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def own_properties(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def own_signals(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def properties(self):
|
||||||
|
return { p.name: p for p in self._enum_properties() }
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def signals(self):
|
||||||
|
return { s.name: s for s in self._enum_signals() }
|
||||||
|
|
||||||
|
def lookup_property(self, property: str):
|
||||||
|
if prop := self.properties.get(property):
|
||||||
|
return prop
|
||||||
|
elif self.is_partial:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise CompileError(
|
||||||
|
f"Class {self.full_name} does not have a property called {property}",
|
||||||
|
did_you_mean=(property, self.properties.keys())
|
||||||
|
)
|
||||||
|
|
||||||
|
def _enum_properties(self):
|
||||||
|
yield from self.own_properties.values()
|
||||||
|
|
||||||
|
if self.parent is not None:
|
||||||
|
yield from self.parent.properties.values()
|
||||||
|
|
||||||
|
for impl in self.implements:
|
||||||
|
yield from impl.properties.values()
|
||||||
|
|
||||||
|
def _enum_signals(self):
|
||||||
|
yield from self.own_signals.values()
|
||||||
|
|
||||||
|
if self.parent is not None:
|
||||||
|
yield from self.parent.signals.values()
|
||||||
|
|
||||||
|
for impl in self.implements:
|
||||||
|
yield from impl.signals.values()
|
||||||
|
|
||||||
|
def assignable_to(self, other) -> bool:
|
||||||
|
if self == other:
|
||||||
|
return True
|
||||||
|
elif self.parent and self.parent.assignable_to(other):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
for iface in self.implements:
|
||||||
|
if iface.assignable_to(other):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Class(GirNode, GirClass):
|
||||||
def __init__(self, ns, tl: typelib.Typelib):
|
def __init__(self, ns, tl: typelib.Typelib):
|
||||||
super().__init__(ns, tl)
|
super().__init__(ns, tl)
|
||||||
|
|
||||||
|
@ -388,43 +477,31 @@ class Class(GirNode, GirType):
|
||||||
result += " implements " + ", ".join([impl.full_name for impl in self.implements])
|
result += " implements " + ", ".join([impl.full_name for impl in self.implements])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def properties(self):
|
|
||||||
return { p.name: p for p in self._enum_properties() }
|
|
||||||
|
|
||||||
@cached_property
|
class PartialClass(GirClass):
|
||||||
def signals(self):
|
def __init__(self, name: str, parent: GirType = None):
|
||||||
return { s.name: s for s in self._enum_signals() }
|
self._name = name
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
def assignable_to(self, other) -> bool:
|
@property
|
||||||
if self == other:
|
def full_name(self):
|
||||||
return True
|
return self._name
|
||||||
elif self.parent and self.parent.assignable_to(other):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
for iface in self.implements:
|
|
||||||
if iface.assignable_to(other):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def glib_type_name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def abstract(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _enum_properties(self):
|
@property
|
||||||
yield from self.own_properties.values()
|
def parent(self):
|
||||||
|
return self._parent
|
||||||
|
|
||||||
if self.parent is not None:
|
@property
|
||||||
yield from self.parent.properties.values()
|
def is_partial(self):
|
||||||
|
return True
|
||||||
for impl in self.implements:
|
|
||||||
yield from impl.properties.values()
|
|
||||||
|
|
||||||
def _enum_signals(self):
|
|
||||||
yield from self.own_signals.values()
|
|
||||||
|
|
||||||
if self.parent is not None:
|
|
||||||
yield from self.parent.signals.values()
|
|
||||||
|
|
||||||
for impl in self.implements:
|
|
||||||
yield from impl.signals.values()
|
|
||||||
|
|
||||||
|
|
||||||
class EnumMember(GirNode):
|
class EnumMember(GirNode):
|
||||||
|
|
|
@ -126,8 +126,7 @@ class LookupOp(InfixExpr):
|
||||||
@property
|
@property
|
||||||
def gir_type(self):
|
def gir_type(self):
|
||||||
if parent_type := self.lhs.gir_type:
|
if parent_type := self.lhs.gir_type:
|
||||||
if isinstance(parent_type, gir.Class) or isinstance(parent_type, gir.Interface):
|
if prop := parent_type.lookup_property(self.tokens["property"]):
|
||||||
if prop := parent_type.properties.get(self.tokens["property"]):
|
|
||||||
return prop.type
|
return prop.type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -137,15 +136,11 @@ class LookupOp(InfixExpr):
|
||||||
|
|
||||||
@validate("property")
|
@validate("property")
|
||||||
def property_exists(self):
|
def property_exists(self):
|
||||||
if parent_type := self.lhs.gir_type:
|
try:
|
||||||
if not (isinstance(parent_type, gir.Class) or isinstance(parent_type, gir.Interface)):
|
self.gir_type
|
||||||
raise CompileError(f"Type {parent_type.full_name} does not have properties")
|
except CompileError as e:
|
||||||
elif self.tokens["property"] not in parent_type.properties:
|
e.hints.append("Do you need to cast the previous expression?")
|
||||||
raise CompileError(
|
raise e
|
||||||
f"{parent_type.full_name} does not have a property called {self.tokens['property']}",
|
|
||||||
hints=["Do you need to cast the previous expression?"],
|
|
||||||
did_you_mean=(self.tokens['property'], parent_type.properties.keys()),
|
|
||||||
)
|
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
if isinstance(self.lhs, IdentExpr) and self.lhs.is_this:
|
if isinstance(self.lhs, IdentExpr) and self.lhs.is_this:
|
||||||
|
|
|
@ -48,14 +48,7 @@ class Object(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
class_names = self.children[ClassName]
|
return self.children[ClassName][0].gir_type
|
||||||
if len(class_names) > 0:
|
|
||||||
if isinstance(class_names[0].gir_type, Class):
|
|
||||||
return class_names[0].gir_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def glib_type_name(self) -> str:
|
|
||||||
return self.children[ClassName][0].glib_type_name
|
|
||||||
|
|
||||||
@docs("namespace")
|
@docs("namespace")
|
||||||
def namespace_docs(self):
|
def namespace_docs(self):
|
||||||
|
@ -83,7 +76,7 @@ class Object(AstNode):
|
||||||
|
|
||||||
def emit_start_tag(self, xml: XmlEmitter):
|
def emit_start_tag(self, xml: XmlEmitter):
|
||||||
xml.start_tag("object", **{
|
xml.start_tag("object", **{
|
||||||
"class": self.glib_type_name,
|
"class": self.gir_class,
|
||||||
"id": self.tokens["id"],
|
"id": self.tokens["id"],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -64,37 +64,16 @@ class Property(AstNode):
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
return self.parent.parent.gir_class
|
return self.parent.parent.gir_class
|
||||||
|
|
||||||
|
@validate("name")
|
||||||
@property
|
|
||||||
def gir_property(self):
|
def gir_property(self):
|
||||||
if self.gir_class is not None:
|
if self.gir_class is not None:
|
||||||
return self.gir_class.properties.get(self.tokens["name"])
|
return self.gir_class.lookup_property(self.tokens["name"])
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value_type(self):
|
def value_type(self):
|
||||||
if self.gir_property is not None:
|
if self.gir_property is not None:
|
||||||
return self.gir_property.type
|
return self.gir_property.type
|
||||||
|
|
||||||
|
|
||||||
@validate("name")
|
|
||||||
def property_exists(self):
|
|
||||||
if self.gir_class is None:
|
|
||||||
# Objects that we have no gir data on should not be validated
|
|
||||||
# This happens for classes defined by the app itself
|
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(self.parent.parent, Template):
|
|
||||||
# If the property is part of a template, it might be defined by
|
|
||||||
# the application and thus not in gir
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.gir_property is None:
|
|
||||||
raise CompileError(
|
|
||||||
f"Class {self.gir_class.full_name} does not contain a property called {self.tokens['name']}",
|
|
||||||
did_you_mean=(self.tokens["name"], self.gir_class.properties.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
@validate("bind")
|
@validate("bind")
|
||||||
def property_bindable(self):
|
def property_bindable(self):
|
||||||
if self.tokens["bind"] and self.gir_property is not None and self.gir_property.construct_only:
|
if self.tokens["bind"] and self.gir_property is not None and self.gir_property.construct_only:
|
||||||
|
|
|
@ -27,17 +27,12 @@ class Filters(AstNode):
|
||||||
def container_is_file_filter(self):
|
def container_is_file_filter(self):
|
||||||
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
|
validate_parent_type(self, "Gtk", "FileFilter", "file filter properties")
|
||||||
|
|
||||||
@validate()
|
@validate("tag")
|
||||||
def unique_in_parent(self):
|
|
||||||
# The token argument to validate() needs to be calculated based on
|
|
||||||
# the instance, hence wrapping it like this.
|
|
||||||
@validate(self.tokens["tag_name"])
|
|
||||||
def wrapped_validator(self):
|
def wrapped_validator(self):
|
||||||
self.validate_unique_in_parent(
|
self.validate_unique_in_parent(
|
||||||
f"Duplicate {self.tokens['tag_name']} block",
|
f"Duplicate {self.tokens['tag_name']} block",
|
||||||
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
check=lambda child: child.tokens["tag_name"] == self.tokens["tag_name"],
|
||||||
)
|
)
|
||||||
wrapped_validator(self)
|
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.start_tag(self.tokens["tag_name"])
|
xml.start_tag(self.tokens["tag_name"])
|
||||||
|
@ -57,7 +52,7 @@ def create_node(tag_name: str, singular: str):
|
||||||
return Group(
|
return Group(
|
||||||
Filters,
|
Filters,
|
||||||
[
|
[
|
||||||
Keyword(tag_name),
|
Keyword(tag_name, "tag"),
|
||||||
UseLiteral("tag_name", tag_name),
|
UseLiteral("tag_name", tag_name),
|
||||||
"[",
|
"[",
|
||||||
Delimited(
|
Delimited(
|
||||||
|
|
|
@ -49,6 +49,10 @@ class Child(AstNode):
|
||||||
if gir_class.assignable_to(parent_type):
|
if gir_class.assignable_to(parent_type):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
if gir_class.is_partial:
|
||||||
|
# we don't know if the class implements Gtk.Buildable or not
|
||||||
|
return
|
||||||
|
|
||||||
hints=["only Gio.ListStore or Gtk.Buildable implementors can have children"]
|
hints=["only Gio.ListStore or Gtk.Buildable implementors can have children"]
|
||||||
if "child" in gir_class.properties:
|
if "child" in gir_class.properties:
|
||||||
hints.append("did you mean to assign this object to the 'child' property?")
|
hints.append("did you mean to assign this object to the 'child' property?")
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
from .gobject_object import Object, ObjectContent
|
from .gobject_object import Object, ObjectContent
|
||||||
from .common import *
|
from .common import *
|
||||||
|
@ -34,11 +35,14 @@ class Template(Object):
|
||||||
ObjectContent,
|
ObjectContent,
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def gir_class(self):
|
def gir_class(self):
|
||||||
# Templates might not have a parent class defined
|
# Templates might not have a parent class defined
|
||||||
|
parent = None
|
||||||
if len(self.children[ClassName]):
|
if len(self.children[ClassName]):
|
||||||
return self.children[ClassName][0].gir_type
|
parent = self.children[ClassName][0].gir_type
|
||||||
|
|
||||||
|
return gir.PartialClass(self.tokens["id"], parent)
|
||||||
|
|
||||||
@validate("id")
|
@validate("id")
|
||||||
def unique_in_parent(self):
|
def unique_in_parent(self):
|
||||||
|
|
|
@ -25,7 +25,7 @@ from .common import *
|
||||||
class GtkDirective(AstNode):
|
class GtkDirective(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
Match("using").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
Match("using").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
||||||
Match("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
Keyword("Gtk").err("File must start with a \"using Gtk\" directive (e.g. `using Gtk 4.0;`)"),
|
||||||
UseNumberText("version").expected("a version number for GTK"),
|
UseNumberText("version").expected("a version number for GTK"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,15 +33,23 @@ class GtkDirective(AstNode):
|
||||||
def gtk_version(self):
|
def gtk_version(self):
|
||||||
version = self.tokens["version"]
|
version = self.tokens["version"]
|
||||||
if version not in ["4.0"]:
|
if version not in ["4.0"]:
|
||||||
err = CompileError("Only GTK 4 is supported")
|
err = CompileError("Only GTK 4 is supported", fatal=True)
|
||||||
if version and version.startswith("4"):
|
if version and version.startswith("4"):
|
||||||
err.hint("Expected the GIR version, not an exact version number. Use 'using Gtk 4.0;'.")
|
err.hint("Expected the GIR version, not an exact version number. Use 'using Gtk 4.0;'.")
|
||||||
else:
|
else:
|
||||||
err.hint("Expected 'using Gtk 4.0;'")
|
err.hint("Expected 'using Gtk 4.0;'")
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
|
return version
|
||||||
|
|
||||||
|
@validate()
|
||||||
|
def gir_namespace(self):
|
||||||
|
if self.gtk_version is None:
|
||||||
|
# use that error message instead
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
gir.get_namespace("Gtk", version)
|
return gir.get_namespace("Gtk", self.gtk_version)
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
"Could not find GTK 4 introspection files. Is gobject-introspection installed?",
|
"Could not find GTK 4 introspection files. Is gobject-introspection installed?",
|
||||||
|
@ -52,18 +60,9 @@ class GtkDirective(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gir_namespace(self):
|
|
||||||
# validate the GTK version first to make sure the more specific error
|
|
||||||
# message is emitted
|
|
||||||
self.gtk_version()
|
|
||||||
return gir.get_namespace("Gtk", self.tokens["version"])
|
|
||||||
|
|
||||||
|
|
||||||
def emit_xml(self, xml: XmlEmitter):
|
def emit_xml(self, xml: XmlEmitter):
|
||||||
xml.put_self_closing("requires", lib="gtk", version=self.tokens["version"])
|
xml.put_self_closing("requires", lib="gtk", version=self.tokens["version"])
|
||||||
|
|
||||||
|
|
||||||
class Import(AstNode):
|
class Import(AstNode):
|
||||||
grammar = Statement(
|
grammar = Statement(
|
||||||
"using",
|
"using",
|
||||||
|
|
|
@ -19,8 +19,9 @@
|
||||||
|
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
|
from functools import cached_property
|
||||||
from .common import *
|
from .common import *
|
||||||
from ..gir import Class, Interface
|
from ..gir import GirClass, Class, Interface
|
||||||
|
|
||||||
|
|
||||||
class TypeName(AstNode):
|
class TypeName(AstNode):
|
||||||
|
@ -53,18 +54,16 @@ class TypeName(AstNode):
|
||||||
if not self.tokens["ignore_gir"]:
|
if not self.tokens["ignore_gir"]:
|
||||||
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
|
return self.root.gir.namespaces.get(self.tokens["namespace"] or "Gtk")
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def gir_type(self) -> T.Optional[gir.Class]:
|
def gir_type(self) -> T.Optional[gir.Class]:
|
||||||
if self.tokens["class_name"] and not self.tokens["ignore_gir"]:
|
if self.tokens["ignore_gir"]:
|
||||||
|
return gir.PartialClass(self.tokens["class_name"])
|
||||||
|
else:
|
||||||
return self.root.gir.get_type(self.tokens["class_name"], self.tokens["namespace"])
|
return self.root.gir.get_type(self.tokens["class_name"], self.tokens["namespace"])
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def glib_type_name(self) -> str:
|
def glib_type_name(self) -> str:
|
||||||
if gir_type := self.gir_type:
|
return self.gir_type.glib_type_name
|
||||||
return gir_type.glib_type_name
|
|
||||||
else:
|
|
||||||
return self.tokens["class_name"]
|
|
||||||
|
|
||||||
@docs("namespace")
|
@docs("namespace")
|
||||||
def namespace_docs(self):
|
def namespace_docs(self):
|
||||||
|
@ -83,7 +82,7 @@ class TypeName(AstNode):
|
||||||
class ClassName(TypeName):
|
class ClassName(TypeName):
|
||||||
@validate("namespace", "class_name")
|
@validate("namespace", "class_name")
|
||||||
def gir_class_exists(self):
|
def gir_class_exists(self):
|
||||||
if self.gir_type is not None and not isinstance(self.gir_type, Class):
|
if self.gir_type is not None and not isinstance(self.gir_type, GirClass):
|
||||||
if isinstance(self.gir_type, Interface):
|
if isinstance(self.gir_type, Interface):
|
||||||
raise CompileError(f"{self.gir_type.full_name} is an interface, not a class")
|
raise CompileError(f"{self.gir_type.full_name} is an interface, not a class")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -42,7 +42,8 @@ class UI(AstNode, Scope):
|
||||||
self._gir_errors = []
|
self._gir_errors = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
gir_ctx.add_namespace(self.children[GtkDirective][0].gir_namespace)
|
if ns := self.children[GtkDirective][0].gir_namespace:
|
||||||
|
gir_ctx.add_namespace(ns)
|
||||||
except CompileError as e:
|
except CompileError as e:
|
||||||
self._gir_errors.append(e)
|
self._gir_errors.append(e)
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ class UI(AstNode, Scope):
|
||||||
xml.end_tag()
|
xml.end_tag()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: ScopeVariable(id, obj.gir_class, lambda xml, id=id: emit_xml(xml, id), obj.glib_type_name)
|
id: ScopeVariable(id, obj.gir_class, lambda xml, id=id: emit_xml(xml, id))
|
||||||
for id, obj in self.objects_by_id.items()
|
for id, obj in self.objects_by_id.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
5,32,9,Gtk.Widget does not have a property called something
|
5,32,9,Class Gtk.Widget does not have a property called something
|
||||||
6,43,5,Type int does not have properties
|
6,43,5,Type int does not have properties
|
||||||
7,17,7,Could not find object with ID 'nothing'
|
7,17,7,Could not find object with ID 'nothing'
|
|
@ -1 +1 @@
|
||||||
4,3,19,Class Gtk.Label does not contain a property called not-a-real-property
|
4,3,19,Class Gtk.Label does not have a property called not-a-real-property
|
||||||
|
|
5
tests/samples/template_binding.blp
Normal file
5
tests/samples/template_binding.blp
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
|
||||||
|
template MyWidget {
|
||||||
|
label: bind MyWidget.other-label;
|
||||||
|
}
|
11
tests/samples/template_binding.ui
Normal file
11
tests/samples/template_binding.ui
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk" version="4.0"/>
|
||||||
|
<template class="MyWidget">
|
||||||
|
<binding name="label">
|
||||||
|
<lookup name="other-label" type="MyWidget">
|
||||||
|
<constant>MyWidget</constant>
|
||||||
|
</lookup>
|
||||||
|
</binding>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -90,6 +90,9 @@ class TestSamples(unittest.TestCase):
|
||||||
raise MultipleErrors(warnings)
|
raise MultipleErrors(warnings)
|
||||||
except PrintableError as e:
|
except PrintableError as e:
|
||||||
def error_str(error):
|
def error_str(error):
|
||||||
|
if error.start is None:
|
||||||
|
raise Exception("Error start/end range was never set")
|
||||||
|
|
||||||
line, col = utils.idx_to_pos(error.start + 1, blueprint)
|
line, col = utils.idx_to_pos(error.start + 1, blueprint)
|
||||||
len = error.end - error.start
|
len = error.end - error.start
|
||||||
return ",".join([str(line + 1), str(col), str(len), error.message])
|
return ",".join([str(line + 1), str(col), str(len), error.message])
|
||||||
|
@ -157,6 +160,7 @@ class TestSamples(unittest.TestCase):
|
||||||
self.assert_sample("strings")
|
self.assert_sample("strings")
|
||||||
self.assert_sample("style")
|
self.assert_sample("style")
|
||||||
self.assert_sample("template")
|
self.assert_sample("template")
|
||||||
|
self.assert_sample("template_binding")
|
||||||
self.assert_sample("template_no_parent")
|
self.assert_sample("template_no_parent")
|
||||||
self.assert_sample("translated")
|
self.assert_sample("translated")
|
||||||
self.assert_sample("uint")
|
self.assert_sample("uint")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue