1+ from __future__ import annotations
2+ from pathlib import Path
3+ from typing import Union , Generic , TypeVar
4+ from abc import ABC , abstractmethod
5+ from collections .abc import Sequence
6+
7+ from lark import Lark , Transformer , Tree , Token , v_args
8+ from lark .reconstruct import Reconstructor
9+
10+ R = TypeVar ("R" )
11+ class SupportsRead (ABC , Generic [R ]):
12+ @abstractmethod
13+ def read (self , size : int | None = - 1 ) -> R :
14+ pass
15+
16+ LANG_PATH = Path (__file__ ).parent / "language.lark"
17+
18+ lang = Lark (LANG_PATH .read_text (), maybe_placeholders = False )
19+ reconstructor = Reconstructor (lang )
20+
21+ def parse (script : Union [str , bytes , SupportsRead [str ], Path ]) -> Tree :
22+ if isinstance (script , Path ):
23+ script = script .read_text ()
24+ if isinstance (script , SupportsRead ):
25+ read_data = script .read ()
26+ assert isinstance (read_data , str )
27+ script = read_data
28+ if isinstance (script , bytes ):
29+ script = script .decode ("utf-8" )
30+ return lang .parse (script )
31+
32+ def unparse (tree : Tree ) -> str :
33+ return reconstructor .reconstruct (tree )
34+
35+ class PrettyUnparser (Transformer ):
36+ INDENT_STRING = " "
37+
38+ @classmethod
39+ def _indent (cls , text ):
40+ if not text :
41+ return ""
42+ return "\n " .join (cls .INDENT_STRING + line for line in text .splitlines ())
43+
44+ def PARAM_NAME (self , token ):
45+ return token .value
46+
47+ def BLOCK_NAME (self , token ):
48+ return token .value
49+
50+ def EVENT (self , token ):
51+ return token .value
52+
53+ def CONTROL_BLOCK_NAME (self , token ):
54+ return token .value
55+
56+ def _PREPROC_INSTR_CONTENT (self , token ):
57+ return token .value
58+
59+ def _COMMMENT_CONTENT (self , token ):
60+ return token .value
61+
62+ @v_args (inline = True )
63+ def hat (self , child ):
64+ return child
65+
66+ @v_args (inline = True )
67+ def param (self , child ):
68+ return child
69+
70+ @v_args (inline = True )
71+ def value_param (self , name ):
72+ return name
73+
74+ @v_args (inline = True )
75+ def bool_param (self , name ):
76+ return f"<{ name } >"
77+
78+ @v_args (inline = True )
79+ def event_hat (self , event_name ):
80+ return f"when ({ event_name } )"
81+
82+ def block_hat (self , items ):
83+ name , * params = items
84+ params_str = ", " .join (params )
85+ return f"custom_block { name } ({ params_str } )"
86+
87+ @v_args (inline = True )
88+ def PREPROC_INSTR (self , content ):
89+ return f"%%{ content } %%"
90+
91+ @v_args (inline = True )
92+ def COMMMENT (self , content ):
93+ return f"##{ content } ##"
94+
95+ def block (self , items ):
96+ params = []
97+ inner_blocks = []
98+ for i in items [1 :]:
99+ if not isinstance (i , Tree ):
100+ continue
101+ if str (i .data ) == "block_content" :
102+ inner_blocks .extend (i .children )
103+ if str (i .data ) == "block_params" :
104+ params .extend (i .children )
105+ block_name = items [0 ]
106+ block_text = f"{ block_name } ({ ', ' .join (params )} )" if params or not inner_blocks else f"{ block_name } "
107+ if len (inner_blocks ) > 0 :
108+ blocks_content = "\n " .join (inner_blocks )
109+ indented_content = self ._indent (blocks_content )
110+ block_text += f" {{\n { indented_content } \n }}"
111+ return block_text
112+
113+ def LITERAL_NUMBER (self , number : str ):
114+ return number
115+
116+ @v_args (inline = True )
117+ def expr (self , item ):
118+ return item
119+
120+ @v_args (inline = True )
121+ def low_expr1 (self , item ):
122+ if " " in item :
123+ return f"({ item } )"
124+ return item
125+
126+ @v_args (inline = True )
127+ def low_expr2 (self , item ):
128+ return item
129+
130+ def addition (self , items ):
131+ return items [0 ] + " + " + items [1 ]
132+
133+ def subtraction (self , items ):
134+ return items [0 ] + " - " + items [1 ]
135+
136+ def multiplication (self , items ):
137+ return items [0 ] + " * " + items [1 ]
138+
139+ def division (self , items ):
140+ return items [0 ] + " / " + items [1 ]
141+
142+ def top_level_block (self , items ):
143+ first_item = items [0 ]
144+ if first_item .startswith ("%%" ) or first_item .startswith ("##" ):
145+ return first_item
146+
147+ hat , * blocks = items
148+ blocks_content = "\n " .join (blocks )
149+ indented_content = self ._indent (blocks_content )
150+ return f"{ hat } {{\n { indented_content } \n }}"
151+
152+ def start (self , items ):
153+ return "\n \n " .join (items )
154+
155+ def pretty_unparse (tree : Tree ):
156+ return PrettyUnparser ().transform (tree )
157+
158+ if __name__ == "__main__" :
159+ EXAMPLE_FILE = Path (__file__ ).parent / "example.txt"
160+ tree = parse (EXAMPLE_FILE )
161+ print (tree .pretty ())
162+ print ()
163+ print ()
164+ print (tree )
165+ print ()
166+ print ()
167+ print (unparse (tree ))
168+ print ()
169+ print ()
170+ print (pretty_unparse (tree ))
0 commit comments