77 InstanceExpression ,
88 Literal ,
99 Method ,
10+ NewClassExpression ,
1011 Parameter ,
1112 Property ,
1213 ReturnStatement ,
@@ -24,7 +25,95 @@ public static function inject(&$type, $name, $signature, $body) {
2425 isset ($ type [$ key ]) || $ type [$ key ]= new Method (['public ' ], $ name , $ signature , [new ReturnStatement ($ body )]);
2526 }
2627
28+ /** Rewrites a given record and returns it body */
29+ public static function rewrite ($ node ) {
30+ $ body = $ node ->body ;
31+ $ string = $ object = $ value = '' ;
32+ $ signature = new Signature ([], null );
33+ $ constructor = new Method (['public ' ], '__construct ' , $ signature , []);
34+ foreach ($ node ->components as $ c ) {
35+ $ l = $ c ->line ;
36+
37+ $ modifiers = null === $ c ->promote ? ['private ' ] : explode (' ' , $ c ->promote );
38+ $ c ->promote = null ;
39+ $ signature ->parameters []= $ c ;
40+
41+ // Assigment inside constructor
42+ $ r = new InstanceExpression (new Variable ('this ' , $ l ), new Literal ($ c ->name , $ l ), $ l );
43+ $ constructor ->body []= new Assignment ($ r , '= ' , new Variable ($ c ->name , $ l ), $ l );
44+
45+ // Property declaration + accessor method
46+ $ type = $ c ->variadic ? ($ c ->type ? new IsArray ($ c ->type ) : new IsLiteral ('array ' )) : $ c ->type ;
47+ $ body []= new Property ($ modifiers , $ c ->name , $ type , null , [], null , $ l );
48+ $ body []= new Method (['public ' ], $ c ->name , new Signature ([], $ type ), [new ReturnStatement ($ r , $ l )]);
49+
50+ // Code for string representation, hashcode and comparison
51+ $ string .= ', ' .$ c ->name .': ". \\util \\Objects::stringOf($this-> ' .$ c ->name .')." ' ;
52+ $ object .= ', $this-> ' .$ c ->name ;
53+ $ value .= ', $value-> ' .$ c ->name ;
54+ }
55+
56+ // Create constructor, inlining <init>. Also support deprecated __init() function
57+ if (isset ($ body ['<init> ' ])) {
58+ foreach ($ body ['<init> ' ] as $ statement ) {
59+ $ constructor ->body []= $ statement ;
60+ }
61+ unset($ body ['<init> ' ]);
62+ }
63+ $ body ['__construct() ' ]= $ constructor ;
64+
65+ // Implement lang.Value
66+ self ::inject ($ body , 'toString ' , new Signature ([], new IsLiteral ('string ' )), new Code (
67+ '" ' .strtr (substr ($ node ->name , 1 ), '\\' , '. ' ).'( ' .substr ($ string , 2 ).')" '
68+ ));
69+ self ::inject ($ body , 'hashCode ' , new Signature ([], new IsLiteral ('string ' )), new Code (
70+ 'md5( \\util \\Objects::hashOf([" ' .substr ($ node ->name , 1 ).'" ' .$ object .'])) '
71+ ));
72+ self ::inject ($ body , 'compareTo ' , new Signature ([new Parameter ('value ' , null )], new IsLiteral ('int ' )), new Code (
73+ '$value instanceof self ? \\util \\Objects::compare([ ' .substr ($ object , 2 ).'], [ ' .substr ($ value , 2 ).']) : 1 '
74+ ));
75+
76+ // Add decomposition
77+ self ::inject ($ body , '__invoke ' , new Signature ([new Parameter ('map ' , new IsLiteral ('callable ' ), new Literal ('null ' ))], null ), new Code (
78+ 'null === $map ? [ ' .substr ($ object , 2 ).'] : $map( ' .substr ($ object , 2 ).') '
79+ ));
80+
81+ return $ body ;
82+ }
83+
2784 public function setup ($ language , $ emitter ) {
85+
86+ // Anonymous records
87+ $ new = $ language ->symbol ('new ' )->nud ;
88+ $ language ->prefix ('new ' , 0 , function ($ parse , $ token ) use ($ new ) {
89+ if ('record ' !== $ parse ->token ->value ) return $ new ($ parse , $ token );
90+
91+ // Anonymous record syntax: `new record(...) { }`
92+ $ parse ->forward ();
93+
94+ $ parse ->expecting ('( ' , 'new arguments ' );
95+ $ arguments = $ this ->arguments ($ parse );
96+ $ parse ->expecting (') ' , 'new arguments ' );
97+
98+ $ parse ->expecting ('{ ' , 'anonymous record ' );
99+ $ body = $ this ->typeBody ($ parse , null );
100+ $ parse ->expecting ('} ' , 'anonymous record ' );
101+
102+ $ components = [];
103+ foreach ($ arguments as $ name => $ argument ) {
104+ $ components []= new Parameter ($ name , null ); // TODO: Infer type
105+ }
106+
107+ $ expr = new NewClassExpression (
108+ new RecordDeclaration ([], '\\record ' , $ components , null , [], $ body ),
109+ $ arguments ,
110+ $ token ->line
111+ );
112+ $ expr ->kind = 'newrecord ' ;
113+ return $ expr ;
114+ });
115+
116+ // Record declaration
28117 $ language ->stmt ('record ' , function ($ parse , $ token ) {
29118 $ comment = $ parse ->comment ;
30119 $ line = $ parse ->token ->line ;
@@ -80,65 +169,28 @@ public function setup($language, $emitter) {
80169 $ body ['<init> ' ]= $ statements ;
81170 });
82171
83- $ emitter ->transform ('record ' , function ($ codegen , $ node ) {
84- $ body = $ node ->body ;
85- $ string = $ object = $ value = '' ;
86- $ signature = new Signature ([], null );
87- $ constructor = new Method (['public ' ], '__construct ' , $ signature , []);
88- foreach ($ node ->components as $ c ) {
89- $ l = $ c ->line ;
90-
91- $ modifiers = null === $ c ->promote ? ['private ' ] : explode (' ' , $ c ->promote );
92- $ c ->promote = null ;
93- $ signature ->parameters []= $ c ;
94-
95- // Assigment inside constructor
96- $ r = new InstanceExpression (new Variable ('this ' , $ l ), new Literal ($ c ->name , $ l ), $ l );
97- $ constructor ->body []= new Assignment ($ r , '= ' , new Variable ($ c ->name , $ l ), $ l );
98-
99- // Property declaration + accessor method
100- $ type = $ c ->variadic ? ($ c ->type ? new IsArray ($ c ->type ) : new IsLiteral ('array ' )) : $ c ->type ;
101- $ body []= new Property ($ modifiers , $ c ->name , $ type , null , [], null , $ l );
102- $ body []= new Method (['public ' ], $ c ->name , new Signature ([], $ type ), [new ReturnStatement ($ r , $ l )]);
103-
104- // Code for string representation, hashcode and comparison
105- $ string .= ', ' .$ c ->name .': ". \\util \\Objects::stringOf($this-> ' .$ c ->name .')." ' ;
106- $ object .= ', $this-> ' .$ c ->name ;
107- $ value .= ', $value-> ' .$ c ->name ;
108- }
109-
110- // Create constructor, inlining <init>.
111- if (isset ($ body ['<init> ' ])) {
112- foreach ($ body ['<init> ' ] as $ statement ) {
113- $ constructor ->body []= $ statement ;
114- }
115- unset($ body ['<init> ' ]);
116- }
117- $ body ['__construct() ' ]= $ constructor ;
118-
119- // Implement lang.Value
120- self ::inject ($ body , 'toString ' , new Signature ([], new IsLiteral ('string ' )), new Code (
121- '" ' .strtr (substr ($ node ->name , 1 ), '\\' , '. ' ).'( ' .substr ($ string , 2 ).')" '
122- ));
123- self ::inject ($ body , 'hashCode ' , new Signature ([], new IsLiteral ('string ' )), new Code (
124- 'md5( \\util \\Objects::hashOf([" ' .substr ($ node ->name , 1 ).'" ' .$ object .'])) '
125- ));
126- self ::inject ($ body , 'compareTo ' , new Signature ([new Parameter ('value ' , null )], new IsLiteral ('int ' )), new Code (
127- '$value instanceof self ? \\util \\Objects::compare([ ' .substr ($ object , 2 ).'], [ ' .substr ($ value , 2 ).']) : 1 '
128- ));
129- $ node ->implements []= new IsValue ('\\lang \\Value ' );
130-
131- // Add decomposition
132- self ::inject ($ body , '__invoke ' , new Signature ([new Parameter ('map ' , new IsLiteral ('callable ' ), new Literal ('null ' ))], null ), new Code (
133- 'null === $map ? [ ' .substr ($ object , 2 ).'] : $map( ' .substr ($ object , 2 ).') '
134- ));
172+ $ emitter ->transform ('newrecord ' , function ($ codegen , $ node ) {
173+ $ node ->kind = 'newclass ' ;
174+ $ node ->definition = new ClassDeclaration (
175+ [],
176+ null ,
177+ null ,
178+ [new IsValue ('\\lang \\Value ' )],
179+ self ::rewrite ($ node ->definition ),
180+ [],
181+ null ,
182+ $ node ->line
183+ );
184+ return $ node ;
185+ });
135186
187+ $ emitter ->transform ('record ' , function ($ codegen , $ node ) {
136188 return new ClassDeclaration (
137189 ['final ' ],
138190 $ node ->name ,
139191 $ node ->parent ,
140- $ node ->implements ,
141- $ body ,
192+ array_merge ([ new IsValue ( '\\ lang \\ Value ' )], $ node ->implements ) ,
193+ self :: rewrite ( $ node ) ,
142194 $ node ->annotations ,
143195 $ node ->comment ,
144196 $ node ->line
0 commit comments