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 .values import (
ArrayValue,
ExprLiteral,
ExprValue,
Flag,
Flags,
IdentLiteral,

View file

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

View file

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

View file

@ -143,12 +143,13 @@ 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,
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]:

View file

@ -23,7 +23,7 @@ from blueprintcompiler.gir import ArrayType
from blueprintcompiler.lsp_utils import SemanticToken
from .common import *
from .contexts import ExprLiteralCtx, ScopeCtx, ValueTypeCtx
from .contexts import ExprValueCtx, ScopeCtx, ValueTypeCtx
from .expression import Expression
from .gobject_object import Object
from .types import TypeName
@ -112,35 +112,6 @@ 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")
@ -350,7 +321,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[ExprLiteralCtx]:
if not self.context[ExprValueCtx]:
raise CompileError(
'"item" can only be used in an expression literal'
)
@ -410,7 +381,6 @@ class IdentLiteral(AstNode):
class Literal(AstNode):
grammar = AnyOf(
TypeLiteral,
ExprLiteral,
QuotedLiteral,
NumberLiteral,
IdentLiteral,
@ -419,7 +389,7 @@ class Literal(AstNode):
@property
def value(
self,
) -> T.Union[TypeLiteral, ExprLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]:
) -> T.Union[TypeLiteral, QuotedLiteral, NumberLiteral, IdentLiteral]:
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):
grammar = AnyOf(Translated, Flags, Literal)

View file

@ -134,6 +134,11 @@ 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)
@ -205,8 +210,6 @@ 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))
@ -220,12 +223,6 @@ 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 ExprLiteral:
.. _Syntax ExprValue:
Expression Literals
-------------------
Expression Values
-----------------
.. 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.

View file

@ -58,7 +58,7 @@ Properties
.. 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.

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",
"expr_closure",
"expr_closure_args",
"expr_literal_closure",
"expr_value_closure",
"parseable",
"signal",
"signal_not_swapped",