logos.pl 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. #!/usr/bin/perl
  2. use 5.006;
  3. use warnings;
  4. use strict;
  5. use FindBin;
  6. use lib "$FindBin::Bin/lib";
  7. use Digest::MD5 'md5_hex';
  8. use Module::Load;
  9. use Module::Load::Conditional 'can_load';
  10. use Getopt::Long;
  11. package Logos;
  12. sub sigil { my $id = shift; return "_logos_$id\$"; }
  13. package main;
  14. use Logos::Util;
  15. $Logos::Util::errorhandler = \&utilErrorHandler;
  16. use aliased 'Logos::Patch';
  17. use aliased 'Logos::Patch::Source::Generator' => 'Patch::Source::Generator';
  18. use aliased 'Logos::Patch';
  19. use aliased 'Logos::Group';
  20. use aliased 'Logos::Method';
  21. use aliased 'Logos::Class';
  22. use aliased 'Logos::Subclass';
  23. use aliased 'Logos::StaticClassGroup' ;
  24. use aliased 'Logos::Property';
  25. use aliased 'Logos::Function';
  26. use Logos::Generator;
  27. %main::CONFIG = ( generator => "MobileSubstrate",
  28. warnings => "default",
  29. );
  30. $main::warnings = 0;
  31. GetOptions("config|c=s" => \%main::CONFIG);
  32. my $filename = $ARGV[0];
  33. die "Syntax: $FindBin::Script filename\n" if !$filename;
  34. open(FILE, $filename) or die "Could not open $filename.\n";
  35. my @lines = ();
  36. my @patches = ();
  37. my $preprocessed = 0;
  38. my %lineMapping = ();
  39. { # If the first line matches "# \d \"...\"", this file has been run through the preprocessor already.
  40. my $firstline = <FILE>;
  41. seek(FILE, 0, Fcntl::SEEK_SET);
  42. if($firstline && $firstline =~ /^# \d+ \"(.*?)\"$/) {
  43. $preprocessed = 1;
  44. $filename = $1;
  45. }
  46. $.--; # Reset line number.
  47. }
  48. {
  49. my $readignore = 0;
  50. my $built = "";
  51. my $building = 0;
  52. my $iflevel = -1;
  53. READLOOP: while(my $line = <FILE>) {
  54. chomp($line);
  55. if($preprocessed && $line =~ /^# (\d+) \"(.*?)\"/) {
  56. $lineMapping{$.+1} = [$2, $1];
  57. }
  58. if($readignore) {
  59. # Handle #if nesting.
  60. if($iflevel > -1 && $line =~ /^#\s*if(n?def)?/) {
  61. $iflevel++;
  62. } elsif($iflevel > 0 && $line =~ /^#\s*endif/) {
  63. $line = $';
  64. $iflevel--;
  65. }
  66. # End of a multi-line comment or #if block while ignoring input.
  67. if($iflevel == 0 || ($iflevel == -1 && $line =~ /^.*?\*\/\s*/)) {
  68. $readignore = 0;
  69. $iflevel = -1;
  70. $line = $';
  71. }
  72. }
  73. if($readignore) { push(@lines, ""); next; }
  74. my @quotes = quotes($line);
  75. # Delete all single-line /* xxx */ comments.
  76. while($line =~ /\/\*.*?\*\//g) {
  77. next if fallsBetween($-[0], @quotes);
  78. $line = $`.$';
  79. redo READLOOP;
  80. }
  81. # Start of a multi-line /* comment.
  82. while($line =~ /\/\*.*$/g) {
  83. next if fallsBetween($-[0], @quotes);
  84. $line = $`;
  85. push(@lines, $line);
  86. $readignore = 1;
  87. next READLOOP;
  88. }
  89. # Delete all single-line to-EOL // xxx comments.
  90. while($line =~ /\/\//g) {
  91. next if fallsBetween($-[0], @quotes);
  92. $line = $`;
  93. redo READLOOP;
  94. }
  95. # #if 0.
  96. while($line =~ /^\s*#\s*if\s+0/) {
  97. $iflevel = 1;
  98. push(@lines, "");
  99. $readignore = 1;
  100. next READLOOP;
  101. }
  102. if(!$readignore) {
  103. # Line starts with - (return), start gluing lines together until we find a { or ;...
  104. if(!$building
  105. && (
  106. $line =~ /^\s*(%new.*?)?\s*([+-])\s*\(\s*(.*?)\s*\)/
  107. || $line =~ /%orig[^;]*$/
  108. || $line =~ /%init[^;]*$/
  109. )
  110. && index($line, "{") < $-[0] && index($line, ";") < $-[0]) {
  111. if(fallsBetween($-[0], @quotes)) {
  112. push(@lines, $line);
  113. next;
  114. }
  115. $building = 1;
  116. $built = $line;
  117. push(@lines, "");
  118. next;
  119. } elsif($building) {
  120. $line =~ s/^\s+//g;
  121. $built .= " ".$line;
  122. if(index($line,"{") != -1 || index($line,";") != -1) {
  123. push(@lines, $built);
  124. $building = 0;
  125. $built = "";
  126. next;
  127. }
  128. push(@lines, "");
  129. next;
  130. }
  131. push(@lines, $line) if !$readignore;
  132. }
  133. }
  134. if($building == 1) {
  135. push(@lines, $built);
  136. }
  137. }
  138. close(FILE);
  139. $lineMapping{0} = ["$filename", 0] if scalar keys %lineMapping == 0;
  140. my $lineno = 0;
  141. my $defaultGroup = Group->new();
  142. $defaultGroup->name("_ungrouped");
  143. $defaultGroup->explicit(0);
  144. my $staticClassGroup = StaticClassGroup->new();
  145. my @groups = ($defaultGroup);
  146. my $currentGroup = $defaultGroup;
  147. my $currentClass = undef;
  148. my $currentMethod = undef;
  149. my $newMethodTypeEncoding = undef;
  150. my $currentFunction = undef;
  151. my %classes = ();
  152. my @nestingstack = ();
  153. my @firstDirectivePosition;
  154. my @lastInitPosition;
  155. my %depthMapping = ("0:0" => 0);
  156. my $depth = 0;
  157. # Mk. I processing loop - directive processing.
  158. foreach my $line (@lines) {
  159. # We don't want to process in-order, so %group %thing %end won't kill itself automatically
  160. # because it found a %end with the %group. This allows things to proceed out-of-order:
  161. # we re-start the scan loop with the next % every time we find a match so that the commands don't need to
  162. # be in the processed order on every line. That would be pointless.
  163. my @quotes = quotes($line);
  164. # Brace Depth Mapping
  165. pos($line) = 0;
  166. my %depthsForCurrentLine;
  167. $depthsForCurrentLine{"$lineno:0"} = $depth;
  168. while($line =~ /([{}]|(?<=@)(interface|implementation|protocol|end))/g) {
  169. next if fallsBetween($-[0], @quotes);
  170. my $depthtoken = $lineno.":".($-[0]+1);
  171. $depth++ if($& eq "{");
  172. $depth++ if($& eq "implementation");
  173. $depth++ if($& eq "interface");
  174. # @protocol, but not "@protocol X;" or "@protocol("
  175. $depth++ if($& eq "protocol" && substr($line, $-[0]) !~ /^protocol(\s+([_\$A-Za-z0-9]+(,\s*)?)+;|\s*\()/);
  176. $depth-- if($& eq "}");
  177. $depth-- if($& eq "end");
  178. fileError($lineno, "fell off the face of the planet when we found a \'$&\'") if $depth < 0;
  179. $depthMapping{$depthtoken} = $depth;
  180. $depthsForCurrentLine{$depthtoken} = $depth;
  181. }
  182. # Directive
  183. pos($line) = 0;
  184. while($line =~ m/\B(?=(\%\w|&\s*\%\w|[+-]\s*\(\s*.*?\s*\)))/gc) {
  185. next if fallsBetween($-[0], @quotes);
  186. my @directiveDepthTokens = locationOpeningDepthAtPositionInMapping(\%depthsForCurrentLine, $lineno, $-[0]);
  187. my $directiveDepth;
  188. $directiveDepth = 0 if(!@directiveDepthTokens);
  189. $directiveDepth = $depthsForCurrentLine{join(':', @directiveDepthTokens)} if @directiveDepthTokens;
  190. if($line =~ /\G%hook\s+([\$_\w]+)/gc) {
  191. # "%hook <identifier>"
  192. fileError($lineno, "%hook does not make sense inside a block") if($directiveDepth >= 1);
  193. nestingMustNotContain($lineno, "%hook", \@nestingstack, "hook", "subclass");
  194. @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
  195. nestPush("hook", $lineno, \@nestingstack);
  196. $currentClass = $currentGroup->addClassNamed($1);
  197. $classes{$currentClass->name}++;
  198. patchHere(undef);
  199. } elsif($line =~ /\G%subclass\s+([\$_\w]+)\s*:\s*([\$_\w]+)\s*(\<\s*(.*?)\s*\>)?/gc) {
  200. # %subclass <identifier> : <identifier> \<<protocols ...>\>
  201. fileError($lineno, "%subclass does not make sense inside a block") if($directiveDepth >= 1);
  202. nestingMustNotContain($lineno, "%subclass", \@nestingstack, "hook", "subclass");
  203. @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
  204. nestPush("subclass", $lineno, \@nestingstack);
  205. my $classname = $1;
  206. my $superclassname = $2;
  207. my $superclass = $currentGroup->addClassNamed($superclassname);
  208. $superclass->required(1);
  209. $currentClass = Subclass->new();
  210. $currentClass->name($classname);
  211. $currentClass->superclass($superclass);
  212. if(defined($3) && defined($4)) {
  213. my @protocols = split(/\s*,\s*/, $4);
  214. foreach(@protocols) {
  215. $currentClass->addProtocol($_);
  216. }
  217. }
  218. $currentGroup->addClass($currentClass);
  219. $staticClassGroup->addDeclaredOnlyClass($classname);
  220. $classes{$superclassname}++;
  221. $classes{$classname}++;
  222. patchHere(undef);
  223. } elsif($line =~ /\G%group\s+([\$_\w]+)/gc) {
  224. # %group <identifier>
  225. fileError($lineno, "%group does not make sense inside a block") if($directiveDepth >= 1);
  226. nestingMustNotContain($lineno, "%group", \@nestingstack, "group");
  227. @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
  228. nestPush("group", $lineno, \@nestingstack);
  229. $currentGroup = getGroup($1);
  230. my $patchSource = undef;
  231. if(!defined($currentGroup)) {
  232. $currentGroup = Group->new();
  233. $currentGroup->name($1);
  234. push(@groups, $currentGroup);
  235. my $capturedGroup = $currentGroup;
  236. $patchSource = Patch::Source::Generator->new($capturedGroup, 'declarations');
  237. }
  238. patchHere($patchSource);
  239. } elsif($line =~ /\G%class\s+([+-])?([\$_\w]+)/gc) {
  240. # %class [+-]<identifier>
  241. @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
  242. fileWarning($lineno, "%class is deprecated and will be removed in the future; consider switching to inline %c()");
  243. my $scope = $1;
  244. $scope = "-" if !$scope;
  245. my $classname = $2;
  246. if($scope eq "+") {
  247. $staticClassGroup->addUsedMetaClass($classname);
  248. } else {
  249. $staticClassGroup->addUsedClass($classname);
  250. }
  251. $classes{$classname}++;
  252. patchHere(undef);
  253. } elsif($line =~ /\G%c\(\s*([+-])?([\$_\w]+)\s*\)/gc) {
  254. # %c([+-]<identifier>)
  255. @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
  256. my $scope = $1;
  257. $scope = "-" if !$scope;
  258. my $classname = $2;
  259. if($scope eq "+") {
  260. $staticClassGroup->addUsedMetaClass($classname);
  261. } else {
  262. $staticClassGroup->addUsedClass($classname);
  263. }
  264. $classes{$classname}++;
  265. patchHere(Patch::Source::Generator->new($classname, 'classReferenceWithScope', $scope));
  266. } elsif($line =~ /\G%new(\((.*?)\))?(?=\W?)/gc) {
  267. # %new[(type)]
  268. nestingMustContain($lineno, "%new", \@nestingstack, "hook", "subclass");
  269. my $xtype = "";
  270. $xtype = $2 if $2;
  271. $newMethodTypeEncoding = $xtype;
  272. patchHere(undef);
  273. } elsif($currentClass && $line =~ /\G([+-])\s*\(\s*(.*?)\s*\)(?=\s*[\$\w:])/gc && $directiveDepth < 1) {
  274. # [+-] (<return>)<[X:]>, but only when we're in a %hook.
  275. # Gasp! We've been moved to a different group!
  276. if($currentClass->group != $currentGroup) {
  277. my $classname = $currentClass->name;
  278. $currentClass = $currentGroup->addClassNamed($classname);
  279. }
  280. my $scope = $1;
  281. my $return = $2;
  282. my $method = Method->new();
  283. $method->class($currentClass);
  284. if($scope eq "+") {
  285. $currentClass->hasmetahooks(1);
  286. } else {
  287. $currentClass->hasinstancehooks(1);
  288. }
  289. $method->scope($scope);
  290. $method->return($return);
  291. if(defined $newMethodTypeEncoding) {
  292. $method->setNew(1);
  293. $method->type($newMethodTypeEncoding);
  294. $newMethodTypeEncoding = undef;
  295. }
  296. my @selparts = ();
  297. my $patchStart = $-[0];
  298. # word, then an optional: ": (argtype)argname"
  299. while($line =~ /\G\s*([\$\w]*)(\s*:\s*(\((.+?)\))?\s*([\$\w]+?)\b)?/gc) {
  300. if(!$1 && !$2) { # Exit the loop if both Keywords and Args are missing: e.g. false positive.
  301. pos($line) = $-[0];
  302. last;
  303. }
  304. my $keyword = $1; # Add any keyword.
  305. push(@selparts, $keyword);
  306. last if !$2; # Exit the loop if there are no args (single keyword.)
  307. $method->addArgument($3 ? $4 : "id", $5);
  308. }
  309. $method->selectorParts(@selparts);
  310. $currentClass->addMethod($method);
  311. $currentMethod = $method;
  312. my $patch = Patch->new();
  313. $patch->line($lineno);
  314. $patch->range($patchStart, pos($line));
  315. $patch->source(Patch::Source::Generator->new($method, 'definition'));
  316. addPatch($patch);
  317. } elsif($line =~ /\G%orig\b/gc) {
  318. # %orig, with optional following parens.
  319. if (!defined $currentClass) {
  320. fileError($lineno, "%orig does not make sense outside a function") if(!defined($currentFunction));
  321. my $patchStart = $-[0];
  322. my $remaining = substr($line, pos($line));
  323. my $orig_args = undef;
  324. my ($popen, $pclose) = matchedParenthesisSet($remaining);
  325. if(defined $popen) {
  326. $orig_args = substr($remaining, $popen, $pclose-$popen-1);;
  327. pos($line) = pos($line) + $pclose;
  328. }
  329. my $patch = Patch->new();
  330. $patch->line($lineno);
  331. $patch->range($patchStart, pos($line));
  332. $patch->source(Patch::Source::Generator->new($currentFunction, 'originalFunctionCall', $orig_args));
  333. addPatch($patch);
  334. } else {
  335. nestingMustContain($lineno, "%orig", \@nestingstack, "hook", "subclass");
  336. fileError($lineno, "%orig does not make sense outside a method") if(!defined($currentMethod));
  337. fileError($lineno, "%orig does not make sense outside a block") if($directiveDepth < 1);
  338. fileWarning($lineno, "%orig in new method ".prettyPrintMethod($currentMethod)." will be non-operative.") if $currentMethod->isNew;
  339. my $patchStart = $-[0];
  340. my $remaining = substr($line, pos($line));
  341. my $orig_args = undef;
  342. my ($popen, $pclose) = matchedParenthesisSet($remaining);
  343. if(defined $popen) {
  344. $orig_args = substr($remaining, $popen, $pclose-$popen-1);;
  345. pos($line) = pos($line) + $pclose;
  346. }
  347. my $capturedMethod = $currentMethod;
  348. my $patch = Patch->new();
  349. $patch->line($lineno);
  350. $patch->range($patchStart, pos($line));
  351. $patch->source(Patch::Source::Generator->new($capturedMethod, 'originalCall', $orig_args));
  352. addPatch($patch);
  353. }
  354. } elsif($line =~ /\G&\s*%orig\b/gc) {
  355. # &%orig, at a word boundary
  356. if (!defined $currentClass) {
  357. fileError($lineno, "%orig does not make sense outside a function") if(!defined($currentFunction));
  358. my $patchStart = $-[0];
  359. my $patchEnd = $patchStart + 6;
  360. my $patch = Patch->new();
  361. $patch->line($lineno);
  362. $patch->range($patchStart, $patchEnd);
  363. $patch->source(Patch::Source::Generator->new($currentFunction, 'originalFunctionName'));
  364. addPatch($patch);
  365. } else {
  366. nestingMustContain($lineno, "%orig", \@nestingstack, "hook", "subclass");
  367. fileError($lineno, "%orig does not make sense outside a method") if(!defined($currentMethod));
  368. fileError($lineno, "%orig does not make sense outside a block") if($directiveDepth < 1);
  369. fileError($lineno, "no original method pointer for &%orig in new method ".prettyPrintMethod($currentMethod).".") if $currentMethod->isNew;
  370. my $capturedMethod = $currentMethod;
  371. patchHere(Patch::Source::Generator->new($capturedMethod, 'originalFunctionName'));
  372. }
  373. } elsif($line =~ /\G%log\b/gc) {
  374. # %log
  375. nestingMustContain($lineno, "%log", \@nestingstack, "hook", "subclass");
  376. my $patchStart = $-[0];
  377. my $remaining = substr($line, pos($line));
  378. my $log_args = undef;
  379. my ($popen, $pclose) = matchedParenthesisSet($remaining);
  380. if(defined $popen) {
  381. $log_args = substr($remaining, $popen, $pclose-$popen-1);
  382. pos($line) = pos($line) + $pclose;
  383. }
  384. my $capturedMethod = $currentMethod;
  385. my $patch = Patch->new();
  386. $patch->line($lineno);
  387. $patch->range($patchStart, pos($line));
  388. $patch->source(Patch::Source::Generator->new($capturedMethod, 'buildLogCall', $log_args));
  389. addPatch($patch);
  390. } elsif($line =~ /\G%ctor\b/gc) {
  391. # %ctor
  392. fileError($lineno, "%ctor does not make sense inside a block") if($directiveDepth >= 1);
  393. nestingMustNotContain($lineno, "%ctor", \@nestingstack, "hook", "subclass");
  394. my $replacement = "static __attribute__((constructor)) void _logosLocalCtor_".substr(md5_hex($`.$lineno.$'), 0, 8)."(int argc, char **argv, char **envp)";
  395. patchHere($replacement);
  396. } elsif($line =~ /\G%dtor\b/gc) {
  397. # %dtor
  398. fileError($lineno, "%dtor does not make sense inside a block") if($directiveDepth >= 1);
  399. nestingMustNotContain($lineno, "%dtor", \@nestingstack, "hook", "subclass");
  400. my $replacement = "static __attribute__((destructor)) void _logosLocalDtor_".substr(md5_hex($`.$lineno.$'), 0, 8)."(int argc, char **argv, char **envp)";
  401. patchHere($replacement);
  402. } elsif($line =~ /\G%init\b/gc) {
  403. # %init, with optional following parens
  404. fileError($lineno, "%init does not make sense outside a block") if($directiveDepth < 1);
  405. my $groupname = "_ungrouped";
  406. my $patchStart = $-[0];
  407. my $remaining = substr($line, pos($line));
  408. my $argstring = undef;
  409. my ($popen, $pclose) = matchedParenthesisSet($remaining);
  410. if(defined $popen) {
  411. $argstring = substr($remaining, $popen, $pclose-$popen-1);;
  412. pos($line) = pos($line) + $pclose;
  413. }
  414. my @args;
  415. @args = smartSplit(qr/\s*,\s*/, $argstring) if defined($argstring);
  416. my $tempgroupname = undef;
  417. $tempgroupname = $args[0] if $args[0] && $args[0] !~ /=/;
  418. if(defined($tempgroupname)) {
  419. $groupname = $tempgroupname;
  420. shift(@args);
  421. }
  422. my $group = getGroup($groupname);
  423. fileError($lineno, "%init for an undefined %group $groupname") if !$group;
  424. fileError($lineno, "re-%init of %group ".$group->name.", first initialized at ".lineDescriptionForPhysicalLine($group->initLine)) if $group->initialized;
  425. foreach my $arg (@args) {
  426. if($arg !~ /=/) {
  427. fileWarning($lineno, "unknown argument to %init: $arg");
  428. next;
  429. }
  430. my @parts = smartSplit(qr/\s*=\s*/, $arg, 2);
  431. if(!defined($parts[0]) || !defined($parts[1])) {
  432. fileWarning($lineno, "invalid class=expr in %init");
  433. next;
  434. }
  435. my $classname = $parts[0];
  436. my $expr = $parts[1];
  437. my $scope = "-";
  438. if($classname =~ /^([+-])/) {
  439. $scope = $1;
  440. $classname = $';
  441. }
  442. my $class = $group->getClassNamed($classname);
  443. if(!defined($class)) {
  444. fileWarning($lineno, "tried to set expression for unknown class $classname in group $groupname");
  445. next;
  446. }
  447. $class->expression($expr) if $scope eq "-";
  448. $class->metaexpression($expr) if $scope eq "+";
  449. }
  450. $group->initLine($lineno);
  451. $group->initialized(1);
  452. while($line =~ /\G\s*;/gc) { };
  453. my $patchEnd = pos($line);
  454. my $patch = Patch->new();
  455. $patch->line($lineno);
  456. $patch->range($patchStart, pos($line));
  457. $patch->source(Patch::Source::Generator->new($group, 'initializers'));
  458. addPatch($patch);
  459. @lastInitPosition = ($lineno, pos($line));
  460. } elsif($line =~ /\G%end\b/gc) {
  461. # %end
  462. fileError($lineno, "%end does not make sense inside a block") if($directiveDepth >= 1);
  463. my $closing = nestPop(\@nestingstack);
  464. fileError($lineno, "dangling %end") if !$closing;
  465. if($closing eq "group") {
  466. $currentGroup = getGroup("_ungrouped");
  467. }
  468. if($closing eq "hook" || $closing eq "subclass") {
  469. $currentClass = undef;
  470. }
  471. patchHere(undef);
  472. } elsif($line =~ /\G%config\s*\(\s*(\w+)\s*=\s*(.*?)\s*\)/gc) {
  473. $main::CONFIG{$1} = $2;
  474. patchHere(undef);
  475. } elsif($line =~ /\G%property\s*(?:\((\s*\w+\s*(?:,\s*(?:\w|\=|:)+\s*)*)\))?\s*((?:\w+\s+\**)+)(\w+)\s*;/gc){
  476. nestingMustContain($lineno, "%property", \@nestingstack, "hook", "subclass");
  477. # check property attribute validity
  478. my @attributes = split/\(?\s*,\s*\)?/, $1;
  479. my ($assign, $retain, $copy, $nonatomic, $getter, $setter);
  480. my $numattr = 0;
  481. foreach(@attributes){
  482. $numattr++;
  483. if($_ =~ /assign/){
  484. $assign = 1;
  485. }elsif($_ =~ /retain/){
  486. $retain = 1;
  487. }elsif($_ =~ /copy/){
  488. $copy = 1;
  489. }elsif($_ =~ /nonatomic/){
  490. $nonatomic = 1;
  491. }elsif($_ =~ /getter=(\w+)/){
  492. $getter = $1;
  493. }elsif($_ =~ /setter=(\w+:)/){
  494. $setter = $1;
  495. }elsif($_ =~ /readwrite|readonly/){
  496. fileError($lineno, "property attribute '".$_."' not supported.");
  497. }else{
  498. fileError($lineno, "unknown property attribute '".$_."'.");
  499. }
  500. }
  501. if(!$assign && !$retain && !$copy){
  502. fileWarning($lineno, "no 'assign', 'retain', or 'copy' attribute is specified - 'assign' is assumed");
  503. push(@attributes, "assign");
  504. $numattr++;
  505. }
  506. if($assign && $retain){
  507. fileError($lineno, "property attributes 'assign' and 'retain' are mutually exclusive.");
  508. }
  509. if($assign && $copy){
  510. fileError($lineno, "property attributes 'assign' and 'copy' are mutually exclusive.");
  511. }
  512. if($copy && $retain){
  513. fileError($lineno, "property attributes 'copy' and 'retain' are mutually exclusive.");
  514. }
  515. my $property = Property->new();
  516. $property->class($currentClass->name);
  517. if($currentGroup){
  518. $property->group($currentGroup->name);
  519. }else{
  520. $property->group("_ungrouped");
  521. }
  522. $property->numattr($numattr);
  523. $property->attributes(@attributes);
  524. $property->type($2);
  525. $property->name($3);
  526. $currentClass->addProperty($property);
  527. my $patchStart = $-[0];
  528. my $patch = Patch->new();
  529. $patch->line($lineno);
  530. $patch->range($patchStart, pos($line));
  531. $patch->source(Patch::Source::Generator->new($property, 'getters_setters'));
  532. addPatch($patch);
  533. } elsif($line =~ /\G%hookf\b/gc) {
  534. #%hookf
  535. fileError($lineno, "%hookf does not make sense inside a block") if($directiveDepth >= 1);
  536. nestingMustNotContain($lineno, "%hookf", \@nestingstack, "hook", "subclass");
  537. @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
  538. my $patchStart = $-[0];
  539. my $remaining = substr($line, pos($line));
  540. my $argumentString = undef;
  541. my $args = [];
  542. my ($popen, $pclose) = matchedParenthesisSet($remaining);
  543. if(defined $popen) {
  544. $argumentString = substr($remaining, $popen, $pclose-$popen-1);
  545. pos($line) = pos($line) + $pclose;
  546. @$args = Logos::Util::smartSplit(qr/\s*,\s*/, $argumentString);
  547. }
  548. $currentFunction = $currentGroup->addFunction($args);
  549. my $patch = Patch->new();
  550. $patch->line($lineno);
  551. $patch->range($patchStart, pos($line));
  552. $patch->source(Patch::Source::Generator->new($currentFunction, 'declaration'));
  553. addPatch($patch);
  554. }
  555. }
  556. $lineno++;
  557. }
  558. while(scalar(@nestingstack) > 0) {
  559. my $closing = pop(@nestingstack);
  560. my @parts = split(/:/, $closing);
  561. fileWarning($lineno, "missing %end (%".$parts[0]." opened at ".lineDescriptionForPhysicalLine($parts[1])." extends to EOF)");
  562. }
  563. Logos::Generator::use($main::CONFIG{"generator"});
  564. my $hasGeneratorPreamble = $preprocessed; # If we're already preprocessed, we cannot insert #include statements.
  565. $hasGeneratorPreamble = Logos::Generator::for->findPreamble(\@lines) if !$hasGeneratorPreamble;
  566. if(@firstDirectivePosition) {
  567. # Loop until we find a blank line at depth 0 to splice our preamble in.
  568. # The top of the file (or, alternatively, the first line of our file post-
  569. # preprocessing) will be considered to be a blank line.
  570. #
  571. # This breaks if one includes a blank line between "int blah()" and its
  572. # corresponding "{", however. Nobody codes like that anyway.
  573. # This will probably also break if you keep your "{" and "}" inside header files
  574. # that you #include into your code. Nobody codes like that, either.
  575. # Optimization: Only do this once.
  576. my @depthKeys = sort {
  577. my @ba=split(/:/,$b);
  578. my @aa=split(/:/,$a);
  579. ($ba[0] == $aa[0]
  580. ? $ba[1] <=> $aa[1]
  581. : $ba[0] <=> $aa[0])
  582. } keys %depthMapping;
  583. my $line = $firstDirectivePosition[0];
  584. my $pos = $firstDirectivePosition[1];
  585. while(1) {
  586. my $depth = lookupDepthMapping($line, $pos, \@depthKeys);
  587. my $above;
  588. $above = "" if $line eq 0;
  589. if($preprocessed) {
  590. my @lm = lookupLineMapping($line);
  591. $above = "" if($lm[0] eq $filename && $lm[1] == 1);
  592. }
  593. $above = $lines[$line-1] if !defined $above;
  594. last if $depth == 0 && $above =~ /^\s*$/;
  595. $line-- if($pos == 0);
  596. $pos = 0;
  597. }
  598. my $patch = Patch->new();
  599. $patch->line($line);
  600. my @patchsource = ();
  601. push(@patchsource, Patch::Source::Generator->new(undef, 'preamble', $hasGeneratorPreamble));
  602. push(@patchsource, Patch::Source::Generator->new(undef, 'generateClassList', keys %classes));
  603. push(@patchsource, Patch::Source::Generator->new($groups[0], 'declarations'));
  604. push(@patchsource, Patch::Source::Generator->new($staticClassGroup, 'declarations'));
  605. $patch->source(\@patchsource);
  606. addPatch($patch);
  607. if(!@lastInitPosition) {
  608. # If we haven't seen any %init directives, generate the default constructor.
  609. my $patch = Patch->new();
  610. $patch->line(scalar @lines);
  611. $patch->squash(1);
  612. $patch->source(defaultConstructorSource());
  613. addPatch($patch);
  614. }
  615. }
  616. my @unInitGroups = map {$_->name;} (grep {!$_->initialized && $_->initRequired;} @groups);
  617. fileError($lineno, "non-initialized hook group".(scalar @unInitGroups == 1 ? "" : "s").": ".join(", ", @unInitGroups)) if scalar @unInitGroups > 0;
  618. my @sortedPatches = sort { ($b->line == $a->line ? ($b->start || -1) <=> ($a->start || -1) : $b->line <=> $a->line) } @patches;
  619. if(exists $main::CONFIG{"dump"}) {
  620. my $dumphref = {
  621. linemap=>\%lineMapping,
  622. depthmap=>\%depthMapping,
  623. groups=>\@groups,
  624. patches=>\@patches,
  625. lines=>\@lines,
  626. config=>\%::CONFIG
  627. };
  628. if($main::CONFIG{"dump"} eq "yaml") {
  629. load 'YAML::Syck';
  630. print STDERR YAML::Syck::Dump($dumphref);
  631. }
  632. #print STDERR Data::Dumper->Dump([\@groups, \@patches, \@lines, \%::CONFIG], [qw(groups patches lines config)]);
  633. }
  634. if($main::warnings > 0 && exists $main::CONFIG{"warnings"} && $main::CONFIG{"warnings"} eq "error") {
  635. exit(1);
  636. }
  637. for(@sortedPatches) {
  638. $_->apply(\@lines);
  639. }
  640. splice(@lines, 0, 0, generateLineDirectiveForPhysicalLine(0)) if !$preprocessed;
  641. foreach my $oline (@lines) {
  642. print $oline."\n" if defined($oline);
  643. }
  644. sub defaultConstructorSource {
  645. my @return;
  646. my @initRequiredGroups = grep {$_->initRequired;} @groups;
  647. my @explicitGroups = grep {$_->explicit;} @initRequiredGroups;
  648. fileError($lineno, "Cannot generate an autoconstructor with multiple %groups. Please explicitly create a constructor.") if scalar @explicitGroups > 0;
  649. if(scalar @initRequiredGroups > 0) {
  650. push(@return, "static __attribute__((constructor)) void _logosLocalInit() {\n");
  651. foreach(@initRequiredGroups) {
  652. fileError($lineno, "re-%init of %group ".$_->name.", first initialized at ".lineDescriptionForPhysicalLine($_->initLine)) if $_->initialized;
  653. push(@return, Patch::Source::Generator->new($_, 'initializers'));
  654. push(@return, " ");
  655. $_->initLine($lineno);
  656. $_->initialized(1);
  657. }
  658. push(@return, "}\n");
  659. }
  660. return @return > 0 ? \@return : undef;
  661. }
  662. sub fileWarning {
  663. my $curline = shift;
  664. my $reason = shift;
  665. my @lineMap = lookupLineMapping($curline);
  666. my $filename = $lineMap[0];
  667. my $print = 1;
  668. if(exists($main::CONFIG{"warnings"})) {
  669. if($main::CONFIG{"warnings"} eq "error") {
  670. if($main::warnings == 0) {
  671. print STDERR "logos: warnings being treated as errors\n";
  672. }
  673. } elsif($main::CONFIG{"warnings"} eq "none") {
  674. $print = 0;
  675. }
  676. }
  677. print STDERR "$filename:".($curline > -1 ? $lineMap[1].":" : "")." warning: $reason\n" if($print == 1);
  678. $main::warnings++;
  679. }
  680. sub fileError {
  681. my $curline = shift;
  682. my $reason = shift;
  683. my @lineMap = lookupLineMapping($curline);
  684. my $filename = $lineMap[0];
  685. die "$filename:".($curline > -1 ? $lineMap[1].":" : "")." error: $reason\n";
  686. }
  687. sub nestingError {
  688. my $curline = shift;
  689. my $thisblock = shift;
  690. my $reason = shift;
  691. my @parts = split(/:/, $reason);
  692. fileError $curline, "$thisblock inside a %".$parts[0].", opened at ".lineDescriptionForPhysicalLine($parts[1]);
  693. }
  694. sub nestingMustContain {
  695. my $lineno = shift;
  696. my $trying = shift;
  697. my $stackref = shift;
  698. return if nestingContains($stackref, @_);
  699. fileError($lineno, "$trying found outside of ".join(" or ", @_));
  700. }
  701. sub nestingMustNotContain {
  702. my $lineno = shift;
  703. my $trying = shift;
  704. my $stackref = shift;
  705. nestingError($lineno, $trying, $_) if nestingContains($stackref, @_);
  706. }
  707. sub nestingContains {
  708. my $stackref = shift;
  709. my @stack = @$stackref;
  710. my @search = @_;
  711. my @parts = ();
  712. foreach my $nest (@stack) {
  713. @parts = split(/:/, $nest);
  714. foreach my $find (@search) {
  715. if($find eq $parts[0]) {
  716. $_ = $nest;
  717. return $_;
  718. }
  719. }
  720. }
  721. $_ = undef;
  722. return undef;
  723. }
  724. sub nestPush {
  725. my $type = shift;
  726. my $line = shift;
  727. my $ref_stack = shift;
  728. push(@{$ref_stack}, $type.":".$line);
  729. }
  730. sub nestPop {
  731. my $ref_stack = shift;
  732. my $outgoing = pop(@{$ref_stack});
  733. return undef if !$outgoing;
  734. my @parts = split(/:/, $outgoing);
  735. return $parts[0];
  736. }
  737. sub getGroup {
  738. my $name = shift;
  739. foreach(@groups) {
  740. return $_ if $_->name eq $name;
  741. }
  742. return undef;
  743. }
  744. sub lookupLineMapping {
  745. my $fileline = shift;
  746. $fileline++;
  747. for (sort {$b <=> $a} keys %lineMapping) {
  748. if($fileline >= $_) {
  749. my @x = @{$lineMapping{$_}};
  750. return ($x[0], $x[1] + ($fileline-$_));
  751. }
  752. }
  753. return undef;
  754. }
  755. sub generateLineDirectiveForPhysicalLine {
  756. my $physline = shift;
  757. my @lineMap = lookupLineMapping($physline);
  758. my $filename = $lineMap[0];
  759. my $lineno = $lineMap[1];
  760. return ($preprocessed ? "# " : "#line ").$lineno." \"$filename\"";
  761. }
  762. sub lineDescriptionForPhysicalLine {
  763. my $physline = shift;
  764. my @lineMap = lookupLineMapping($physline);
  765. my $filename = $lineMap[0];
  766. my $lineno = $lineMap[1];
  767. return "$filename:$lineno";
  768. }
  769. sub locationOpeningDepthAtPositionInMapping {
  770. my $dref = shift;
  771. my $fileline = shift;
  772. my $pos = shift;
  773. my $kref = shift;
  774. my @keys;
  775. if($kref) {
  776. @keys = @$kref;
  777. } else {
  778. @keys = sort {
  779. my @ba=split(/:/,$b);
  780. my @aa=split(/:/,$a);
  781. ($ba[0] == $aa[0]
  782. ? $ba[1] <=> $aa[1]
  783. : $ba[0] <=> $aa[0])
  784. } keys %$dref;
  785. }
  786. for (@keys) {
  787. my @depthTokens = split(/:/, $_);
  788. if($fileline > $depthTokens[0] || ($fileline == $depthTokens[0] && $pos >= $depthTokens[1])) {
  789. return @depthTokens;
  790. }
  791. }
  792. return undef;
  793. }
  794. sub lookupDepthMapping {
  795. my $fileline = shift;
  796. my $pos = shift;
  797. my $kref = shift;
  798. my @depthTokens = locationOpeningDepthAtPositionInMapping(\%depthMapping, $fileline, $pos, $kref);
  799. return 0 if(!@depthTokens);
  800. return $depthMapping{join(':', @depthTokens)};
  801. }
  802. sub patchHere {
  803. my $source = shift;
  804. my $patch = Patch->new();
  805. $patch->line($lineno);
  806. $patch->range($-[0], $+[0]);
  807. $patch->source($source);
  808. push @patches, $patch;
  809. }
  810. sub addPatch {
  811. my $patch = shift;
  812. push @patches, $patch;
  813. }
  814. sub prettyPrintMethod {
  815. my $method = shift;
  816. return $method->scope."[".$method->class->name." ".$method->selector."]";
  817. }
  818. sub utilErrorHandler {
  819. fileError($lineno, shift);
  820. }