diff --git a/blueprintcompiler/language/__init__.py b/blueprintcompiler/language/__init__.py index 45163ab..5eb2b60 100644 --- a/blueprintcompiler/language/__init__.py +++ b/blueprintcompiler/language/__init__.py @@ -41,7 +41,7 @@ from .types import ClassName from .ui import UI from .values import ( ArrayValue, - ExprLiteral, + ExprValue, Flag, Flags, IdentLiteral, diff --git a/blueprintcompiler/language/contexts.py b/blueprintcompiler/language/contexts.py index 19023d8..6e26048 100644 --- a/blueprintcompiler/language/contexts.py +++ b/blueprintcompiler/language/contexts.py @@ -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.""" diff --git a/blueprintcompiler/language/gobject_property.py b/blueprintcompiler/language/gobject_property.py index b553909..50a7512 100644 --- a/blueprintcompiler/language/gobject_property.py +++ b/blueprintcompiler/language/gobject_property.py @@ -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 diff --git a/blueprintcompiler/language/gobject_signal.py b/blueprintcompiler/language/gobject_signal.py index b052e3c..9c27b97 100644 --- a/blueprintcompiler/language/gobject_signal.py +++ b/blueprintcompiler/language/gobject_signal.py @@ -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]: diff --git a/blueprintcompiler/language/values.py b/blueprintcompiler/language/values.py index 607f3aa..6d60724 100644 --- a/blueprintcompiler/language/values.py +++ b/blueprintcompiler/language/values.py @@ -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) diff --git a/blueprintcompiler/outputs/xml/__init__.py b/blueprintcompiler/outputs/xml/__init__.py index 59caa14..5c03761 100644 --- a/blueprintcompiler/outputs/xml/__init__.py +++ b/blueprintcompiler/outputs/xml/__init__.py @@ -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() diff --git a/docs/reference/expressions.rst b/docs/reference/expressions.rst index e0358c5..3d523d1 100644 --- a/docs/reference/expressions.rst +++ b/docs/reference/expressions.rst @@ -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 -.. _Syntax ExprLiteral: +.. _Syntax ExprValue: -Expression Literals -------------------- +Expression Values +----------------- .. rst-class:: grammar-block - ExprLiteral = 'expr' :ref:`Expression` + ExprValue = 'expr' :ref:`Expression` Some APIs take *an expression itself*--not its result--as a property value. For example, `Gtk.BoolFilter `_ has an ``expression`` property of type `Gtk.Expression `_. This expression is evaluated for every item in a list model to determine whether the item should be filtered. diff --git a/docs/reference/objects.rst b/docs/reference/objects.rst index 09f5af8..6f76da6 100644 --- a/docs/reference/objects.rst +++ b/docs/reference/objects.rst @@ -58,7 +58,7 @@ Properties .. rst-class:: grammar-block - Property = `> ':' ( :ref:`Binding` | :ref:`ObjectValue` | :ref:`Value` ) ';' + Property = `> ':' ( :ref:`Binding` | :ref:`ExprValue` | :ref:`ObjectValue` | :ref:`Value` ) ';' Properties specify the details of each object, like a label's text, an image's icon name, or the margins on a container. diff --git a/tests/sample_errors/expr_literal_assignment.blp b/tests/sample_errors/expr_value_assignment.blp similarity index 100% rename from tests/sample_errors/expr_literal_assignment.blp rename to tests/sample_errors/expr_value_assignment.blp diff --git a/tests/sample_errors/expr_literal_assignment.err b/tests/sample_errors/expr_value_assignment.err similarity index 100% rename from tests/sample_errors/expr_literal_assignment.err rename to tests/sample_errors/expr_value_assignment.err diff --git a/tests/sample_errors/expr_literal_closure_arg.blp b/tests/sample_errors/expr_value_closure_arg.blp similarity index 100% rename from tests/sample_errors/expr_literal_closure_arg.blp rename to tests/sample_errors/expr_value_closure_arg.blp diff --git a/tests/sample_errors/expr_literal_closure_arg.err b/tests/sample_errors/expr_value_closure_arg.err similarity index 100% rename from tests/sample_errors/expr_literal_closure_arg.err rename to tests/sample_errors/expr_value_closure_arg.err diff --git a/tests/sample_errors/expr_literal_item.blp b/tests/sample_errors/expr_value_item.blp similarity index 100% rename from tests/sample_errors/expr_literal_item.blp rename to tests/sample_errors/expr_value_item.blp diff --git a/tests/sample_errors/expr_literal_item.err b/tests/sample_errors/expr_value_item.err similarity index 100% rename from tests/sample_errors/expr_literal_item.err rename to tests/sample_errors/expr_value_item.err diff --git a/tests/sample_errors/incomplete_signal.blp b/tests/sample_errors/incomplete_signal.blp new file mode 100644 index 0000000..4ec693d --- /dev/null +++ b/tests/sample_errors/incomplete_signal.blp @@ -0,0 +1,5 @@ +using Gtk 4.0; + +Label { + notify:: +} diff --git a/tests/sample_errors/incomplete_signal.err b/tests/sample_errors/incomplete_signal.err new file mode 100644 index 0000000..901ef3b --- /dev/null +++ b/tests/sample_errors/incomplete_signal.err @@ -0,0 +1,2 @@ +5,1,0,Expected a signal detail name +4,9,3,Unexpected tokens \ No newline at end of file diff --git a/tests/samples/expr_literal.blp b/tests/samples/expr_value.blp similarity index 100% rename from tests/samples/expr_literal.blp rename to tests/samples/expr_value.blp diff --git a/tests/samples/expr_literal.ui b/tests/samples/expr_value.ui similarity index 100% rename from tests/samples/expr_literal.ui rename to tests/samples/expr_value.ui diff --git a/tests/samples/expr_literal_closure.blp b/tests/samples/expr_value_closure.blp similarity index 100% rename from tests/samples/expr_literal_closure.blp rename to tests/samples/expr_value_closure.blp diff --git a/tests/samples/expr_literal_closure.ui b/tests/samples/expr_value_closure.ui similarity index 100% rename from tests/samples/expr_literal_closure.ui rename to tests/samples/expr_value_closure.ui diff --git a/tests/samples/expr_literal_literal.blp b/tests/samples/expr_value_literal.blp similarity index 100% rename from tests/samples/expr_literal_literal.blp rename to tests/samples/expr_value_literal.blp diff --git a/tests/samples/expr_literal_literal.ui b/tests/samples/expr_value_literal.ui similarity index 100% rename from tests/samples/expr_literal_literal.ui rename to tests/samples/expr_value_literal.ui diff --git a/tests/test_samples.py b/tests/test_samples.py index 401a424..1f56eb6 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -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",