9393 "TRACE" ,
9494 "COST" ,
9595 "EXPLAIN" ,
96+ "LOCAL" ,
9697 "EDGE" ,
9798 "CLOUD" ,
9899 "MEMORY" ,
@@ -657,8 +658,15 @@ def _parse_observe_declaration(self):
657658 if self ._match_delimiter (":" ):
658659 self ._advance ()
659660 annotation = self ._parse_annotation_expression ()
660- self ._expect_operator ("=" )
661- value = self ._parse_expression ()
661+ if self ._match_operator ("=" ):
662+ self ._advance ()
663+ value = self ._parse_expression ()
664+ else :
665+ value = Identifier (
666+ name_tok .value ,
667+ line = name_tok .line ,
668+ column = name_tok .column ,
669+ )
662670 return ObserveDeclaration (
663671 name_tok .value , value , annotation ,
664672 line = tok .line , column = tok .column ,
@@ -672,12 +680,21 @@ def _parse_on_change_statement(self):
672680 return OnChangeStatement (signal , body , line = tok .line , column = tok .column )
673681
674682 def _parse_canvas_block (self ):
675- """Parse: canvas [name] : block."""
683+ """Parse: canvas [name] : block or canvas [name] { ... } ."""
676684 tok = self ._advance () # consume CANVAS
677685 name = ""
678686 if self ._match_type (TokenType .IDENTIFIER ):
679687 name = self ._advance ().value
680- body = self ._parse_block ()
688+ if self ._match_delimiter ("{" ):
689+ self ._advance ()
690+ self ._skip_bracket_newlines ()
691+ body = []
692+ while not self ._at_end () and not self ._match_delimiter ("}" ):
693+ body .append (self ._parse_statement ())
694+ self ._skip_bracket_newlines ()
695+ self ._expect_delimiter ("}" )
696+ else :
697+ body = self ._parse_block ()
681698 return CanvasBlock (name = name , body = body , line = tok .line , column = tok .column )
682699
683700 def _parse_render_statement (self ):
@@ -921,6 +938,32 @@ def _parse_case_pattern(self):
921938 """
922939 pattern = self ._parse_or_expression ()
923940
941+ if self ._match_delimiter ("{" ) and isinstance (pattern , Identifier ):
942+ entries = []
943+ self ._advance ()
944+ self ._skip_bracket_newlines ()
945+ while not self ._match_delimiter ("}" ) and not self ._at_end ():
946+ key_tok = self ._expect_identifier ()
947+ key = Identifier (
948+ key_tok .value ,
949+ line = key_tok .line ,
950+ column = key_tok .column ,
951+ )
952+ self ._expect_delimiter (":" )
953+ value = self ._parse_expression ()
954+ entries .append ((key , value ))
955+ if self ._match_delimiter ("," ):
956+ self ._advance ()
957+ self ._skip_bracket_newlines ()
958+ self ._expect_delimiter ("}" )
959+ pattern = BinaryOp (
960+ pattern ,
961+ "{}" ,
962+ DictLiteral (entries , line = pattern .line , column = pattern .column ),
963+ line = pattern .line ,
964+ column = pattern .column ,
965+ )
966+
924967 # OR patterns: pattern | pattern | ...
925968 if self ._match_operator ("|" ):
926969 patterns = [pattern ]
@@ -1242,6 +1285,14 @@ def _parse_return_statement(self):
12421285 and not self ._match_type (TokenType .DEDENT ) \
12431286 and not self ._match_type (TokenType .EOF ):
12441287 value = self ._parse_expression ()
1288+ if self ._match_delimiter ("," ):
1289+ elements = [value ]
1290+ while self ._match_delimiter ("," ):
1291+ self ._advance ()
1292+ if self ._at_end () or self ._match_type (TokenType .NEWLINE ):
1293+ break
1294+ elements .append (self ._parse_expression ())
1295+ value = TupleLiteral (elements , line = tok .line , column = tok .column )
12451296 return ReturnStatement (value , line = tok .line , column = tok .column )
12461297
12471298 def _parse_yield_statement (self ):
@@ -1724,6 +1775,10 @@ def _parse_keyword_atom(self, tok):
17241775 if concept == "NONE" :
17251776 self ._advance ()
17261777 return NoneLiteral (line = tok .line , column = tok .column )
1778+ if concept == "PAR" and self ._match_next_delimiter ("[" ):
1779+ return self ._parse_prefix_soft_call ()
1780+ if concept == "SPAWN" and self ._has_inline_expression_after_keyword ():
1781+ return self ._parse_prefix_soft_call ()
17271782 if concept in _AI_NATIVE_CONCEPTS and self ._is_native_ai_form ():
17281783 return self ._parse_native_ai_expression ()
17291784 if concept in _IDENTIFIER_LIKE_CONCEPTS :
@@ -1735,6 +1790,33 @@ def _parse_keyword_atom(self, tok):
17351790 return self ._parse_yield_expr ()
17361791 return None
17371792
1793+ def _match_next_delimiter (self , delim ):
1794+ """Check whether the next token is a specific delimiter."""
1795+ idx = self .pos + 1
1796+ if idx < len (self .tokens ):
1797+ tok = self .tokens [idx ]
1798+ return tok .type == TokenType .DELIMITER and tok .value == delim
1799+ return False
1800+
1801+ def _has_inline_expression_after_keyword (self ):
1802+ """Return True when the next token starts an inline expression."""
1803+ idx = self .pos + 1
1804+ if idx >= len (self .tokens ):
1805+ return False
1806+ nxt = self .tokens [idx ]
1807+ return nxt .type not in {
1808+ TokenType .NEWLINE ,
1809+ TokenType .DEDENT ,
1810+ TokenType .EOF ,
1811+ }
1812+
1813+ def _parse_prefix_soft_call (self ):
1814+ """Parse soft-keyword prefix forms such as `par [..]` or `spawn expr`."""
1815+ tok = self ._advance ()
1816+ func = Identifier (tok .value , line = tok .line , column = tok .column )
1817+ arg = self ._parse_expression ()
1818+ return CallExpr (func , [arg ], [], line = tok .line , column = tok .column )
1819+
17381820 def _parse_atom (self ): # pylint: disable=too-many-branches
17391821 """Parse atomic expressions: literals, identifiers, parenthesized."""
17401822 tok = self ._current ()
@@ -1858,7 +1940,7 @@ def _parse_native_ai_expression(self):
18581940
18591941 if self ._match_delimiter (":" ):
18601942 self ._advance ()
1861- args .append (self ._parse_expression ( ))
1943+ args .append (self ._parse_native_ai_template ( tok ))
18621944 else :
18631945 self ._error ("EXPECTED_EXPRESSION" , self ._current (), token = self ._current ().value )
18641946
@@ -1871,6 +1953,42 @@ def _parse_native_ai_expression(self):
18711953
18721954 return node
18731955
1956+ def _parse_native_ai_template (self , tok ):
1957+ """Parse the template section of a native AI expression."""
1958+ if not self ._match_type (TokenType .NEWLINE ):
1959+ return self ._parse_expression ()
1960+
1961+ self ._advance ()
1962+ self ._skip_newlines ()
1963+ if not self ._match_type (TokenType .INDENT ):
1964+ return StringLiteral ("" , line = tok .line , column = tok .column )
1965+
1966+ self ._advance ()
1967+ lines = []
1968+ current_line = []
1969+
1970+ while not self ._at_end () and not self ._match_type (TokenType .DEDENT ):
1971+ cur = self ._current ()
1972+ if cur .type == TokenType .NEWLINE :
1973+ if current_line :
1974+ lines .append (" " .join (current_line ).strip ())
1975+ current_line = []
1976+ self ._advance ()
1977+ continue
1978+ current_line .append (str (cur .value ))
1979+ self ._advance ()
1980+
1981+ if current_line :
1982+ lines .append (" " .join (current_line ).strip ())
1983+ if self ._match_type (TokenType .DEDENT ):
1984+ self ._advance ()
1985+
1986+ return StringLiteral (
1987+ "\n " .join (line for line in lines if line ),
1988+ line = tok .line ,
1989+ column = tok .column ,
1990+ )
1991+
18741992 def _parse_model_ref_literal (self ):
18751993 """Parse a model reference literal starting with `@`."""
18761994 tok = self ._advance () # consume @
@@ -2009,7 +2127,7 @@ def _parse_list_literal(self):
20092127 self ._advance () # consume ]
20102128 return ListLiteral ([], line = tok .line , column = tok .column )
20112129
2012- first = self ._parse_expression ()
2130+ first = self ._parse_list_element ()
20132131
20142132 # Check for list comprehension: [expr FOR ...]
20152133 if self ._match_concept ("LOOP_FOR" ):
@@ -2023,11 +2141,24 @@ def _parse_list_literal(self):
20232141 self ._skip_bracket_newlines ()
20242142 if self ._match_delimiter ("]" ):
20252143 break
2026- elements .append (self ._parse_expression ())
2144+ elements .append (self ._parse_list_element ())
20272145 self ._skip_bracket_newlines ()
20282146 self ._expect_delimiter ("]" )
20292147 return ListLiteral (elements , line = tok .line , column = tok .column )
20302148
2149+ def _parse_list_element (self ):
2150+ """Parse a single list element, including starred forms."""
2151+ if self ._match_operator ("*" ):
2152+ tok = self ._advance ()
2153+ value = self ._parse_expression ()
2154+ return StarredExpr (
2155+ value ,
2156+ is_double = False ,
2157+ line = tok .line ,
2158+ column = tok .column ,
2159+ )
2160+ return self ._parse_expression ()
2161+
20312162 def _parse_brace_literal (self ):
20322163 """Parse dict or set literal, including dict unpacking."""
20332164 tok = self ._advance () # consume {
0 commit comments