language: Add closure expressions

This commit is contained in:
James Westman 2022-12-24 21:46:03 -06:00
parent 5cf9b63547
commit 59aa054c4c
No known key found for this signature in database
GPG key ID: CE2DBA0ADB654EA6
10 changed files with 96 additions and 7 deletions

View file

@ -189,7 +189,10 @@ class TypeType(BasicType):
_BASIC_TYPES = { _BASIC_TYPES = {
"bool": BoolType,
"gboolean": BoolType, "gboolean": BoolType,
"string": StringType,
"gchararray": StringType,
"int": IntType, "int": IntType,
"gint": IntType, "gint": IntType,
"gint64": IntType, "gint64": IntType,
@ -730,6 +733,9 @@ class GirContext:
return None return None
def get_type(self, name: str, ns: str) -> T.Optional[GirNode]: def get_type(self, name: str, ns: str) -> T.Optional[GirNode]:
if ns is None and name in _BASIC_TYPES:
return _BASIC_TYPES[name]()
ns = ns or "Gtk" ns = ns or "Gtk"
if ns not in self.namespaces: if ns not in self.namespaces:

View file

@ -1,5 +1,5 @@
from .attributes import BaseAttribute, BaseTypedAttribute from .attributes import BaseAttribute, BaseTypedAttribute
from .expression import CastExpr, IdentExpr, LookupOp, ExprChain from .expression import CastExpr, ClosureExpr, Expr, ExprChain, IdentExpr, LookupOp
from .gobject_object import Object, ObjectContent from .gobject_object import Object, ObjectContent
from .gobject_property import Property from .gobject_property import Property
from .gobject_signal import Signal from .gobject_signal import Signal

View file

@ -25,13 +25,24 @@ from .types import TypeName
expr = Pratt() expr = Pratt()
class Expr: class Expr(AstNode):
@property @property
def type(self) -> T.Optional[GirType]: def type(self) -> T.Optional[GirType]:
raise NotImplementedError() raise NotImplementedError()
@property
def rhs(self) -> T.Optional["Expr"]:
if isinstance(self.parent, ExprChain):
children = list(self.parent.children)
if children.index(self) + 1 < len(children):
return children[children.index(self) + 1]
else:
return self.parent.rhs
else:
return None
class ExprChain(Expr, AstNode):
class ExprChain(Expr):
grammar = expr grammar = expr
@property @property
@ -43,14 +54,14 @@ class ExprChain(Expr, AstNode):
return self.last.type return self.last.type
class InfixExpr(Expr, AstNode): class InfixExpr(Expr):
@property @property
def lhs(self): def lhs(self):
children = list(self.parent_by_type(ExprChain).children) children = list(self.parent_by_type(ExprChain).children)
return children[children.index(self) - 1] return children[children.index(self) - 1]
class IdentExpr(Expr, AstNode): class IdentExpr(Expr):
grammar = UseIdent("ident") grammar = UseIdent("ident")
@property @property
@ -124,7 +135,45 @@ class CastExpr(InfixExpr):
) )
class ClosureExpr(Expr):
grammar = [
Optional(["$", UseLiteral("extern", True)]),
UseIdent("name"),
"(",
Delimited(ExprChain, ","),
")",
]
@property
def type(self) -> T.Optional[GirType]:
if isinstance(self.rhs, CastExpr):
return self.rhs.type
else:
return None
@property
def closure_name(self) -> str:
return self.tokens["name"]
@property
def args(self) -> T.List[ExprChain]:
return self.children[ExprChain]
@validate()
def cast_to_return_type(self):
if not isinstance(self.rhs, CastExpr):
raise CompileError(
"Closure expression must be cast to the closure's return type"
)
@validate()
def builtin_exists(self):
if not self.tokens["extern"]:
raise CompileError(f"{self.closure_name} is not a builtin function")
expr.children = [ expr.children = [
Prefix(ClosureExpr),
Prefix(IdentExpr), Prefix(IdentExpr),
Prefix(["(", ExprChain, ")"]), Prefix(["(", ExprChain, ")"]),
Infix(10, LookupOp), Infix(10, LookupOp),

View file

@ -179,7 +179,7 @@ class XmlOutput(OutputFormat):
def _emit_expression(self, expression: ExprChain, xml: XmlEmitter): def _emit_expression(self, expression: ExprChain, xml: XmlEmitter):
self._emit_expression_part(expression.children[-1], xml) self._emit_expression_part(expression.children[-1], xml)
def _emit_expression_part(self, expression, xml: XmlEmitter): def _emit_expression_part(self, expression: Expr, xml: XmlEmitter):
if isinstance(expression, IdentExpr): if isinstance(expression, IdentExpr):
self._emit_ident_expr(expression, xml) self._emit_ident_expr(expression, xml)
elif isinstance(expression, LookupOp): elif isinstance(expression, LookupOp):
@ -188,6 +188,8 @@ class XmlOutput(OutputFormat):
self._emit_expression(expression, xml) self._emit_expression(expression, xml)
elif isinstance(expression, CastExpr): elif isinstance(expression, CastExpr):
self._emit_cast_expr(expression, xml) self._emit_cast_expr(expression, xml)
elif isinstance(expression, ClosureExpr):
self._emit_closure_expr(expression, xml)
else: else:
raise CompilerBugError() raise CompilerBugError()
@ -204,6 +206,12 @@ class XmlOutput(OutputFormat):
def _emit_cast_expr(self, expr: CastExpr, xml: XmlEmitter): def _emit_cast_expr(self, expr: CastExpr, xml: XmlEmitter):
self._emit_expression_part(expr.lhs, xml) self._emit_expression_part(expr.lhs, xml)
def _emit_closure_expr(self, expr: ClosureExpr, xml: XmlEmitter):
xml.start_tag("closure", function=expr.closure_name, type=expr.type)
for arg in expr.args:
self._emit_expression_part(arg, xml)
xml.end_tag()
def _emit_attribute( def _emit_attribute(
self, tag: str, attr: str, name: str, value: Value, xml: XmlEmitter self, tag: str, attr: str, name: str, value: Value, xml: XmlEmitter
): ):

View file

@ -46,7 +46,7 @@ _tokens = [
(TokenType.WHITESPACE, r"\s+"), (TokenType.WHITESPACE, r"\s+"),
(TokenType.COMMENT, r"\/\*[\s\S]*?\*\/"), (TokenType.COMMENT, r"\/\*[\s\S]*?\*\/"),
(TokenType.COMMENT, r"\/\/[^\n]*"), (TokenType.COMMENT, r"\/\/[^\n]*"),
(TokenType.OP, r"<<|>>|=>|::|<|>|:=|\.|\|\||\||\+|\-|\*|=|:|/"), (TokenType.OP, r"\$|<<|>>|=>|::|<|>|:=|\.|\|\||\||\+|\-|\*|=|:|/"),
(TokenType.PUNCTUATION, r"\(|\)|\{|\}|;|\[|\]|\,"), (TokenType.PUNCTUATION, r"\(|\)|\{|\}|;|\[|\]|\,"),
] ]
_TOKENS = [(type, re.compile(regex)) for (type, regex) in _tokens] _TOKENS = [(type, re.compile(regex)) for (type, regex) in _tokens]

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label {
label: bind $closure();
}

View file

@ -0,0 +1 @@
4,15,10,Closure expression must be cast to the closure's return type

View file

@ -0,0 +1,5 @@
using Gtk 4.0;
Label my-label {
label: bind ($my-closure(my-label.margin-bottom)) as (string);
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<object class="GtkLabel" id="my-label">
<binding name="label">
<closure function="my-closure" type="gchararray">
<lookup name="margin-bottom" type="GtkLabel">
<constant>my-label</constant>
</lookup>
</closure>
</binding>
</object>
</interface>

View file

@ -151,6 +151,7 @@ class TestSamples(unittest.TestCase):
self.assert_sample("combo_box_text") self.assert_sample("combo_box_text")
self.assert_sample("comments") self.assert_sample("comments")
self.assert_sample("enum") self.assert_sample("enum")
self.assert_sample("expr_closure", skip_run=True) # The closure doesn't exist
self.assert_sample("expr_lookup") self.assert_sample("expr_lookup")
self.assert_sample("file_filter") self.assert_sample("file_filter")
self.assert_sample("flags") self.assert_sample("flags")
@ -208,6 +209,7 @@ class TestSamples(unittest.TestCase):
self.assert_sample_error("empty") self.assert_sample_error("empty")
self.assert_sample_error("enum_member_dne") self.assert_sample_error("enum_member_dne")
self.assert_sample_error("expr_cast_conversion") self.assert_sample_error("expr_cast_conversion")
self.assert_sample_error("expr_closure_not_cast")
self.assert_sample_error("expr_lookup_dne") self.assert_sample_error("expr_lookup_dne")
self.assert_sample_error("expr_lookup_no_properties") self.assert_sample_error("expr_lookup_no_properties")
self.assert_sample_error("filters_in_non_file_filter") self.assert_sample_error("filters_in_non_file_filter")