mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Change template syntax
Templates now use a TypeName instead of an identifier, which makes it clearer that it's an extern symbol (or that it's a Gtk.ListItem).
This commit is contained in:
parent
aebf8be278
commit
04509e4b2e
31 changed files with 175 additions and 55 deletions
|
@ -57,6 +57,7 @@ class DecompileCtx:
|
|||
self._indent: int = 0
|
||||
self._blocks_need_end: T.List[str] = []
|
||||
self._last_line_type: LineType = LineType.NONE
|
||||
self.template_class: T.Optional[str] = None
|
||||
|
||||
self.gir.add_namespace(get_namespace("Gtk", "4.0"))
|
||||
|
||||
|
@ -158,6 +159,8 @@ class DecompileCtx:
|
|||
)
|
||||
):
|
||||
self.print(f'{name}: "{escape_quote(value)}";')
|
||||
elif value == self.template_class:
|
||||
self.print(f"{name}: template;")
|
||||
elif type.assignable_to(
|
||||
self.gir.namespaces["Gtk"].lookup_type("GObject.Object")
|
||||
):
|
||||
|
|
|
@ -516,7 +516,7 @@ class Class(GirNode, GirType):
|
|||
|
||||
|
||||
class TemplateType(GirType):
|
||||
def __init__(self, name: str, parent: T.Optional[Class]):
|
||||
def __init__(self, name: str, parent: T.Optional[GirType]):
|
||||
self._name = name
|
||||
self.parent = parent
|
||||
|
||||
|
@ -534,14 +534,14 @@ class TemplateType(GirType):
|
|||
|
||||
@cached_property
|
||||
def properties(self) -> T.Mapping[str, Property]:
|
||||
if self.parent is None or isinstance(self.parent, ExternType):
|
||||
if not (isinstance(self.parent, Class) or isinstance(self.parent, Interface)):
|
||||
return {}
|
||||
else:
|
||||
return self.parent.properties
|
||||
|
||||
@cached_property
|
||||
def signals(self) -> T.Mapping[str, Signal]:
|
||||
if self.parent is None or isinstance(self.parent, ExternType):
|
||||
if not (isinstance(self.parent, Class) or isinstance(self.parent, Interface)):
|
||||
return {}
|
||||
else:
|
||||
return self.parent.signals
|
||||
|
|
|
@ -23,6 +23,7 @@ from functools import cached_property
|
|||
|
||||
from .common import *
|
||||
from .gobject_object import Object
|
||||
from .gtkbuilder_template import Template
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -51,9 +52,12 @@ class ScopeCtx:
|
|||
|
||||
if obj.tokens["id"] in passed:
|
||||
token = obj.group.tokens["id"]
|
||||
raise CompileError(
|
||||
f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end
|
||||
)
|
||||
if not isinstance(obj, Template):
|
||||
raise CompileError(
|
||||
f"Duplicate object ID '{obj.tokens['id']}'",
|
||||
token.start,
|
||||
token.end,
|
||||
)
|
||||
passed[obj.tokens["id"]] = obj
|
||||
|
||||
def _iter_recursive(self, node: AstNode):
|
||||
|
|
|
@ -85,9 +85,9 @@ class LiteralExpr(ExprBase):
|
|||
def is_object(self) -> bool:
|
||||
from .values import IdentLiteral
|
||||
|
||||
return (
|
||||
isinstance(self.literal.value, IdentLiteral)
|
||||
and self.literal.value.ident in self.context[ScopeCtx].objects
|
||||
return isinstance(self.literal.value, IdentLiteral) and (
|
||||
self.literal.value.ident in self.context[ScopeCtx].objects
|
||||
or self.root.is_legacy_template(self.literal.value.ident)
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -49,7 +49,7 @@ class Object(AstNode):
|
|||
return self.tokens["id"]
|
||||
|
||||
@property
|
||||
def class_name(self) -> T.Optional[ClassName]:
|
||||
def class_name(self) -> ClassName:
|
||||
return self.children[ClassName][0]
|
||||
|
||||
@property
|
||||
|
@ -78,7 +78,9 @@ class Object(AstNode):
|
|||
|
||||
@validate("id")
|
||||
def object_id_not_reserved(self):
|
||||
if self.id in RESERVED_IDS:
|
||||
from .gtkbuilder_template import Template
|
||||
|
||||
if not isinstance(self, Template) and self.id in RESERVED_IDS:
|
||||
raise CompileWarning(f"{self.id} may be a confusing object ID")
|
||||
|
||||
|
||||
|
|
|
@ -19,15 +19,18 @@
|
|||
|
||||
import typing as T
|
||||
|
||||
from blueprintcompiler.language.common import GirType
|
||||
|
||||
from .gobject_object import Object, ObjectContent
|
||||
from .common import *
|
||||
from .types import ClassName
|
||||
from ..gir import TemplateType
|
||||
from .types import ClassName, TemplateClassName
|
||||
|
||||
|
||||
class Template(Object):
|
||||
grammar = [
|
||||
"template",
|
||||
UseIdent("id").expected("template class name"),
|
||||
UseExact("id", "template"),
|
||||
to_parse_node(TemplateClassName).expected("template type"),
|
||||
Optional(
|
||||
[
|
||||
Match(":"),
|
||||
|
@ -39,21 +42,29 @@ class Template(Object):
|
|||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return self.tokens["id"]
|
||||
return "template"
|
||||
|
||||
@property
|
||||
def class_name(self) -> T.Optional[ClassName]:
|
||||
if len(self.children[ClassName]):
|
||||
return self.children[ClassName][0]
|
||||
def gir_class(self) -> GirType:
|
||||
if isinstance(self.class_name.gir_type, ExternType):
|
||||
if gir := self.parent_type:
|
||||
return TemplateType(self.class_name.gir_type.full_name, gir.gir_type)
|
||||
return self.class_name.gir_type
|
||||
|
||||
@property
|
||||
def parent_type(self) -> T.Optional[ClassName]:
|
||||
if len(self.children[ClassName]) == 2:
|
||||
return self.children[ClassName][1]
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def gir_class(self):
|
||||
if self.class_name is None:
|
||||
return gir.TemplateType(self.id, None)
|
||||
else:
|
||||
return gir.TemplateType(self.id, self.class_name.gir_type)
|
||||
@validate()
|
||||
def parent_only_if_extern(self):
|
||||
if not isinstance(self.class_name.gir_type, ExternType):
|
||||
if self.parent_type is not None:
|
||||
raise CompileError(
|
||||
"Parent type may only be specified if the template type is extern"
|
||||
)
|
||||
|
||||
@validate("id")
|
||||
def unique_in_parent(self):
|
||||
|
@ -63,10 +74,15 @@ class Template(Object):
|
|||
|
||||
|
||||
@decompiler("template")
|
||||
def decompile_template(ctx: DecompileCtx, gir, klass, parent="Widget"):
|
||||
gir_class = ctx.type_by_cname(parent)
|
||||
if gir_class is None:
|
||||
ctx.print(f"template {klass} : .{parent} {{")
|
||||
else:
|
||||
ctx.print(f"template {klass} : {decompile.full_name(gir_class)} {{")
|
||||
return gir_class
|
||||
def decompile_template(ctx: DecompileCtx, gir, klass, parent=None):
|
||||
def class_name(cname: str) -> str:
|
||||
if gir := ctx.type_by_cname(cname):
|
||||
return decompile.full_name(gir)
|
||||
else:
|
||||
return "$" + cname
|
||||
|
||||
ctx.print(f"template {class_name(klass)} : {class_name(parent)} {{")
|
||||
|
||||
ctx.template_class = klass
|
||||
|
||||
return ctx.type_by_cname(klass) or ctx.type_by_cname(parent)
|
||||
|
|
|
@ -70,6 +70,8 @@ class PropertyBinding(AstNode):
|
|||
|
||||
@property
|
||||
def source_obj(self) -> T.Optional[Object]:
|
||||
if self.root.is_legacy_template(self.source):
|
||||
return self.root.template
|
||||
return self.context[ScopeCtx].objects.get(self.source)
|
||||
|
||||
@property
|
||||
|
@ -127,3 +129,11 @@ class PropertyBinding(AstNode):
|
|||
"Use 'bind-property', introduced in blueprint 0.8.0, to use binding flags",
|
||||
actions=[CodeAction("Use 'bind-property'", "bind-property")],
|
||||
)
|
||||
|
||||
@validate("source")
|
||||
def legacy_template(self):
|
||||
if self.root.is_legacy_template(self.source):
|
||||
raise UpgradeWarning(
|
||||
"Use 'template' instead of the class name (introduced in 0.8.0)",
|
||||
actions=[CodeAction("Use 'template'", "template")],
|
||||
)
|
||||
|
|
|
@ -122,3 +122,42 @@ class ConcreteClassName(ClassName):
|
|||
f"{self.gir_type.full_name} can't be instantiated because it's abstract",
|
||||
hints=[f"did you mean to use a subclass of {self.gir_type.full_name}?"],
|
||||
)
|
||||
|
||||
|
||||
class TemplateClassName(ClassName):
|
||||
"""Handles the special case of a template type. The old syntax uses an identifier,
|
||||
which is ambiguous with the new syntax. So this class displays an appropriate
|
||||
upgrade warning instead of a class not found error."""
|
||||
|
||||
@property
|
||||
def is_legacy(self):
|
||||
return (
|
||||
self.tokens["extern"] is None
|
||||
and self.tokens["namespace"] is None
|
||||
and self.root.gir.get_type(self.tokens["class_name"], "Gtk") is None
|
||||
)
|
||||
|
||||
@property
|
||||
def gir_type(self) -> gir.GirType:
|
||||
if self.is_legacy:
|
||||
return gir.ExternType(self.tokens["class_name"])
|
||||
else:
|
||||
return super().gir_type
|
||||
|
||||
@validate("class_name")
|
||||
def type_exists(self):
|
||||
if self.is_legacy:
|
||||
if type := self.root.gir.get_type_by_cname(self.tokens["class_name"]):
|
||||
replacement = type.full_name
|
||||
else:
|
||||
replacement = "$" + self.tokens["class_name"]
|
||||
|
||||
raise UpgradeWarning(
|
||||
"Use type syntax here (introduced in blueprint 0.8.0)",
|
||||
actions=[CodeAction("Use type syntax", replace_with=replacement)],
|
||||
)
|
||||
|
||||
if not self.tokens["extern"] and self.gir_ns is not None:
|
||||
self.root.gir.validate_type(
|
||||
self.tokens["class_name"], self.tokens["namespace"]
|
||||
)
|
||||
|
|
|
@ -43,7 +43,7 @@ class UI(AstNode):
|
|||
]
|
||||
|
||||
@property
|
||||
def gir(self):
|
||||
def gir(self) -> gir.GirContext:
|
||||
gir_ctx = gir.GirContext()
|
||||
self._gir_errors = []
|
||||
|
||||
|
@ -84,6 +84,20 @@ class UI(AstNode):
|
|||
or isinstance(child, Menu)
|
||||
]
|
||||
|
||||
@property
|
||||
def template(self) -> T.Optional[Template]:
|
||||
if len(self.children[Template]):
|
||||
return self.children[Template][0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def is_legacy_template(self, id: str) -> bool:
|
||||
return (
|
||||
id not in self.context[ScopeCtx].objects
|
||||
and self.template is not None
|
||||
and self.template.class_name.glib_type_name == id
|
||||
)
|
||||
|
||||
@context(ScopeCtx)
|
||||
def scope_ctx(self) -> ScopeCtx:
|
||||
return ScopeCtx(node=self)
|
||||
|
|
|
@ -260,6 +260,8 @@ class IdentLiteral(AstNode):
|
|||
return gir.BoolType()
|
||||
elif object := self.context[ScopeCtx].objects.get(self.ident):
|
||||
return object.gir_class
|
||||
elif self.root.is_legacy_template(self.ident):
|
||||
return self.root.template.class_name.gir_type
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -277,6 +279,12 @@ class IdentLiteral(AstNode):
|
|||
did_you_mean=(self.ident, list(expected_type.members.keys())),
|
||||
)
|
||||
|
||||
elif self.root.is_legacy_template(self.ident):
|
||||
raise UpgradeWarning(
|
||||
"Use 'template' instead of the class name (introduced in 0.8.0)",
|
||||
actions=[CodeAction("Use 'template'", "template")],
|
||||
)
|
||||
|
||||
elif expected_type is not None:
|
||||
object = self.context[ScopeCtx].objects.get(self.ident)
|
||||
if object is None:
|
||||
|
|
|
@ -8,6 +8,7 @@ from .xml_emitter import XmlEmitter
|
|||
class XmlOutput(OutputFormat):
|
||||
def emit(self, ui: UI) -> str:
|
||||
xml = XmlEmitter()
|
||||
self._ui = ui
|
||||
self._emit_ui(ui, xml)
|
||||
return xml.result
|
||||
|
||||
|
@ -32,7 +33,9 @@ class XmlOutput(OutputFormat):
|
|||
xml.put_self_closing("requires", lib="gtk", version=gtk.gir_namespace.version)
|
||||
|
||||
def _emit_template(self, template: Template, xml: XmlEmitter):
|
||||
xml.start_tag("template", **{"class": template.id}, parent=template.class_name)
|
||||
xml.start_tag(
|
||||
"template", **{"class": template.gir_class}, parent=template.parent_type
|
||||
)
|
||||
self._emit_object_or_template(template, xml)
|
||||
xml.end_tag()
|
||||
|
||||
|
@ -188,6 +191,8 @@ class XmlOutput(OutputFormat):
|
|||
xml.put_text(value.ident)
|
||||
elif isinstance(value_type, gir.Enumeration):
|
||||
xml.put_text(str(value_type.members[value.ident].value))
|
||||
elif value.ident == "template" and self._ui.template is not None:
|
||||
xml.put_text(self._ui.template.gir_class.glib_type_name)
|
||||
else:
|
||||
xml.put_text(value.ident)
|
||||
elif isinstance(value, TypeLiteral):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue