123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968 |
- #!/usr/bin/perl
- use 5.006;
- use warnings;
- use strict;
- use FindBin;
- use lib "$FindBin::Bin/lib";
- use Digest::MD5 'md5_hex';
- use Module::Load;
- use Module::Load::Conditional 'can_load';
- use Getopt::Long;
- package Logos;
- sub sigil { my $id = shift; return "_logos_$id\$"; }
- package main;
- use Logos::Util;
- $Logos::Util::errorhandler = \&utilErrorHandler;
- use aliased 'Logos::Patch';
- use aliased 'Logos::Patch::Source::Generator' => 'Patch::Source::Generator';
- use aliased 'Logos::Patch';
- use aliased 'Logos::Group';
- use aliased 'Logos::Method';
- use aliased 'Logos::Class';
- use aliased 'Logos::Subclass';
- use aliased 'Logos::StaticClassGroup' ;
- use aliased 'Logos::Property';
- use aliased 'Logos::Function';
- use Logos::Generator;
- %main::CONFIG = ( generator => "MobileSubstrate",
- warnings => "default",
- );
- $main::warnings = 0;
- GetOptions("config|c=s" => \%main::CONFIG);
- my $filename = $ARGV[0];
- die "Syntax: $FindBin::Script filename\n" if !$filename;
- open(FILE, $filename) or die "Could not open $filename.\n";
- my @lines = ();
- my @patches = ();
- my $preprocessed = 0;
- my %lineMapping = ();
- { # If the first line matches "# \d \"...\"", this file has been run through the preprocessor already.
- my $firstline = <FILE>;
- seek(FILE, 0, Fcntl::SEEK_SET);
- if($firstline && $firstline =~ /^# \d+ \"(.*?)\"$/) {
- $preprocessed = 1;
- $filename = $1;
- }
- $.--; # Reset line number.
- }
- {
- my $readignore = 0;
- my $built = "";
- my $building = 0;
- my $iflevel = -1;
- READLOOP: while(my $line = <FILE>) {
- chomp($line);
- if($preprocessed && $line =~ /^# (\d+) \"(.*?)\"/) {
- $lineMapping{$.+1} = [$2, $1];
- }
- if($readignore) {
- # Handle #if nesting.
- if($iflevel > -1 && $line =~ /^#\s*if(n?def)?/) {
- $iflevel++;
- } elsif($iflevel > 0 && $line =~ /^#\s*endif/) {
- $line = $';
- $iflevel--;
- }
- # End of a multi-line comment or #if block while ignoring input.
- if($iflevel == 0 || ($iflevel == -1 && $line =~ /^.*?\*\/\s*/)) {
- $readignore = 0;
- $iflevel = -1;
- $line = $';
- }
- }
- if($readignore) { push(@lines, ""); next; }
- my @quotes = quotes($line);
- # Delete all single-line /* xxx */ comments.
- while($line =~ /\/\*.*?\*\//g) {
- next if fallsBetween($-[0], @quotes);
- $line = $`.$';
- redo READLOOP;
- }
- # Start of a multi-line /* comment.
- while($line =~ /\/\*.*$/g) {
- next if fallsBetween($-[0], @quotes);
- $line = $`;
- push(@lines, $line);
- $readignore = 1;
- next READLOOP;
- }
- # Delete all single-line to-EOL // xxx comments.
- while($line =~ /\/\//g) {
- next if fallsBetween($-[0], @quotes);
- $line = $`;
- redo READLOOP;
- }
- # #if 0.
- while($line =~ /^\s*#\s*if\s+0/) {
- $iflevel = 1;
- push(@lines, "");
- $readignore = 1;
- next READLOOP;
- }
- if(!$readignore) {
- # Line starts with - (return), start gluing lines together until we find a { or ;...
- if(!$building
- && (
- $line =~ /^\s*(%new.*?)?\s*([+-])\s*\(\s*(.*?)\s*\)/
- || $line =~ /%orig[^;]*$/
- || $line =~ /%init[^;]*$/
- )
- && index($line, "{") < $-[0] && index($line, ";") < $-[0]) {
- if(fallsBetween($-[0], @quotes)) {
- push(@lines, $line);
- next;
- }
- $building = 1;
- $built = $line;
- push(@lines, "");
- next;
- } elsif($building) {
- $line =~ s/^\s+//g;
- $built .= " ".$line;
- if(index($line,"{") != -1 || index($line,";") != -1) {
- push(@lines, $built);
- $building = 0;
- $built = "";
- next;
- }
- push(@lines, "");
- next;
- }
- push(@lines, $line) if !$readignore;
- }
- }
- if($building == 1) {
- push(@lines, $built);
- }
- }
- close(FILE);
- $lineMapping{0} = ["$filename", 0] if scalar keys %lineMapping == 0;
- my $lineno = 0;
- my $defaultGroup = Group->new();
- $defaultGroup->name("_ungrouped");
- $defaultGroup->explicit(0);
- my $staticClassGroup = StaticClassGroup->new();
- my @groups = ($defaultGroup);
- my $currentGroup = $defaultGroup;
- my $currentClass = undef;
- my $currentMethod = undef;
- my $newMethodTypeEncoding = undef;
- my $currentFunction = undef;
- my %classes = ();
- my @nestingstack = ();
- my @firstDirectivePosition;
- my @lastInitPosition;
- my %depthMapping = ("0:0" => 0);
- my $depth = 0;
- # Mk. I processing loop - directive processing.
- foreach my $line (@lines) {
- # We don't want to process in-order, so %group %thing %end won't kill itself automatically
- # because it found a %end with the %group. This allows things to proceed out-of-order:
- # we re-start the scan loop with the next % every time we find a match so that the commands don't need to
- # be in the processed order on every line. That would be pointless.
- my @quotes = quotes($line);
- # Brace Depth Mapping
- pos($line) = 0;
- my %depthsForCurrentLine;
- $depthsForCurrentLine{"$lineno:0"} = $depth;
- while($line =~ /([{}]|(?<=@)(interface|implementation|protocol|end))/g) {
- next if fallsBetween($-[0], @quotes);
- my $depthtoken = $lineno.":".($-[0]+1);
- $depth++ if($& eq "{");
- $depth++ if($& eq "implementation");
- $depth++ if($& eq "interface");
- # @protocol, but not "@protocol X;" or "@protocol("
- $depth++ if($& eq "protocol" && substr($line, $-[0]) !~ /^protocol(\s+([_\$A-Za-z0-9]+(,\s*)?)+;|\s*\()/);
- $depth-- if($& eq "}");
- $depth-- if($& eq "end");
- fileError($lineno, "fell off the face of the planet when we found a \'$&\'") if $depth < 0;
- $depthMapping{$depthtoken} = $depth;
- $depthsForCurrentLine{$depthtoken} = $depth;
- }
- # Directive
- pos($line) = 0;
- while($line =~ m/\B(?=(\%\w|&\s*\%\w|[+-]\s*\(\s*.*?\s*\)))/gc) {
- next if fallsBetween($-[0], @quotes);
- my @directiveDepthTokens = locationOpeningDepthAtPositionInMapping(\%depthsForCurrentLine, $lineno, $-[0]);
- my $directiveDepth;
- $directiveDepth = 0 if(!@directiveDepthTokens);
- $directiveDepth = $depthsForCurrentLine{join(':', @directiveDepthTokens)} if @directiveDepthTokens;
- if($line =~ /\G%hook\s+([\$_\w]+)/gc) {
- # "%hook <identifier>"
- fileError($lineno, "%hook does not make sense inside a block") if($directiveDepth >= 1);
- nestingMustNotContain($lineno, "%hook", \@nestingstack, "hook", "subclass");
- @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
- nestPush("hook", $lineno, \@nestingstack);
- $currentClass = $currentGroup->addClassNamed($1);
- $classes{$currentClass->name}++;
- patchHere(undef);
- } elsif($line =~ /\G%subclass\s+([\$_\w]+)\s*:\s*([\$_\w]+)\s*(\<\s*(.*?)\s*\>)?/gc) {
- # %subclass <identifier> : <identifier> \<<protocols ...>\>
- fileError($lineno, "%subclass does not make sense inside a block") if($directiveDepth >= 1);
- nestingMustNotContain($lineno, "%subclass", \@nestingstack, "hook", "subclass");
- @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
- nestPush("subclass", $lineno, \@nestingstack);
- my $classname = $1;
- my $superclassname = $2;
- my $superclass = $currentGroup->addClassNamed($superclassname);
- $superclass->required(1);
- $currentClass = Subclass->new();
- $currentClass->name($classname);
- $currentClass->superclass($superclass);
- if(defined($3) && defined($4)) {
- my @protocols = split(/\s*,\s*/, $4);
- foreach(@protocols) {
- $currentClass->addProtocol($_);
- }
- }
- $currentGroup->addClass($currentClass);
- $staticClassGroup->addDeclaredOnlyClass($classname);
- $classes{$superclassname}++;
- $classes{$classname}++;
- patchHere(undef);
- } elsif($line =~ /\G%group\s+([\$_\w]+)/gc) {
- # %group <identifier>
- fileError($lineno, "%group does not make sense inside a block") if($directiveDepth >= 1);
- nestingMustNotContain($lineno, "%group", \@nestingstack, "group");
- @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
- nestPush("group", $lineno, \@nestingstack);
- $currentGroup = getGroup($1);
- my $patchSource = undef;
- if(!defined($currentGroup)) {
- $currentGroup = Group->new();
- $currentGroup->name($1);
- push(@groups, $currentGroup);
- my $capturedGroup = $currentGroup;
- $patchSource = Patch::Source::Generator->new($capturedGroup, 'declarations');
- }
- patchHere($patchSource);
- } elsif($line =~ /\G%class\s+([+-])?([\$_\w]+)/gc) {
- # %class [+-]<identifier>
- @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
- fileWarning($lineno, "%class is deprecated and will be removed in the future; consider switching to inline %c()");
- my $scope = $1;
- $scope = "-" if !$scope;
- my $classname = $2;
- if($scope eq "+") {
- $staticClassGroup->addUsedMetaClass($classname);
- } else {
- $staticClassGroup->addUsedClass($classname);
- }
- $classes{$classname}++;
- patchHere(undef);
- } elsif($line =~ /\G%c\(\s*([+-])?([\$_\w]+)\s*\)/gc) {
- # %c([+-]<identifier>)
- @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
- my $scope = $1;
- $scope = "-" if !$scope;
- my $classname = $2;
- if($scope eq "+") {
- $staticClassGroup->addUsedMetaClass($classname);
- } else {
- $staticClassGroup->addUsedClass($classname);
- }
- $classes{$classname}++;
- patchHere(Patch::Source::Generator->new($classname, 'classReferenceWithScope', $scope));
- } elsif($line =~ /\G%new(\((.*?)\))?(?=\W?)/gc) {
- # %new[(type)]
- nestingMustContain($lineno, "%new", \@nestingstack, "hook", "subclass");
- my $xtype = "";
- $xtype = $2 if $2;
- $newMethodTypeEncoding = $xtype;
- patchHere(undef);
- } elsif($currentClass && $line =~ /\G([+-])\s*\(\s*(.*?)\s*\)(?=\s*[\$\w:])/gc && $directiveDepth < 1) {
- # [+-] (<return>)<[X:]>, but only when we're in a %hook.
- # Gasp! We've been moved to a different group!
- if($currentClass->group != $currentGroup) {
- my $classname = $currentClass->name;
- $currentClass = $currentGroup->addClassNamed($classname);
- }
- my $scope = $1;
- my $return = $2;
- my $method = Method->new();
- $method->class($currentClass);
- if($scope eq "+") {
- $currentClass->hasmetahooks(1);
- } else {
- $currentClass->hasinstancehooks(1);
- }
- $method->scope($scope);
- $method->return($return);
- if(defined $newMethodTypeEncoding) {
- $method->setNew(1);
- $method->type($newMethodTypeEncoding);
- $newMethodTypeEncoding = undef;
- }
- my @selparts = ();
- my $patchStart = $-[0];
- # word, then an optional: ": (argtype)argname"
- while($line =~ /\G\s*([\$\w]*)(\s*:\s*(\((.+?)\))?\s*([\$\w]+?)\b)?/gc) {
- if(!$1 && !$2) { # Exit the loop if both Keywords and Args are missing: e.g. false positive.
- pos($line) = $-[0];
- last;
- }
- my $keyword = $1; # Add any keyword.
- push(@selparts, $keyword);
- last if !$2; # Exit the loop if there are no args (single keyword.)
- $method->addArgument($3 ? $4 : "id", $5);
- }
- $method->selectorParts(@selparts);
- $currentClass->addMethod($method);
- $currentMethod = $method;
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($method, 'definition'));
- addPatch($patch);
- } elsif($line =~ /\G%orig\b/gc) {
- # %orig, with optional following parens.
- if (!defined $currentClass) {
- fileError($lineno, "%orig does not make sense outside a function") if(!defined($currentFunction));
- my $patchStart = $-[0];
- my $remaining = substr($line, pos($line));
- my $orig_args = undef;
- my ($popen, $pclose) = matchedParenthesisSet($remaining);
- if(defined $popen) {
- $orig_args = substr($remaining, $popen, $pclose-$popen-1);;
- pos($line) = pos($line) + $pclose;
- }
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($currentFunction, 'originalFunctionCall', $orig_args));
- addPatch($patch);
- } else {
- nestingMustContain($lineno, "%orig", \@nestingstack, "hook", "subclass");
- fileError($lineno, "%orig does not make sense outside a method") if(!defined($currentMethod));
- fileError($lineno, "%orig does not make sense outside a block") if($directiveDepth < 1);
- fileWarning($lineno, "%orig in new method ".prettyPrintMethod($currentMethod)." will be non-operative.") if $currentMethod->isNew;
- my $patchStart = $-[0];
- my $remaining = substr($line, pos($line));
- my $orig_args = undef;
- my ($popen, $pclose) = matchedParenthesisSet($remaining);
- if(defined $popen) {
- $orig_args = substr($remaining, $popen, $pclose-$popen-1);;
- pos($line) = pos($line) + $pclose;
- }
- my $capturedMethod = $currentMethod;
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($capturedMethod, 'originalCall', $orig_args));
- addPatch($patch);
- }
- } elsif($line =~ /\G&\s*%orig\b/gc) {
- # &%orig, at a word boundary
- if (!defined $currentClass) {
- fileError($lineno, "%orig does not make sense outside a function") if(!defined($currentFunction));
- my $patchStart = $-[0];
- my $patchEnd = $patchStart + 6;
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, $patchEnd);
- $patch->source(Patch::Source::Generator->new($currentFunction, 'originalFunctionName'));
- addPatch($patch);
- } else {
- nestingMustContain($lineno, "%orig", \@nestingstack, "hook", "subclass");
- fileError($lineno, "%orig does not make sense outside a method") if(!defined($currentMethod));
- fileError($lineno, "%orig does not make sense outside a block") if($directiveDepth < 1);
- fileError($lineno, "no original method pointer for &%orig in new method ".prettyPrintMethod($currentMethod).".") if $currentMethod->isNew;
- my $capturedMethod = $currentMethod;
- patchHere(Patch::Source::Generator->new($capturedMethod, 'originalFunctionName'));
- }
- } elsif($line =~ /\G%log\b/gc) {
- # %log
- nestingMustContain($lineno, "%log", \@nestingstack, "hook", "subclass");
- my $patchStart = $-[0];
- my $remaining = substr($line, pos($line));
- my $log_args = undef;
- my ($popen, $pclose) = matchedParenthesisSet($remaining);
- if(defined $popen) {
- $log_args = substr($remaining, $popen, $pclose-$popen-1);
- pos($line) = pos($line) + $pclose;
- }
- my $capturedMethod = $currentMethod;
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($capturedMethod, 'buildLogCall', $log_args));
- addPatch($patch);
- } elsif($line =~ /\G%ctor\b/gc) {
- # %ctor
- fileError($lineno, "%ctor does not make sense inside a block") if($directiveDepth >= 1);
- nestingMustNotContain($lineno, "%ctor", \@nestingstack, "hook", "subclass");
- my $replacement = "static __attribute__((constructor)) void _logosLocalCtor_".substr(md5_hex($`.$lineno.$'), 0, 8)."(int argc, char **argv, char **envp)";
- patchHere($replacement);
- } elsif($line =~ /\G%dtor\b/gc) {
- # %dtor
- fileError($lineno, "%dtor does not make sense inside a block") if($directiveDepth >= 1);
- nestingMustNotContain($lineno, "%dtor", \@nestingstack, "hook", "subclass");
- my $replacement = "static __attribute__((destructor)) void _logosLocalDtor_".substr(md5_hex($`.$lineno.$'), 0, 8)."(int argc, char **argv, char **envp)";
- patchHere($replacement);
- } elsif($line =~ /\G%init\b/gc) {
- # %init, with optional following parens
- fileError($lineno, "%init does not make sense outside a block") if($directiveDepth < 1);
- my $groupname = "_ungrouped";
- my $patchStart = $-[0];
- my $remaining = substr($line, pos($line));
- my $argstring = undef;
- my ($popen, $pclose) = matchedParenthesisSet($remaining);
- if(defined $popen) {
- $argstring = substr($remaining, $popen, $pclose-$popen-1);;
- pos($line) = pos($line) + $pclose;
- }
- my @args;
- @args = smartSplit(qr/\s*,\s*/, $argstring) if defined($argstring);
- my $tempgroupname = undef;
- $tempgroupname = $args[0] if $args[0] && $args[0] !~ /=/;
- if(defined($tempgroupname)) {
- $groupname = $tempgroupname;
- shift(@args);
- }
- my $group = getGroup($groupname);
- fileError($lineno, "%init for an undefined %group $groupname") if !$group;
- fileError($lineno, "re-%init of %group ".$group->name.", first initialized at ".lineDescriptionForPhysicalLine($group->initLine)) if $group->initialized;
- foreach my $arg (@args) {
- if($arg !~ /=/) {
- fileWarning($lineno, "unknown argument to %init: $arg");
- next;
- }
- my @parts = smartSplit(qr/\s*=\s*/, $arg, 2);
- if(!defined($parts[0]) || !defined($parts[1])) {
- fileWarning($lineno, "invalid class=expr in %init");
- next;
- }
- my $classname = $parts[0];
- my $expr = $parts[1];
- my $scope = "-";
- if($classname =~ /^([+-])/) {
- $scope = $1;
- $classname = $';
- }
- my $class = $group->getClassNamed($classname);
- if(!defined($class)) {
- fileWarning($lineno, "tried to set expression for unknown class $classname in group $groupname");
- next;
- }
- $class->expression($expr) if $scope eq "-";
- $class->metaexpression($expr) if $scope eq "+";
- }
- $group->initLine($lineno);
- $group->initialized(1);
- while($line =~ /\G\s*;/gc) { };
- my $patchEnd = pos($line);
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($group, 'initializers'));
- addPatch($patch);
- @lastInitPosition = ($lineno, pos($line));
- } elsif($line =~ /\G%end\b/gc) {
- # %end
- fileError($lineno, "%end does not make sense inside a block") if($directiveDepth >= 1);
- my $closing = nestPop(\@nestingstack);
- fileError($lineno, "dangling %end") if !$closing;
- if($closing eq "group") {
- $currentGroup = getGroup("_ungrouped");
- }
- if($closing eq "hook" || $closing eq "subclass") {
- $currentClass = undef;
- }
- patchHere(undef);
- } elsif($line =~ /\G%config\s*\(\s*(\w+)\s*=\s*(.*?)\s*\)/gc) {
- $main::CONFIG{$1} = $2;
- patchHere(undef);
- } elsif($line =~ /\G%property\s*(?:\((\s*\w+\s*(?:,\s*(?:\w|\=|:)+\s*)*)\))?\s*((?:\w+\s+\**)+)(\w+)\s*;/gc){
- nestingMustContain($lineno, "%property", \@nestingstack, "hook", "subclass");
- # check property attribute validity
- my @attributes = split/\(?\s*,\s*\)?/, $1;
- my ($assign, $retain, $copy, $nonatomic, $getter, $setter);
- my $numattr = 0;
- foreach(@attributes){
- $numattr++;
- if($_ =~ /assign/){
- $assign = 1;
- }elsif($_ =~ /retain/){
- $retain = 1;
- }elsif($_ =~ /copy/){
- $copy = 1;
- }elsif($_ =~ /nonatomic/){
- $nonatomic = 1;
- }elsif($_ =~ /getter=(\w+)/){
- $getter = $1;
- }elsif($_ =~ /setter=(\w+:)/){
- $setter = $1;
- }elsif($_ =~ /readwrite|readonly/){
- fileError($lineno, "property attribute '".$_."' not supported.");
- }else{
- fileError($lineno, "unknown property attribute '".$_."'.");
- }
- }
- if(!$assign && !$retain && !$copy){
- fileWarning($lineno, "no 'assign', 'retain', or 'copy' attribute is specified - 'assign' is assumed");
- push(@attributes, "assign");
- $numattr++;
- }
- if($assign && $retain){
- fileError($lineno, "property attributes 'assign' and 'retain' are mutually exclusive.");
- }
- if($assign && $copy){
- fileError($lineno, "property attributes 'assign' and 'copy' are mutually exclusive.");
- }
- if($copy && $retain){
- fileError($lineno, "property attributes 'copy' and 'retain' are mutually exclusive.");
- }
- my $property = Property->new();
- $property->class($currentClass->name);
- if($currentGroup){
- $property->group($currentGroup->name);
- }else{
- $property->group("_ungrouped");
- }
- $property->numattr($numattr);
- $property->attributes(@attributes);
- $property->type($2);
- $property->name($3);
- $currentClass->addProperty($property);
- my $patchStart = $-[0];
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($property, 'getters_setters'));
- addPatch($patch);
- } elsif($line =~ /\G%hookf\b/gc) {
- #%hookf
- fileError($lineno, "%hookf does not make sense inside a block") if($directiveDepth >= 1);
- nestingMustNotContain($lineno, "%hookf", \@nestingstack, "hook", "subclass");
- @firstDirectivePosition = ($lineno, $-[0]) if !@firstDirectivePosition;
- my $patchStart = $-[0];
- my $remaining = substr($line, pos($line));
- my $argumentString = undef;
- my $args = [];
- my ($popen, $pclose) = matchedParenthesisSet($remaining);
- if(defined $popen) {
- $argumentString = substr($remaining, $popen, $pclose-$popen-1);
- pos($line) = pos($line) + $pclose;
- @$args = Logos::Util::smartSplit(qr/\s*,\s*/, $argumentString);
- }
- $currentFunction = $currentGroup->addFunction($args);
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($patchStart, pos($line));
- $patch->source(Patch::Source::Generator->new($currentFunction, 'declaration'));
- addPatch($patch);
- }
- }
- $lineno++;
- }
- while(scalar(@nestingstack) > 0) {
- my $closing = pop(@nestingstack);
- my @parts = split(/:/, $closing);
- fileWarning($lineno, "missing %end (%".$parts[0]." opened at ".lineDescriptionForPhysicalLine($parts[1])." extends to EOF)");
- }
- Logos::Generator::use($main::CONFIG{"generator"});
- my $hasGeneratorPreamble = $preprocessed; # If we're already preprocessed, we cannot insert #include statements.
- $hasGeneratorPreamble = Logos::Generator::for->findPreamble(\@lines) if !$hasGeneratorPreamble;
- if(@firstDirectivePosition) {
- # Loop until we find a blank line at depth 0 to splice our preamble in.
- # The top of the file (or, alternatively, the first line of our file post-
- # preprocessing) will be considered to be a blank line.
- #
- # This breaks if one includes a blank line between "int blah()" and its
- # corresponding "{", however. Nobody codes like that anyway.
- # This will probably also break if you keep your "{" and "}" inside header files
- # that you #include into your code. Nobody codes like that, either.
- # Optimization: Only do this once.
- my @depthKeys = sort {
- my @ba=split(/:/,$b);
- my @aa=split(/:/,$a);
- ($ba[0] == $aa[0]
- ? $ba[1] <=> $aa[1]
- : $ba[0] <=> $aa[0])
- } keys %depthMapping;
- my $line = $firstDirectivePosition[0];
- my $pos = $firstDirectivePosition[1];
- while(1) {
- my $depth = lookupDepthMapping($line, $pos, \@depthKeys);
- my $above;
- $above = "" if $line eq 0;
- if($preprocessed) {
- my @lm = lookupLineMapping($line);
- $above = "" if($lm[0] eq $filename && $lm[1] == 1);
- }
- $above = $lines[$line-1] if !defined $above;
- last if $depth == 0 && $above =~ /^\s*$/;
- $line-- if($pos == 0);
- $pos = 0;
- }
- my $patch = Patch->new();
- $patch->line($line);
- my @patchsource = ();
- push(@patchsource, Patch::Source::Generator->new(undef, 'preamble', $hasGeneratorPreamble));
- push(@patchsource, Patch::Source::Generator->new(undef, 'generateClassList', keys %classes));
- push(@patchsource, Patch::Source::Generator->new($groups[0], 'declarations'));
- push(@patchsource, Patch::Source::Generator->new($staticClassGroup, 'declarations'));
- $patch->source(\@patchsource);
- addPatch($patch);
- if(!@lastInitPosition) {
- # If we haven't seen any %init directives, generate the default constructor.
- my $patch = Patch->new();
- $patch->line(scalar @lines);
- $patch->squash(1);
- $patch->source(defaultConstructorSource());
- addPatch($patch);
- }
- }
- my @unInitGroups = map {$_->name;} (grep {!$_->initialized && $_->initRequired;} @groups);
- fileError($lineno, "non-initialized hook group".(scalar @unInitGroups == 1 ? "" : "s").": ".join(", ", @unInitGroups)) if scalar @unInitGroups > 0;
- my @sortedPatches = sort { ($b->line == $a->line ? ($b->start || -1) <=> ($a->start || -1) : $b->line <=> $a->line) } @patches;
- if(exists $main::CONFIG{"dump"}) {
- my $dumphref = {
- linemap=>\%lineMapping,
- depthmap=>\%depthMapping,
- groups=>\@groups,
- patches=>\@patches,
- lines=>\@lines,
- config=>\%::CONFIG
- };
- if($main::CONFIG{"dump"} eq "yaml") {
- load 'YAML::Syck';
- print STDERR YAML::Syck::Dump($dumphref);
- }
- #print STDERR Data::Dumper->Dump([\@groups, \@patches, \@lines, \%::CONFIG], [qw(groups patches lines config)]);
- }
- if($main::warnings > 0 && exists $main::CONFIG{"warnings"} && $main::CONFIG{"warnings"} eq "error") {
- exit(1);
- }
- for(@sortedPatches) {
- $_->apply(\@lines);
- }
- splice(@lines, 0, 0, generateLineDirectiveForPhysicalLine(0)) if !$preprocessed;
- foreach my $oline (@lines) {
- print $oline."\n" if defined($oline);
- }
- sub defaultConstructorSource {
- my @return;
- my @initRequiredGroups = grep {$_->initRequired;} @groups;
- my @explicitGroups = grep {$_->explicit;} @initRequiredGroups;
- fileError($lineno, "Cannot generate an autoconstructor with multiple %groups. Please explicitly create a constructor.") if scalar @explicitGroups > 0;
- if(scalar @initRequiredGroups > 0) {
- push(@return, "static __attribute__((constructor)) void _logosLocalInit() {\n");
- foreach(@initRequiredGroups) {
- fileError($lineno, "re-%init of %group ".$_->name.", first initialized at ".lineDescriptionForPhysicalLine($_->initLine)) if $_->initialized;
- push(@return, Patch::Source::Generator->new($_, 'initializers'));
- push(@return, " ");
- $_->initLine($lineno);
- $_->initialized(1);
- }
- push(@return, "}\n");
- }
- return @return > 0 ? \@return : undef;
- }
- sub fileWarning {
- my $curline = shift;
- my $reason = shift;
- my @lineMap = lookupLineMapping($curline);
- my $filename = $lineMap[0];
- my $print = 1;
- if(exists($main::CONFIG{"warnings"})) {
- if($main::CONFIG{"warnings"} eq "error") {
- if($main::warnings == 0) {
- print STDERR "logos: warnings being treated as errors\n";
- }
- } elsif($main::CONFIG{"warnings"} eq "none") {
- $print = 0;
- }
- }
- print STDERR "$filename:".($curline > -1 ? $lineMap[1].":" : "")." warning: $reason\n" if($print == 1);
- $main::warnings++;
- }
- sub fileError {
- my $curline = shift;
- my $reason = shift;
- my @lineMap = lookupLineMapping($curline);
- my $filename = $lineMap[0];
- die "$filename:".($curline > -1 ? $lineMap[1].":" : "")." error: $reason\n";
- }
- sub nestingError {
- my $curline = shift;
- my $thisblock = shift;
- my $reason = shift;
- my @parts = split(/:/, $reason);
- fileError $curline, "$thisblock inside a %".$parts[0].", opened at ".lineDescriptionForPhysicalLine($parts[1]);
- }
- sub nestingMustContain {
- my $lineno = shift;
- my $trying = shift;
- my $stackref = shift;
- return if nestingContains($stackref, @_);
- fileError($lineno, "$trying found outside of ".join(" or ", @_));
- }
- sub nestingMustNotContain {
- my $lineno = shift;
- my $trying = shift;
- my $stackref = shift;
- nestingError($lineno, $trying, $_) if nestingContains($stackref, @_);
- }
- sub nestingContains {
- my $stackref = shift;
- my @stack = @$stackref;
- my @search = @_;
- my @parts = ();
- foreach my $nest (@stack) {
- @parts = split(/:/, $nest);
- foreach my $find (@search) {
- if($find eq $parts[0]) {
- $_ = $nest;
- return $_;
- }
- }
- }
- $_ = undef;
- return undef;
- }
- sub nestPush {
- my $type = shift;
- my $line = shift;
- my $ref_stack = shift;
- push(@{$ref_stack}, $type.":".$line);
- }
- sub nestPop {
- my $ref_stack = shift;
- my $outgoing = pop(@{$ref_stack});
- return undef if !$outgoing;
- my @parts = split(/:/, $outgoing);
- return $parts[0];
- }
- sub getGroup {
- my $name = shift;
- foreach(@groups) {
- return $_ if $_->name eq $name;
- }
- return undef;
- }
- sub lookupLineMapping {
- my $fileline = shift;
- $fileline++;
- for (sort {$b <=> $a} keys %lineMapping) {
- if($fileline >= $_) {
- my @x = @{$lineMapping{$_}};
- return ($x[0], $x[1] + ($fileline-$_));
- }
- }
- return undef;
- }
- sub generateLineDirectiveForPhysicalLine {
- my $physline = shift;
- my @lineMap = lookupLineMapping($physline);
- my $filename = $lineMap[0];
- my $lineno = $lineMap[1];
- return ($preprocessed ? "# " : "#line ").$lineno." \"$filename\"";
- }
- sub lineDescriptionForPhysicalLine {
- my $physline = shift;
- my @lineMap = lookupLineMapping($physline);
- my $filename = $lineMap[0];
- my $lineno = $lineMap[1];
- return "$filename:$lineno";
- }
- sub locationOpeningDepthAtPositionInMapping {
- my $dref = shift;
- my $fileline = shift;
- my $pos = shift;
- my $kref = shift;
- my @keys;
- if($kref) {
- @keys = @$kref;
- } else {
- @keys = sort {
- my @ba=split(/:/,$b);
- my @aa=split(/:/,$a);
- ($ba[0] == $aa[0]
- ? $ba[1] <=> $aa[1]
- : $ba[0] <=> $aa[0])
- } keys %$dref;
- }
- for (@keys) {
- my @depthTokens = split(/:/, $_);
- if($fileline > $depthTokens[0] || ($fileline == $depthTokens[0] && $pos >= $depthTokens[1])) {
- return @depthTokens;
- }
- }
- return undef;
- }
- sub lookupDepthMapping {
- my $fileline = shift;
- my $pos = shift;
- my $kref = shift;
- my @depthTokens = locationOpeningDepthAtPositionInMapping(\%depthMapping, $fileline, $pos, $kref);
- return 0 if(!@depthTokens);
- return $depthMapping{join(':', @depthTokens)};
- }
- sub patchHere {
- my $source = shift;
- my $patch = Patch->new();
- $patch->line($lineno);
- $patch->range($-[0], $+[0]);
- $patch->source($source);
- push @patches, $patch;
- }
- sub addPatch {
- my $patch = shift;
- push @patches, $patch;
- }
- sub prettyPrintMethod {
- my $method = shift;
- return $method->scope."[".$method->class->name." ".$method->selector."]";
- }
- sub utilErrorHandler {
- fileError($lineno, shift);
- }
|