# # The contents of this file are subject to the Netscape Public License # Version 1.0 (the "NPL"); you may not use this file except in # compliance with the NPL. You may obtain a copy of the NPL at # http://www.mozilla.org/NPL/ # # Software distributed under the NPL is distributed on an "AS IS" basis, # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL # for the specific language governing rights and limitations under the # NPL. # # The Initial Developer of this code under the NPL is Netscape # Communications Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All Rights # Reserved. # # # nad.pl # # Takes a nad file (Netscape Architecture Definition) and # produces a header with the Terminals and Named rules in it # and prints the BURG specification to stdout. # # Usage: # perl nad.pl > # # rules in BURG must be >= 1 # terminals in BURG must be >= 1 # Fields of a $gPrimitiveInfo structure $nameIndex = 0; $categoryIndex = 1; $usageIndex = 2; $commentIndex = 3; # DataNode flag numbers $dnIsReal = 0; $dnCanRaiseException = 1; $dnIsRoot = 2; # Special kinds $voidKind = "vkVoid"; $shortOrigin = "aoVariable"; # grab the arguments ($NadFile, $PrimitiveOperationsSourceFN, $PrimitiveOperationsEnumFN, $DataNodeTemplateFN, $BurgHeaderFN) = @ARGV; #print STDERR "$PrimitiveOperationsSourceFN\n"; # read PrimitiveOperation infos open PrimitiveOperationsSourceFN or die "Couldn't open $PrimitiveOperationsSourceFN: $!\n"; $readingStage = 0; while () { if ($readingStage == 0) { if (/^\s*((?:\/\/.*)?\n)$/) { push @headerLines,$1; } else { $readingStage = 1; } } if ($readingStage == 1) { if (/^\s*ARG-ORIGIN\s+'(.)'\s+(\w+)\s*;\s*\n$/) { $argOrigins{$1} = $2; } elsif (/^\s*ARG-KIND\s+'(.)'\s+(\w+)\s*;\s*\n$/) { $argKinds{$1} = $2; } elsif (/^\s*SHORT-ARG\s+'(.)'\s+(\w+)\s*;\s*\n$/) { $shortArgs{$1} = $2; } elsif (!/^\s*(\/\/.*)?\n$/) { $readingStage = 2; } } if ($readingStage == 2) { die "Bad line: $_\n" unless m/^(?:\s*{(\w+),\s*(\w+),\s*"([^"\s]*)"})?(?:\s*(\/\/.*))?\n?$/; # print STDERR "--->$1***$2***$3***$4\n"; #($name, $category, $usage, $comment) = ($1, $2, $3, $4); push @gPrimitiveInfo, [$1, $2, $3, $4]; } } close PrimitiveOperationsSourceFN; $argOrigins = ""; $argKinds = ""; $shortArgs = ""; foreach (keys %argOrigins) {$argOrigins .= $_;} foreach (keys %argKinds) {$argKinds .= $_;} foreach (keys %shortArgs) {$shortArgs .= $_;} #print STDERR "argOrigins = \"$argOrigins\"\n"; #print STDERR "argKinds = \"$argKinds\"\n"; #print STDERR "shortArgs = \"$shortArgs\"\n"; $maxArgOriginNameLength = maxStringLength(values %argOrigins); $maxValueKindNameLength = maxStringLength(values %argKinds, values %shortArgs); $maxPrimitiveNameLength = maxStringLength(map $_->[$nameIndex], @gPrimitiveInfo); $maxCategoryNameLength = maxStringLength(map $_->[$categoryIndex], @gPrimitiveInfo); # do DataNodeTemplates unlink ($DataNodeTemplateFN) or break; outputNodeTemplates($DataNodeTemplateFN, \@gPrimitiveInfo); # do PrimitiveOperations enum unlink ($PrimitiveOperationsEnumFN) or break; $gLastPrimEnumName = outputEnumFromListX("PrimitiveOperation", $PrimitiveOperationsEnumFN, 0, \@gPrimitiveInfo, \&primInfoToEnumDescriptor); open FILE, ">>$PrimitiveOperationsEnumFN" or die "Couldn't open $PrimitiveOperationsEnumFN: $!\n"; print FILE "\nconst uint nPrimitiveOperations = $gLastPrimEnumName + 1;\n"; close FILE; # now handle the BURG section of the file open NadFile or die "Couldn't open $NadFile: $!\n"; while () { if (/%(.*)/) { $section = $1; if ($section eq "terminals") { #print STDERR "processing terminals...\n"; while() { if (/%/) { last; } else { chop; $ts[++$#ts] = $_; } } } elsif($section eq "startsymbols") { #print STDERR "processing startsymbols...\n"; while() { if (/%/) { last; } else { chop; $sss[++$#sss] = $_; } } } elsif($section eq "top") { #print STDERR "processing top...\n"; while() { if (/%/) { last; } else { $top[++$#top] = $_; } } } elsif($section eq "nonterm_header") { #print STDERR "processing nonterm_header...\n"; while() { if (/%/) { last; } else { $BurgHeaderFN[++$#header] = $_; } } } elsif($section eq "grammar") { #print STDERR "processing grammar...\n"; while() { if (/%/) { last; } else { chop; $old = $_; if (!s/\/\/.*//) { $_ = $old; if (s/\w//) { $_ = $old; ($rules[++$#rules], $costs[++$#costs], $rulenames[++$#rulenames]) = split(/\$/, $_); $costs[$#costs] =~ s/\s//g; $rulenames[$#rulenames] =~ s/\s//g; } } } } } } } # Do NamedRules enum file unlink ($BurgHeaderFN) or break; outputEnumFromListX("NamedRules", $BurgHeaderFN, 1, \@rulenames, \&ruleNameToEnumDescriptor); # print out cruft at top print "%{\n"; foreach $x (@top) { print $x; } print "%}\n"; # print out start symbol # we use a false one so we can have multiple start symbols print "%start xxxGeneratedStartSymbol\n"; # print terminals $termno = 1; foreach $x (@primoperations) { $_ = $x; if (s/\w+//) { print "%term $x = $termno\n"; $termno++; } } # convert a primitive info into a list of terminal names # skip first terminal which is pkNone @ts = (); foreach $x (@gPrimitiveInfo[1..$#gPrimitiveInfo]) { push @ts, $x->[$nameIndex]; } # print out "%term BLAHBLAH = number" foreach $x (@ts) { $_ = $x; if (s/\w+//) { print "%term $x = $termno\n"; $termno++; } } # start counting anonruleno's at the number rules + number of start symbols $anonruleno = $#rules +1 + $#sss + 1; print "%%\n"; # now print out the start symbols # start symbols are anonymous rules foreach $x (@sss) { print "xxxGeneratedStartSymbol:\t$x \t = $anonruleno (0);\n"; $anonruleno++; } # now print out the rules # if it is a named rule we store it away in # an array of named rules so we can print the enum out later # named rules start at 0 anonymous rules pick up where sss's left of $namedruleno = 1; $i = 0; foreach $x (@rules) { #print STDERR "$i rulename: ***$rulenames[$i]*** ***$costs[$i]***\n"; if ($rulenames[$i] eq "") { # this is an anonymous rule #print STDERR "anon\n"; print "$x\t = $anonruleno ($costs[$i]);\n"; $anonruleno++; } else { print "$x\t = $namedruleno ($costs[$i]);\n"; $namedrules[++$#namedrules] = $rulenames[$i]; $namedruleno++; } $i = $i + 1; } print "%%\n"; # primInfoToEnumDescriptor # # Convert a primInfo to a ($enumname, $enumcomment) list sub primInfoToEnumDescriptor { my ($primInfo) = @_; return ($$primInfo[$nameIndex], $$primInfo[$commentIndex]); } # primInfoToEnumDescriptor # # Convert a namedrule (a string) to a ($enumname, $enumcomment) list sub ruleNameToEnumDescriptor { my ($name) = @_; return ($name, ""); } # outputGeneratedHeader # # Output header of generated file. sub outputGeneratedHeader { my ($fh, $filename) = @_; $_ = $filename; if (!(s/.*:([^:]+)\Z/$1/)) { s/(.*)/$1/; } $filename = $1; print $fh "//\n// $filename\n//\n// Generated file\n// DO NOT EDIT\n//\n\n"; } # outputEnumFromListX # # in # name: name of this enum, will be printed as a comment above # fileName: file name which to append this enum # enumBase: value of first enum (usually either 0 or 1) # items: list of items # convertFunc: function that transforms an item into a (enumname, enumcomment) list # # out # the file $fileName with the appended enum # returns the last enum output # sub outputEnumFromListX { my ($name, $fileName, $enumBase, $items, $convertFunc) = @_; my $i; my $lastEnum; my $lastName; for ($i = $#$items; $i >= 0; $i--) { my ($name, $comment) = &$convertFunc($$items[$i]); if ($name ne "") { $lastEnum = $i; $lastName = $name; last; } } #($hi, $crap) = &$convertFunc($$items[$lastEnum]); #print STDERR "----> $lastEnum, $hi <---\n"; open FILE, ">>$fileName" or die "Couldn't open $fileName: $!\n"; outputGeneratedHeader(\*FILE, $fileName); print FILE "enum $name\n"; print FILE "{\n"; my $enumCount = $enumBase; # used so we don't count spaces, but still go through the whole array for ($i = 0; $i <= $#$items; $i++) { # print previous item's comma and comment # if it has a name, then we print out the enum name, else we just print the comment my ($name, $comment) = &$convertFunc($$items[$i]); if ($name ne "") { printf FILE "\t%-20s\t%s\n", "$name = $enumCount" . ($i == $lastEnum ? "" : ","), $comment; $enumCount++; } elsif ($comment eq "") {printf FILE "\n";} else {printf FILE "\t%-20s\t%s\n", "", $comment;} } print FILE "};\n"; close FILE; return $lastName; } sub getResult { my ($expression) = @_; #print STDERR "getResult: $expression\n"; $_ = $expression; if (s/(.*)?->(.*)/$1 $2/) { #print STDERR "getResult -- lhs -- : $1\n"; #print STDERR "getResult -- rhs -- : $2\n"; return $2; } } sub getArgs { my ($expression) = @_; my @returnVal; #print STDERR "return val is $#returnVal\n"; $_ = $expression; if (s/(.*)?->(.*)//) { $lhs = $1; #print STDERR "$lhs XXXX $2\n"; $_ = $lhs; while (s/\b([A-Za-z\(\)]+)//) { #print STDERR "$1 foo \n"; $returnVal[++$#returnVal] = $1; } } #print STDERR "return val is $#returnVal\n"; return @returnVal; } # # Given a list of strings, return the length of the longest one. # sub maxStringLength { my $max = 0; foreach (@_) {$max = length if $max < length;} return $max; } # # Ensure that the usage string has the proper format. # Return the outputs and inputs strings. # sub verifyUsage { my ($usage) = @_; return ("", "") if $usage eq ""; my $flags = 0; die "Bad usage string: $usage\n" unless $usage =~ /^([^:]*):([^:]*)$/; my ($outputs, $inputs) = ($1, $2); #print STDERR "'$usage' -> '$outputs', '$inputs'\n"; die "Bad usage string: $usage\n" unless $outputs =~ /^E?(|[Z$shortArgs]|[$argOrigins][$argKinds])$/o and $inputs ne "*" and $inputs =~ /^(Z|([$argOrigins][$argKinds\@]|[$shortArgs])*\*?)$/o; return ($outputs, $inputs); } # # Decode a single argument, possibly with a wildcard. # # Return four values: # the argument's origin, # the argument's valueKind, and # true if the valueKind is a wildcard (either '@' or '_'). # sub decodeArg { my ($arg) = @_; my $origin = $shortOrigin; my $kind = $voidKind; my $wildcard = 0; if (defined($shortArgs{$arg})) { $kind = $shortArgs{$arg}; } elsif ($arg =~ /^([$argOrigins])([$argKinds])$/) { $origin = $argOrigins{$1}; $kind = $argKinds{$2}; } elsif ($arg =~ /^([$argOrigins])[\@_]$/) { $origin = $argOrigins{$1}; $wildcard = 1; } else { die "Internal error\n"; } return ($origin, $kind, $wildcard); } # # Convert an inputs string returned from verifyUsage into a string that contains the # input constraints only and is appropriate as a C++ identifier name. # Wildcard '@' symbols are converted into '_' symbols. # # Return two values: # the constraint string, # true if the last input is repeated. # sub inputsToConstraintString { my ($inputs) = @_; $inputs = "" if $inputs eq "Z"; die "Internal error\n" unless $inputs =~ /^([^\*]*)(\*?)$/; my $constraints = $1; my $repeat = $2 ne ""; $constraints =~ tr/\@/_/; return ($constraints, $repeat); } # # Convert a constraint string returned from inputsToConstraintString into an array # of single-argument strings and return that array. # sub decodeConstraintString { my ($constraints) = @_; my @args = (); while ($constraints ne "") { die "Internal error\n" unless $constraints =~ /^([$argOrigins][$argKinds\_]|[$shortArgs])(.*)$/o; push @args, $1; $constraints = $2; }; return @args; } # # Convert an outputs string returned from verifyUsage into a ValueKind constant. # Null outputs get vkVoid. # sub outputsToKind { my ($outputs) = @_; if ($outputs =~ /([$shortArgs]|[$argOrigins][$argKinds])/) { my ($origin, $kind, $wildcard) = decodeArg($1); die "Bad output: $outputs\n" if $wildcard || ($origin ne $shortOrigin); return $kind; } else { return $voidKind; } } # # Convert a usage string (without quotes) to a hexadecimal string representing the desired flags value # sub usageToFlags { my ($usage) = @_; my $flags = 0; $flags |= 1<<$dnIsReal if $usage ne ""; $flags |= 1<<$dnCanRaiseException | 1<<$dnIsRoot if $usage =~ /E/; $flags |= 1<<$dnIsRoot if $usage =~ /Z/; return sprintf "0x%.4X", $flags; } # # in # arrayName: name of this array # namesName: name of array of enum names # fileName: file name which to append this enum # names: list of names # # out # the file $fileName with the appended definitions # sub outputNodeTemplates { my ($fileName, $primInfos) = @_; my $i; my $str; my $lastEnum; my %constraintStrings; # find out where to place the comma for ($i = $#$primInfos; $i >= 0; $i--) { if ($primInfos->[$i][$nameIndex] ne "") { $lastEnum = $i; last; } } open FILE, ">>$fileName" or die "Couldn't open $fileName: $!\n"; outputGeneratedHeader(\*FILE, $fileName); print FILE "#include \"Primitives.h\"\n\n"; print FILE @headerLines; print FILE "\nconst DataNode::Template DataNode::templates[nPrimitiveOperations] = \n"; print FILE "{\n"; my $formatString = "\t{%-".($maxPrimitiveNameLength+2)."s". "%-".($maxCategoryNameLength+2)."s". "%-".($maxValueKindNameLength+2)."s". "%-8s\t%s\n"; my $commentFormatString = "\t%-".(1 + $maxPrimitiveNameLength+2 + $maxCategoryNameLength+2 + $maxValueKindNameLength+2 + 8)."s\t%s\n"; for ($i = 0; $i <= $#$primInfos; $i++) { my ($name, $category, $usage, $comment) = ($primInfos->[$i][$nameIndex], $primInfos->[$i][$categoryIndex], $primInfos->[$i][$usageIndex], $primInfos->[$i][$commentIndex]); # If it has a name, then we print out the template, else we just print the comment. if ($name ne "") { my ($outputs, $inputs) = verifyUsage($usage); printf FILE $formatString, "$name,", "$category,", outputsToKind($outputs).",", usageToFlags($usage)."}".($i == $lastEnum ? "" : ","), $comment; my ($constraintString, $repeat) = inputsToConstraintString($inputs); $constraintStrings{$constraintString} = 1; } elsif ($comment eq "") {printf FILE "\n";} else {printf FILE $commentFormatString, "", $comment;} } print FILE "};\n\n\n"; print FILE "#ifdef DEBUG\n"; # Print definitions for all of the constraint strings, printing each unique one only once. my @constraintStrings = sort keys %constraintStrings; my $maxConstraintNameLength = length("constraint") + maxStringLength(@constraintStrings); foreach $str (@constraintStrings) { if ($str ne "") { my @constraints = decodeConstraintString($str); printf FILE "static const DataNode::InputConstraint %-".($maxConstraintNameLength+3)."s= {", "constraint$str\[]"; my @constraint; while (defined($constraint = shift @constraints)) { my ($origin, $kind, $wildcard) = decodeArg($constraint); printf FILE "{%-".($maxValueKindNameLength+2)."s", "$kind,"; print FILE "DataNode::"; if (@constraints) {printf FILE "%-".($maxArgOriginNameLength+3)."s", "$origin},";} else {print FILE "$origin}";} } print FILE "};\n"; } } print FILE "\n\n"; print FILE "const DataNode::InputConstraintPattern DataNode::inputConstraintPatterns[nPrimitiveOperations] = \n"; print FILE "{\n"; $formatString = "\t{%-".($maxConstraintNameLength+2)."s%d, %-7s // %-${maxPrimitiveNameLength}s\t%s\n"; $commentFormatString = "\t%-".(1 + $maxConstraintNameLength+2 + 16 + $maxPrimitiveNameLength)."s\t%s\n"; for ($i = 0; $i <= $#$primInfos; $i++) { my ($name, $usage, $comment) = ($primInfos->[$i][$nameIndex], $primInfos->[$i][$usageIndex], $primInfos->[$i][$commentIndex]); # If it has a name, then we print out the input pattern, else we just print the comment. if ($name ne "") { my ($outputs, $inputs) = verifyUsage($usage); my ($constraintString, $repeat) = inputsToConstraintString($inputs); printf FILE $formatString, ($constraintString eq "" ? "0" : "constraint$constraintString").",", scalar decodeConstraintString($constraintString), ($repeat ? "true" : "false")."}".($i == $lastEnum ? "" : ","), $name, $comment; $constraintStrings{$constraintString} = 1; } elsif ($comment eq "") {printf FILE "\n";} else {printf FILE $commentFormatString, "", $comment;} } print FILE "};\n#endif\n\n\n"; print FILE "#ifdef DEBUG_LOG\n"; print FILE "static const char primitiveOperationNames[nPrimitiveOperations][16] = \n"; print FILE "{\n"; for ($i = 0; $i <= $#$primInfos; $i++) { my $name = $primInfos->[$i][$nameIndex]; # If it has a name, then we print out the name. if ($name ne "") { $nameStr = $name; $nameStr = $1 if $name =~ /^[pc]o(\w+)$/; printf FILE "\t%-".($maxPrimitiveNameLength+1)."s\t// %s\n", "\"$nameStr\"" . ($i == $lastEnum ? "" : ","), $name; } } print FILE "};\n#endif\n\n"; close FILE; }