Compare commits

..

2 commits

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

See merge request jwestman/blueprint-compiler!227
2024-12-24 18:55:37 +00:00
James Westman
ffb125a725
language: Add expression literals
Add expression literals, so you can set properties of type
Gtk.Expression.
2024-12-24 12:54:23 -06:00
23 changed files with 54 additions and 57 deletions

View file

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

View file

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

View file

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

View file

@ -143,13 +143,12 @@ class Signal(AstNode):
@property
def document_symbol(self) -> DocumentSymbol:
detail = self.ranges["detail_start", "detail_end"]
return DocumentSymbol(
self.full_name,
SymbolKind.Event,
self.range,
self.group.tokens["name"].range,
detail.text if detail is not None else None,
self.ranges["detail_start", "detail_end"].text,
)
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 .common import *
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
from .contexts import ExprLiteralCtx, ScopeCtx, ValueTypeCtx
from .expression import Expression
from .gobject_object import Object
from .types import TypeName
@ -112,6 +112,35 @@ class TypeLiteral(AstNode):
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):
grammar = UseQuoted("value")
@ -321,7 +350,7 @@ class IdentLiteral(AstNode):
if not self.context[ValueTypeCtx].allow_null:
raise CompileError("null is not permitted here")
elif self.ident == "item":
if not self.context[ExprValueCtx]:
if not self.context[ExprLiteralCtx]:
raise CompileError(
'"item" can only be used in an expression literal'
)
@ -381,6 +410,7 @@ class IdentLiteral(AstNode):
class Literal(AstNode):
grammar = AnyOf(
TypeLiteral,
ExprLiteral,
QuotedLiteral,
NumberLiteral,
IdentLiteral,
@ -389,7 +419,7 @@ class Literal(AstNode):
@property
def value(
self,
) -> T.Union[TypeLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]:
) -> T.Union[TypeLiteral, ExprLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]:
return self.children[0]
@ -413,35 +443,6 @@ 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):
grammar = AnyOf(Translated, Flags, Literal)

View file

@ -134,11 +134,6 @@ class XmlOutput(OutputFormat):
self._emit_expression(value.expression, xml)
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):
xml.start_tag("property", **props)
self._emit_object(value.object, xml)
@ -210,6 +205,8 @@ class XmlOutput(OutputFormat):
xml.put_text(self._object_id(value, value.ident))
elif isinstance(value, TypeLiteral):
xml.put_text(value.type_name.glib_type_name)
elif isinstance(value, ExprLiteral):
self._emit_expression(value.expression, xml)
else:
if isinstance(value.value, float) and value.value == int(value.value):
xml.put_text(int(value.value))
@ -223,6 +220,12 @@ class XmlOutput(OutputFormat):
xml.put_text(
"|".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:
raise CompilerBugError()

View file

@ -89,14 +89,14 @@ Example
// Cast the result of the closure so blueprint knows it's a string
label: bind $format_bytes(template.file-size) as <string>
.. _Syntax ExprValue:
.. _Syntax ExprLiteral:
Expression Values
-----------------
Expression Literals
-------------------
.. rst-class:: grammar-block
ExprValue = 'expr' :ref:`Expression<Syntax Expression>`
ExprLiteral = '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.

View file

@ -58,7 +58,7 @@ Properties
.. rst-class:: grammar-block
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :ref:`ExprValue<Syntax ExprValue>` | :ref:`ObjectValue<Syntax ObjectValue>` | :ref:`Value<Syntax Value>` ) ';'
Property = <name::ref:`IDENT<Syntax IDENT>`> ':' ( :ref:`Binding<Syntax Binding>` | :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.

View file

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

View file

@ -1,2 +0,0 @@
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",
"expr_closure",
"expr_closure_args",
"expr_value_closure",
"expr_literal_closure",
"parseable",
"signal",
"signal_not_swapped",