mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
Add ScopeCtx instead of root.objects_by_id
This allows us to introduce new scopes, such as in GtkBuilderListItemFactory templates.
This commit is contained in:
parent
ff5fff7f4b
commit
ec844b10ca
8 changed files with 65 additions and 40 deletions
|
@ -33,7 +33,7 @@ class Children:
|
||||||
def __init__(self, children):
|
def __init__(self, children):
|
||||||
self._children = children
|
self._children = children
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> T.Iterator["AstNode"]:
|
||||||
return iter(self._children)
|
return iter(self._children)
|
||||||
|
|
||||||
@T.overload
|
@T.overload
|
||||||
|
@ -175,11 +175,6 @@ class AstNode:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
yield from child.get_semantic_tokens()
|
yield from child.get_semantic_tokens()
|
||||||
|
|
||||||
def iterate_children_recursive(self) -> T.Iterator["AstNode"]:
|
|
||||||
yield self
|
|
||||||
for child in self.children:
|
|
||||||
yield from child.iterate_children_recursive()
|
|
||||||
|
|
||||||
def validate_unique_in_parent(self, error, check=None):
|
def validate_unique_in_parent(self, error, check=None):
|
||||||
for child in self.parent.children:
|
for child in self.parent.children:
|
||||||
if child is self:
|
if child is self:
|
||||||
|
@ -270,7 +265,12 @@ class Context:
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
return self.func(instance)
|
if ctx := getattr(instance, "_context_" + self.type.__name__, None):
|
||||||
|
return ctx
|
||||||
|
else:
|
||||||
|
ctx = self.func(instance)
|
||||||
|
setattr(instance, "_context_" + self.type.__name__, ctx)
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
def context(type: T.Type[TCtx]):
|
def context(type: T.Type[TCtx]):
|
||||||
|
|
|
@ -19,9 +19,44 @@
|
||||||
|
|
||||||
import typing as T
|
import typing as T
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
|
from .gobject_object import Object
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ValueTypeCtx:
|
class ValueTypeCtx:
|
||||||
value_type: T.Optional[GirType]
|
value_type: T.Optional[GirType]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScopeCtx:
|
||||||
|
node: AstNode
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def objects(self) -> T.Dict[str, Object]:
|
||||||
|
return {
|
||||||
|
obj.tokens["id"]: obj
|
||||||
|
for obj in self._iter_recursive(self.node)
|
||||||
|
if obj.tokens["id"] is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
def validate_unique_ids(self) -> None:
|
||||||
|
passed = {}
|
||||||
|
for obj in self._iter_recursive(self.node):
|
||||||
|
if obj.tokens["id"] is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if obj.tokens["id"] in passed:
|
||||||
|
token = obj.group.tokens["id"]
|
||||||
|
raise CompileError(
|
||||||
|
f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end
|
||||||
|
)
|
||||||
|
passed[obj.tokens["id"]] = obj
|
||||||
|
|
||||||
|
def _iter_recursive(self, node: AstNode):
|
||||||
|
yield node
|
||||||
|
for child in node.children:
|
||||||
|
if child.context[ScopeCtx] is self:
|
||||||
|
yield from self._iter_recursive(child)
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
from .types import TypeName
|
from .types import TypeName
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class LiteralExpr(Expr):
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isinstance(self.literal.value, IdentLiteral)
|
isinstance(self.literal.value, IdentLiteral)
|
||||||
and self.literal.value.ident in self.root.objects_by_id
|
and self.literal.value.ident in self.context[ScopeCtx].objects
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -105,7 +105,7 @@ class LiteralExpr(Expr):
|
||||||
from .values import IdentLiteral
|
from .values import IdentLiteral
|
||||||
|
|
||||||
if isinstance(self.literal.value, IdentLiteral):
|
if isinstance(self.literal.value, IdentLiteral):
|
||||||
if object := self.root.objects_by_id.get(self.literal.value.ident):
|
if object := self.context[ScopeCtx].objects.get(self.literal.value.ident):
|
||||||
return not isinstance(object, Template)
|
return not isinstance(object, Template)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
|
from .contexts import ScopeCtx
|
||||||
from .common import *
|
from .common import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +113,7 @@ class Signal(AstNode):
|
||||||
if object_id is None:
|
if object_id is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.root.objects_by_id.get(object_id) is None:
|
if self.context[ScopeCtx].objects.get(object_id) is None:
|
||||||
raise CompileError(f"Could not find object with ID '{object_id}'")
|
raise CompileError(f"Could not find object with ID '{object_id}'")
|
||||||
|
|
||||||
@docs("name")
|
@docs("name")
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
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 ScopeCtx
|
||||||
|
|
||||||
|
|
||||||
class Widget(AstNode):
|
class Widget(AstNode):
|
||||||
|
@ -27,12 +28,15 @@ class Widget(AstNode):
|
||||||
|
|
||||||
@validate("name")
|
@validate("name")
|
||||||
def obj_widget(self):
|
def obj_widget(self):
|
||||||
object = self.root.objects_by_id.get(self.tokens["name"])
|
object = self.context[ScopeCtx].objects.get(self.tokens["name"])
|
||||||
type = self.root.gir.get_type("Widget", "Gtk")
|
type = self.root.gir.get_type("Widget", "Gtk")
|
||||||
if object is None:
|
if object is None:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"Could not find object with ID {self.tokens['name']}",
|
f"Could not find object with ID {self.tokens['name']}",
|
||||||
did_you_mean=(self.tokens["name"], self.root.objects_by_id.keys()),
|
did_you_mean=(
|
||||||
|
self.tokens["name"],
|
||||||
|
self.context[ScopeCtx].objects.keys(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
elif object.gir_class and not object.gir_class.assignable_to(type):
|
elif object.gir_class and not object.gir_class.assignable_to(type):
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
from .common import *
|
from .common import *
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ScopeCtx
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ class PropertyBinding(AstNode):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_obj(self) -> T.Optional[Object]:
|
def source_obj(self) -> T.Optional[Object]:
|
||||||
return self.root.objects_by_id.get(self.source)
|
return self.context[ScopeCtx].objects.get(self.source)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def property_name(self) -> str:
|
def property_name(self) -> str:
|
||||||
|
@ -98,7 +98,7 @@ class PropertyBinding(AstNode):
|
||||||
if self.source_obj is None:
|
if self.source_obj is None:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"Could not find object with ID {self.source}",
|
f"Could not find object with ID {self.source}",
|
||||||
did_you_mean=(self.source, self.root.objects_by_id.keys()),
|
did_you_mean=(self.source, self.context[ScopeCtx].objects.keys()),
|
||||||
)
|
)
|
||||||
|
|
||||||
@validate("property")
|
@validate("property")
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
from functools import cached_property
|
|
||||||
|
|
||||||
from .. import gir
|
from .. import gir
|
||||||
from .imports import GtkDirective, Import
|
from .imports import GtkDirective, Import
|
||||||
from .gtkbuilder_template import Template
|
from .gtkbuilder_template import Template
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .gtk_menu import menu, Menu
|
from .gtk_menu import menu, Menu
|
||||||
from .common import *
|
from .common import *
|
||||||
|
from .contexts import ScopeCtx
|
||||||
|
|
||||||
|
|
||||||
class UI(AstNode):
|
class UI(AstNode):
|
||||||
|
@ -85,13 +84,9 @@ class UI(AstNode):
|
||||||
or isinstance(child, Menu)
|
or isinstance(child, Menu)
|
||||||
]
|
]
|
||||||
|
|
||||||
@cached_property
|
@context(ScopeCtx)
|
||||||
def objects_by_id(self):
|
def scope_ctx(self) -> ScopeCtx:
|
||||||
return {
|
return ScopeCtx(node=self)
|
||||||
obj.tokens["id"]: obj
|
|
||||||
for obj in self.iterate_children_recursive()
|
|
||||||
if obj.tokens["id"] is not None
|
|
||||||
}
|
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def gir_errors(self):
|
def gir_errors(self):
|
||||||
|
@ -102,14 +97,4 @@ class UI(AstNode):
|
||||||
|
|
||||||
@validate()
|
@validate()
|
||||||
def unique_ids(self):
|
def unique_ids(self):
|
||||||
passed = {}
|
self.context[ScopeCtx].validate_unique_ids()
|
||||||
for obj in self.iterate_children_recursive():
|
|
||||||
if obj.tokens["id"] is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if obj.tokens["id"] in passed:
|
|
||||||
token = obj.group.tokens["id"]
|
|
||||||
raise CompileError(
|
|
||||||
f"Duplicate object ID '{obj.tokens['id']}'", token.start, token.end
|
|
||||||
)
|
|
||||||
passed[obj.tokens["id"]] = obj
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ from .types import TypeName
|
||||||
from .property_binding import PropertyBinding
|
from .property_binding import PropertyBinding
|
||||||
from .binding import Binding
|
from .binding import Binding
|
||||||
from .gobject_object import Object
|
from .gobject_object import Object
|
||||||
from .contexts import ValueTypeCtx
|
from .contexts import ScopeCtx, ValueTypeCtx
|
||||||
|
|
||||||
|
|
||||||
class TranslatedWithoutContext(AstNode):
|
class TranslatedWithoutContext(AstNode):
|
||||||
|
@ -274,7 +274,7 @@ class IdentLiteral(AstNode):
|
||||||
return expected_type
|
return expected_type
|
||||||
elif self.ident in ["true", "false"]:
|
elif self.ident in ["true", "false"]:
|
||||||
return gir.BoolType()
|
return gir.BoolType()
|
||||||
elif object := self.root.objects_by_id.get(self.ident):
|
elif object := self.context[ScopeCtx].objects.get(self.ident):
|
||||||
return object.gir_class
|
return object.gir_class
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -294,11 +294,11 @@ class IdentLiteral(AstNode):
|
||||||
)
|
)
|
||||||
|
|
||||||
elif expected_type is not None:
|
elif expected_type is not None:
|
||||||
object = self.root.objects_by_id.get(self.ident)
|
object = self.context[ScopeCtx].objects.get(self.ident)
|
||||||
if object is None:
|
if object is None:
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
f"Could not find object with ID {self.ident}",
|
f"Could not find object with ID {self.ident}",
|
||||||
did_you_mean=(self.ident, self.root.objects_by_id.keys()),
|
did_you_mean=(self.ident, self.context[ScopeCtx].objects.keys()),
|
||||||
)
|
)
|
||||||
elif object.gir_class and not object.gir_class.assignable_to(expected_type):
|
elif object.gir_class and not object.gir_class.assignable_to(expected_type):
|
||||||
raise CompileError(
|
raise CompileError(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue