f0fee75b74eac0eb4856d609de6f76d57051b2b5
[panamaz] / src / notzed.nativez / bin / generate-api
1 #!/usr/bin/perl
2
3 # usage
4 #  -t package    target package
5 #  -d directory  output root
6 #  -v            verbose
7 #  -a datafile   add datafile to the dataset, can be from export.so or export-defines, etc
8
9 use Data::Dumper;
10
11 use File::Path qw(make_path);
12 use File::Basename;
13
14 use strict;
15
16 use Carp 'verbose';
17 use FindBin;
18 use lib "$FindBin::Bin/../lib";
19
20 use api;
21 use code;
22 use method;
23
24 $SIG{ __DIE__ } = sub { Carp::confess( @_ ) };
25 $Data::Dumper::Indent = 1;
26
27 my $apidef = "api.api";
28 my $vars = {
29         package => '',
30         output => 'bin',
31         verbose => 0,
32 };
33 my @apilist;
34
35 while (@ARGV) {
36         my $cmd = shift;
37
38         if ($cmd =~ m/^(-[^-])(.+)/) {
39                 $cmd = $1;
40                 unshift @ARGV, $2;
41         }
42
43         if ($cmd eq "-t") {
44                 $vars->{package} = shift;
45     } elsif ($cmd eq "-d") {
46                 $vars->{output} = shift;
47     } elsif ($cmd eq "-I") {
48                 push @{$vars->{include}}, shift;
49     } elsif ($cmd eq "-v") {
50                 $vars->{verbose}++;
51     } elsif ($cmd eq "-a") {
52                 push @apilist, shift;
53         } else {
54                 $apidef = $cmd;
55         }
56 }
57
58 push @{$vars->{include}}, "$FindBin::Bin/../lib";
59 push @INC, dirname($apidef);
60
61 print Dumper($vars) if $vars->{verbose};
62
63 my $api = new api($apidef, $vars, @apilist);
64
65 #print Dumper($api);
66
67 exportLibraries($api);
68 exportStructs($api);
69 exportConstants($api);
70 exit 0;
71
72 sub formatFunction {
73         my $api = shift;
74         my $c = shift;
75         my $template = shift;
76         my $info = new method($api, $c);
77
78         #print 'function='.Dumper($c);
79         #print 'members='.Dumper(\@members);
80         #print 'info='.Dumper($info);
81
82         my $code;
83
84         #foreach my $l (split /\n/,Dumper($info->{vars}).Dumper($info->{arguments})) {
85         #       $code .= "// $l\n";
86         #}
87
88         $code .= code::applyTemplate($template, $info->{vars});
89         $code =~ s/^\s*\n//osgm;
90
91         return $code;
92 }
93
94 sub formatCall {
95         my $api = shift;
96         my $c = shift;
97         my $template = $api->{index}->{'code:method'};
98         my $upcall = api::findItem($template, 'upcall');
99         my $downcall = api::findItem($template, 'downcall');
100         my $info = new method($api, $c);
101
102         #print 'function='.Dumper($c);
103         #print 'members='.Dumper(\@members);
104
105         my $code;
106         foreach my $l (split /\n/,Dumper($info)) {
107                 $code .= "//$l\n";
108         }
109
110         $info->{vars}->{downcall} = ($c->{access} =~ m/r/) ? code::applyTemplate($downcall, $info->{vars}) : '';
111         $info->{vars}->{upcall} = ($c->{access} =~ m/w/) ? code::applyTemplate($upcall, $info->{vars}) : '';
112
113         return $code.code::applyTemplate(api::findItem($api->{index}->{'code:class'}, 'call'), $info->{vars});
114 }
115
116 sub formatItems {
117         my $api = shift;
118         my $obj = shift;
119         my $inc = shift;
120         my $res = shift;
121         my @list;
122
123         @list = grep {
124                 $api->{output}->{"$_->{type}:$_->{name}"}++;
125                 $res->{seen}->{"$_->{type}:$_->{name}"}++ == 0
126         } $api->findMatches($inc, $res->{ctx});
127
128         if ($inc->{type} eq 'func') {
129                 my $def = $api->{index}->{'func:<default>'};
130                 my $func = api::optionValue('func:template', 'code:method=invoke', $inc, $res->{template});
131                 my $init = api::optionValue('init:template', undef, $inc, $res->{template});
132                 my $funct = findTemplateName($api, $func);
133                 my $initt = findTemplateName($api, $init) if defined $init;
134
135                 push @{$res->{func}}, map { formatFunction($api, $_, $funct) } @list;
136                 push @{$res->{init}}, map { formatFunction($api, $_, $initt) } @list if defined($initt);
137         } elsif ($inc->{type} eq 'define') {
138                 push @{$res->{define}}, map { code::formatDefine($api, $_) } @list;
139         } elsif ($inc->{type} eq 'enum') {
140                 push @{$res->{enum}}, map { code::formatEnum($api, $_) } @list;
141         } elsif ($inc->{type} eq 'call') {
142                 push @{$res->{enum}}, map { formatCall($api, $_) } @list;
143         } else {
144                 die;
145         }
146 }
147
148 sub formatLibrary {
149         my $api = shift;
150         my $obj = shift;
151         my $res = shift;
152         my $data = $api->{data};
153         my $d;
154
155         print "library $obj->{name}\n" if ($api->{vars}->{verbose} > 0);
156
157         foreach my $inc (@{$obj->{items}}) {
158                 if ($inc->{type} eq 'library') {
159                         $api->{output}->{"$inc->{match}"}++;
160                         formatLibrary($api, $api->{index}->{$inc->{match}}, $res);
161                 } elsif ($inc->{type} =~ m/^(func|call|define|enum)$/no) {
162                         print "  $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
163                         formatItems($api, $obj, $inc, $res);
164                 } elsif ($inc->{match} eq 'code:<inline>') {
165                         print "  $inc->{match}\n" if ($api->{vars}->{verbose} > 1);
166                         push @{$res->{func}}, $inc->{literal};
167                 } elsif ($inc->{type} eq 'code') {
168                         # apply template perhaps, or apply with set?
169                         push @{$res->{func}}, map {
170                                 if (defined($inc->{options}->[0])) {
171                                         api::findItem($_, $inc->{options}->[0])->{literal};
172                                 } else {
173                                         $_->{literal}
174                                 }
175                         } grep { $_->{match} =~ m/$inc->{regex}/ } @{$api->{api}};
176                 } elsif ($inc->{type} ne 'field') {
177                         print Dumper($inc);
178                         die;
179                 }
180         }
181 }
182
183 sub findTemplateName {
184         my $api = shift;
185         my $name = shift;
186
187         if ($name =~ m/^(.+)=(.+)$/) {
188                 my $template = api::findItem($api->{index}->{$1}, $2);
189                 return $template if defined $template;
190         }
191         die "can't find template '$name'\n";
192 }
193
194 sub findTemplate {
195         my $api = shift;
196         my $obj = shift;
197         my $s = shift;
198         my $data = $api->{data};
199         my $def = $api->{index}->{"$s->{type}:<default>"};
200         my $name = api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def);
201
202         return findTemplateName($api, $name);
203 }
204
205 # TODO: embedded structs
206 sub formatStruct {
207         my $api = shift;
208         my $obj = shift;
209         my $s = shift;
210         my $data = $api->{data};
211         my $seen = {};
212         my $structTemplate = findTemplate($api, $obj, $s);
213         my @members = code::scanFields($api, $s);
214         my @membersOutput = grep { $_->{field}->{output} } @members;
215
216         my $varhandles = join '', map { code::formatTemplate($_->{match}->{varhandle}, $_->{match}, "\t") } @membersOutput;
217         my $accessors = join "\n", map {
218                 my ($m, $type, $match) = ($_->{field}, $_->{type}, $_->{match});
219                 map {
220                         my $accessor = $api->{index}->{$_};
221
222                         map { code::applyTemplate($_, $match) } grep { $_ } map { api::findItem($accessor, $_) } map {
223                                 my @list = ();
224                                 if ($_ =~ m/r/o) {
225                                         push @list, 'get';
226                                         push @list, 'geti' if $_ =~ m/i/o;
227                                 }
228                                 if ($_ =~ m/w/o) {
229                                         push @list, 'set';
230                                         push @list, 'seti' if $_ =~ m/i/o;
231                                 }
232                                 @list;
233                         } $m->{access};
234                 } split(/,/,api::optionValue('template', undef, $type));
235         } @membersOutput;
236
237         my $res = {
238                 ctx => $s,
239                 library => [],
240                 func => [],
241                 define => [],
242                 enum => [],
243                 call => [],
244                 seen => {},
245                 template => $structTemplate,
246         };
247
248         formatLibrary($api, $obj, $res);
249
250         my $vars = {
251                 %{$api->{vars}},
252                 layout => code::formatStructLayout($api, $s, \@members),
253                 rename => $s->{rename},
254                 name => $s->{name},
255                 varhandles => $varhandles,
256                 accessors => $accessors,
257                 methods => join("\n", @{$res->{func}}),
258                 defines => join("\n", @{$res->{define}}),
259                 enums => join("\n", @{$res->{enum}}),
260         };
261
262         my $code;
263         foreach my $l (split /\n/,Dumper($s, \@members)) {
264                 $code .= "//$l\n";
265         }
266
267         $api->{output}->{"$s->{type}:$s->{name}"} = 1;
268
269         return $code.code::applyTemplate($structTemplate, $vars);
270 }
271
272 # output all libraries
273 sub exportLibraries {
274         my $api = shift;
275         my $data = $api->{data};
276         my $def = $api->{index}->{'library:<default>'};
277
278         foreach my $obj (grep { $_->{type} eq 'library' } @{$api->{api}}) {
279                 next if $api->{output}->{"$obj->{type}:$obj->{name}"};
280                 next if $obj->{output} != 1;
281
282                 my $library = findTemplateName($api, api::optionValue('template', 'code:class=library', $obj, $def));
283                 my $res = {
284                         ctx => $obj,
285                         library => [],
286                         func => [],
287                         init => [],
288                         define => [],
289                         enum => [],
290                         call => [],
291                         seen => {},
292                         template => $library,
293                 };
294
295                 formatLibrary($api, $obj, $res);
296
297                 my $vars = {
298                         %{$api->{vars}},
299                         name => $obj->{name},
300                         init => join("\n", @{$res->{init}}),
301                         defines => join("\n", @{$res->{define}}),
302                         enums => join("\n", @{$res->{enum}}),
303                         funcs => join("\n", @{$res->{func}}),
304                         calls => join("\n", @{$res->{call}}),
305                 };
306
307                 export($api, $obj->{name}, code::applyTemplate($library, $vars));
308         }
309 }
310
311 sub export {
312         my $api = shift;
313         my $name = shift;
314         my $text = shift;
315
316         my $f = $api->openOutput($name);
317         print $f $text;
318         $api->closeOutput($name, $f);
319 }
320
321 sub formatClass {
322         my $api = shift;
323         my $obj = shift;
324         my $s = shift;
325
326         if ($s->{type} =~ m/struct|union/) {
327                 return formatStruct($api, $obj, $s);
328         } elsif ($s->{type} eq 'call') {
329                 return formatCall($api, $s);
330         } else {
331                 die;
332         }
333 }
334
335 sub exportStructs {
336         my $api = shift;
337         my $data = $api->{data};
338
339         # first those directly referenced
340         foreach my $obj (grep { $_->{type} =~ m/call|struct|union/ } @{$api->{api}}) {
341                 my @list = $api->findMatches($obj, $obj);
342
343                 print "gen ".($#list+1)." $obj->{type} $obj->{name}\n" if ($api->{vars}->{verbose} > 0);
344                 foreach my $s (@list) {
345                         next if $api->{output}->{"$s->{type}:$s->{name}"};
346                         print "  $s->{name}\n" if ($api->{vars}->{verbose} > 1);
347
348                         export($api, $s->{rename}, formatClass($api, $obj, $s));
349                 }
350         }
351
352         # then anything else left using the default outputs
353         foreach my $s (grep { $_->{output} && ($_->{type} =~ m/struct|union|call/) && !$api->{output}->{"$_->{type}:$_->{name}"} } @{$api->{api}}) {
354                 my $obj = $data->{"$s->{type}:<default>"};
355                 export($api, $s->{rename}, formatClass($api, $obj, $s));
356         }
357 }
358
359 # exports any define/enum not yet included elsehwere
360 # TODO: this is sort of not very useful
361 sub exportConstants {
362         my $api = shift;
363         my $data = $api->{data};
364         my $template = $api->{index}->{'code:class'};
365         my $constant = api::findItem($template, 'constants');
366
367         # hmm, not sure if i should use obj or just the values directly here
368         foreach my $s (grep { $_->{type} =~ m/define|enum/ } values %{$api->{data}}) {
369                 next if $api->{output}->{"$s->{type}:$s->{name}"};
370                 next if !$s->{output};
371
372                 my $defines = '';
373                 my $enums = '';
374
375                 $defines = code::formatDefine($api, $s) if $s->{type} eq 'define';
376                 $enums = code::formatEnum($api, $s) if $s->{type} eq 'enum';
377
378                 my $vars = {
379                         %{$api->{vars}},
380                         name => $s->{name},
381                         defines => $defines,
382                         enums => $enums,
383                 };
384
385                 my $code;
386                 foreach my $l (split /\n/,Dumper($s)) {
387                         $code .= "//$l\n";
388                 }
389
390                 export($api, $s->{name}, $code.code::applyTemplate($constant, $vars));
391         }
392 }