mirror of
https://github.com/BishopFox/theos-jailed.git
synced 2026-11-07 10:25:57 -10:00
From: Dustin L. Howett <[email protected]> git-svn-id: http://svn.howett.net/svn/theos/trunk@641 4410221e-0ddf-4ce3-99c0-2db6c0dbc727
829 lines
24 KiB
Perl
Executable File
829 lines
24 KiB
Perl
Executable File
#!/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 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 %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.
|
|
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
|
|
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)."()";
|
|
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);
|
|
}
|
|
}
|
|
|
|
$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')) if !$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);
|
|
}
|