Compare commits

..

3 commits

Author SHA1 Message Date
James Westman
a20d21f8fb Merge branch 'expr-literal' into 'main'
language: Add expression literals

See merge request jwestman/blueprint-compiler!227
2024-12-26 23:11:47 +00:00
James Westman
d6f4b88d35
lsp: Fix crash on incomplete detailed signal 2024-12-25 10:31:35 -06:00
James Westman
02344139c2
language: Add expression literals
Add expression literals, so you can set properties of type
Gtk.Expression.
2024-12-24 21:12:38 -06:00
23 changed files with 57 additions and 54 deletions

View file

@ -41,7 +41,7 @@ from .types import ClassName
from .ui import UI from .ui import UI
from .values import ( from .values import (
ArrayValue, ArrayValue,
ExprLiteral, ExprValue,
Flag, Flag,
Flags, Flags,
IdentLiteral, IdentLiteral,

View file

@ -82,6 +82,6 @@ class ScopeCtx:
@dataclass @dataclass
class ExprLiteralCtx: class ExprValueCtx:
"""Indicates that the context is an expression literal, where the """Indicates that the context is an expression literal, where the
"item" keyword may be used.""" "item" keyword may be used."""

View file

@ -21,13 +21,12 @@
from .binding import Binding from .binding import Binding
from .common import * from .common import *
from .contexts import ValueTypeCtx from .contexts import ValueTypeCtx
from .gtkbuilder_template import Template from .values import ArrayValue, ExprValue, ObjectValue, Value
from .values import ArrayValue, ObjectValue, Value
class Property(AstNode): class Property(AstNode):
grammar = Statement( grammar = Statement(
UseIdent("name"), ":", AnyOf(Binding, ObjectValue, Value, ArrayValue) UseIdent("name"), ":", AnyOf(Binding, ExprValue, ObjectValue, Value, ArrayValue)
) )
@property @property
@ -35,7 +34,7 @@ class Property(AstNode):
return self.tokens["name"] return self.tokens["name"]
@property @property
def value(self) -> T.Union[Binding, ObjectValue, Value, ArrayValue]: def value(self) -> T.Union[Binding, ExprValue, ObjectValue, Value, ArrayValue]:
return self.children[0] return self.children[0]
@property @property

View file

@ -143,12 +143,13 @@ class Signal(AstNode):
@property @property
def document_symbol(self) -> DocumentSymbol: def document_symbol(self) -> DocumentSymbol:
detail = self.ranges["detail_start", "detail_end"]
return DocumentSymbol( return DocumentSymbol(
self.full_name, self.full_name,
SymbolKind.Event, SymbolKind.Event,
self.range, self.range,
self.group.tokens["name"].range, self.group.tokens["name"].range,
self.ranges["detail_start", "detail_end"].text, detail.text if detail is not None else None,
) )
def get_reference(self, idx: int) -> T.Optional[LocationLink]: def get_reference(self, idx: int) -> T.Optional[LocationLink]:

View file

@ -23,7 +23,7 @@ from blueprintcompiler.gir import ArrayType
from blueprintcompiler.lsp_utils import SemanticToken from blueprintcompiler.lsp_utils import SemanticToken
from .common import * from .common import *
from .contexts import ExprLiteralCtx, ScopeCtx, ValueTypeCtx from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
from .expression import Expression from .expression import Expression
from .gobject_object import Object from .gobject_object import Object
from .types import TypeName from .types import TypeName
@ -112,35 +112,6 @@ class TypeLiteral(AstNode):
return get_docs_section("Syntax TypeLiteral") return get_docs_section("Syntax TypeLiteral")
class ExprLiteral(AstNode):
grammar = [Keyword("expr"), Expression]
@property
def expression(self) -> Expression:
return self.children[Expression][0]
@validate("expr")
def validate_for_type(self) -> None:
expected_type = self.parent.context[ValueTypeCtx].value_type
expr_type = self.root.gir.get_type("Expression", "Gtk")
if expected_type is not None and not expected_type.assignable_to(expr_type):
raise CompileError(
f"Cannot convert Gtk.Expression to {expected_type.full_name}"
)
@docs("expr")
def ref_docs(self):
return get_docs_section("Syntax ExprLiteral")
@context(ExprLiteralCtx)
def expr_literal(self):
return ExprLiteralCtx()
@context(ValueTypeCtx)
def value_type(self):
return ValueTypeCtx(None, must_infer_type=True)
class QuotedLiteral(AstNode): class QuotedLiteral(AstNode):
grammar = UseQuoted("value") grammar = UseQuoted("value")
@ -350,7 +321,7 @@ class IdentLiteral(AstNode):
if not self.context[ValueTypeCtx].allow_null: if not self.context[ValueTypeCtx].allow_null:
raise CompileError("null is not permitted here") raise CompileError("null is not permitted here")
elif self.ident == "item": elif self.ident == "item":
if not self.context[ExprLiteralCtx]: if not self.context[ExprValueCtx]:
raise CompileError( raise CompileError(
'"item" can only be used in an expression literal' '"item" can only be used in an expression literal'
) )
@ -410,7 +381,6 @@ class IdentLiteral(AstNode):
class Literal(AstNode): class Literal(AstNode):
grammar = AnyOf( grammar = AnyOf(
TypeLiteral, TypeLiteral,
ExprLiteral,
QuotedLiteral, QuotedLiteral,
NumberLiteral, NumberLiteral,
IdentLiteral, IdentLiteral,
@ -419,7 +389,7 @@ class Literal(AstNode):
@property @property
def value( def value(
self, self,
) -> T.Union[TypeLiteral, ExprLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]: ) -> T.Union[TypeLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]:
return self.children[0] return self.children[0]
@ -443,6 +413,35 @@ class ObjectValue(AstNode):
) )
class ExprValue(AstNode):
grammar = [Keyword("expr"), Expression]
@property
def expression(self) -> Expression:
return self.children[Expression][0]
@validate("expr")
def validate_for_type(self) -> None:
expected_type = self.parent.context[ValueTypeCtx].value_type
expr_type = self.root.gir.get_type("Expression", "Gtk")
if expected_type is not None and not expected_type.assignable_to(expr_type):
raise CompileError(
f"Cannot convert Gtk.Expression to {expected_type.full_name}"
)
@docs("expr")
def ref_docs(self):
return get_docs_section("Syntax ExprValue")
@context(ExprValueCtx)
def expr_literal(self):
return ExprValueCtx()
@context(ValueTypeCtx)
def value_type(self):
return ValueTypeCtx(None, must_infer_type=True)
class Value(AstNode): class Value(AstNode):
grammar = AnyOf(Translated, Flags, Literal) grammar = AnyOf(Translated, Flags, Literal)

View file

@ -134,6 +134,11 @@ class XmlOutput(OutputFormat):
self._emit_expression(value.expression, xml) self._emit_expression(value.expression, xml)
xml.end_tag() xml.end_tag()
elif isinstance(value, ExprValue):
xml.start_tag("property", **props)
self._emit_expression(value.expression, xml)
xml.end_tag()
elif isinstance(value, ObjectValue): elif isinstance(value, ObjectValue):
xml.start_tag("property", **props) xml.start_tag("property", **props)
self._emit_object(value.object, xml) self._emit_object(value.object, xml)
@ -205,8 +210,6 @@ class XmlOutput(OutputFormat):
xml.put_text(self._object_id(value, value.ident)) xml.put_text(self._object_id(value, value.ident))
elif isinstance(value, TypeLiteral): elif isinstance(value, TypeLiteral):
xml.put_text(value.type_name.glib_type_name) xml.put_text(value.type_name.glib_type_name)
elif isinstance(value, ExprLiteral):
self._emit_expression(value.expression, xml)
else: else:
if isinstance(value.value, float) and value.value == int(value.value): if isinstance(value.value, float) and value.value == int(value.value):
xml.put_text(int(value.value)) xml.put_text(int(value.value))
@ -220,12 +223,6 @@ class XmlOutput(OutputFormat):
xml.put_text( xml.put_text(
"|".join([str(flag.value or flag.name) for flag in value.child.flags]) "|".join([str(flag.value or flag.name) for flag in value.child.flags])
) )
elif isinstance(value.child, Translated):
raise CompilerBugError("translated values must be handled in the parent")
elif isinstance(value.child, TypeLiteral):
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()

View file

@ -89,14 +89,14 @@ Example
// Cast the result of the closure so blueprint knows it's a string // Cast the result of the closure so blueprint knows it's a string
label: bind $format_bytes(template.file-size) as <string> label: bind $format_bytes(template.file-size) as <string>
.. _Syntax ExprLiteral: .. _Syntax ExprValue:
Expression Literals Expression Values
------------------- -----------------
.. rst-class:: grammar-block .. rst-class:: grammar-block
ExprLiteral = 'expr' :ref:`Expression<Syntax Expression>` ExprValue = 'expr' :ref:`Expression<Syntax Expression>`
Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter <https://docs.gtk.org/gtk4/class.BoolFilter.html>`_ has an ``expression`` property of type `Gtk.Expression <https://docs.gtk.org/gtk4/class.Expression.html>`_. This expression is evaluated for every item in a list model to determine whether the item should be filtered. Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter <https://docs.gtk.org/gtk4/class.BoolFilter.html>`_ has an ``expression`` property of type `Gtk.Expression <https://docs.gtk.org/gtk4/class.Expression.html>`_. This expression is evaluated for every item in a list model to determine whether the item should be filtered.

View file

@ -58,7 +58,7 @@ Properties
.. rst-class:: grammar-block .. rst-class:: grammar-block
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';' Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ExprValue<Syntax ExprValue>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container. Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container.

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label {
notify::
}

View file

@ -0,0 +1,2 @@
5,1,0,Expected a signal detail name
4,9,3,Unexpected tokens

View file

@ -198,7 +198,7 @@ class TestSamples(unittest.TestCase):
"adw_breakpoint_template", "adw_breakpoint_template",
"expr_closure", "expr_closure",
"expr_closure_args", "expr_closure_args",
"expr_literal_closure", "expr_value_closure",
"parseable", "parseable",
"signal", "signal",
"signal_not_swapped", "signal_not_swapped",