IllogiGames:Celestial Pirates/Cv dungeon
< IllogiGames:Celestial Pirates(Redirected from User:Snarglefoop/Games/Cv dungeon)
Jump to navigation
Jump to search
Translator Script used for Celestial Pirates part I, and possibly some other stuff
#!/usr/bin/perl -w
use strict;
#
# Convert the single file source using symbolic names to
# a single file using offsets
#
my $filename = "the-stench-1-rooms.html";
my $index_only = 0;
my $sort_rooms = 0;
my $cv_to_singlefile = 1;
my $cv_to_multifile = 0;
my %room_names; # Room IDs based on definition order
my %room_used;
my @room_array;
my $debug = 0;
my $optimize = 0;
my %alpha_room_ids; # Room IDs based on alphabetical order
my %shared_ilist_for_state; # Map of state number to inventory list number
#
# Some random numbers -- generated by C's 'random' function, which is supposed to be pretty good.
#
#
# NB -- this was supposed to help with the 'scramble' function, which is used to build the (static) links in the
# desert maze. It didn't. The result still stinks. Either the concept is NG or a better way of generating random
# links is needed.
#
my @randoms = (1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492, 596516649, 1189641421, 1025202362,
1350490027, 783368690, 1102520059, 2044897763, 1967513926, 1365180540, 1540383426, 304089172, 1303455736, 35005211, 521595368,
294702567, 1726956429, 336465782, 861021530, 278722862, 233665123, 2145174067, 468703135, 1101513929, 1801979802, 1315634022,
635723058, 1369133069, 1125898167, 1059961393, 2089018456, 628175011, 1656478042, 1131176229, 1653377373, 859484421, 1914544919,
608413784, 756898537, 1734575198, 1973594324, 149798315, 2038664370, 1129566413, 184803526, 412776091, 1424268980, 1911759956,
749241873, 137806862, 42999170, 982906996, 135497281, 511702305, 2084420925, 1937477084, 1827336327, 572660336, 1159126505,
805750846, 1632621729, 1100661313, 1433925857, 1141616124, 84353895, 939819582, 2001100545, 1998898814, 1548233367, 610515434,
1585990364, 1374344043, 760313750, 1477171087, 356426808, 945117276, 1889947178, 1780695788, 709393584, 491705403, 1918502651,
752392754, 1474612399, 2053999932, 1264095060, 1411549676, 1843993368, 943947739, 1984210012, 855636226, 1749698586, 1469348094,
1956297539, 1036140795, 463480570, 2040651434, 1975960378, 317097467, 1892066601, 1376710097, 927612902, 1330573317, 603570492,
1687926652, 660260756, 959997301, 485560280, 402724286, 593209441, 1194953865, 894429689, 364228444, 1947346619, 221558440,
270744729, 1063958031, 1633108117, 2114738097, 2007905771, 1469834481, 822890675, 1610120709, 791698927, 631704567, 498777856,
1255179497, 524872353, 327254586, 1572276965, 269455306, 1703964683, 352406219, 1600028624, 160051528, 2040332871, 112805732,
1120048829, 378409503, 515530019, 1713258270, 1573363368, 1409959708, 2077486715, 1373226340, 1631518149, 200747796, 289700723,
1117142618, 168002245, 150122846, 439493451, 990892921, 1760243555, 1231192379, 1622597488, 111537764, 338888228, 2147469841,
438792350, 1911165193, 269441500, 2142757034, 116087764, 1869470124, 155324914, 8936987, 1982275856, 1275373743, 387346491,
350322227, 841148365, 1960709859, 1760281936, 771151432, 1186452551, 1244316437, 971899228, 1476153275, 213975407, 1139901474,
1626276121, 653468858, 2130794395, 1239036029, 1884661237, 1605908235, 1350573793, 76065818, 1605894428, 1789366143, 1987231011,
1875335928, 1784639529, 2103318776, 1597322404, 1939964443, 2112255763, 1432114613, 1067854538, 352118606, 1782436840, 1909002904,
165344818, 1395235128, 532670688, 1351797369, 492067917, 1504569917, 680466996, 706043324, 496987743, 159259470, 1359512183,
480298490, 1398295499, 1096689772, 2086206725, 601385644, 1172755590, 1544617505, 243268139, 1012502954, 1272469786, 2027907669,
968338082, 722308542, 1820388464, 933110197, 6939507, 740759355, 1285228804, 1789376348, 502278611, 1450573622, 1037127828,
1034949299, 654887343, 1529195746, 392035568, 1335354340, 87755422, 889023311, 1494613810, 1447267605, 1369321801, 745425661,
396473730, 1308044878, 1346811305, 1569229320, 705178736, 1590079444, 434248626, 1977648522, 1470503465, 1402586708, 552473416,
1143408282, 188213258, 559412924, 1884167637, 1473442062, 201305624, 238962600, 776532036, 1238433452, 1273911899, 1431419379,
620145550, 1665947468, 619290071, 707900973, 407487131, 2113903881, 7684930, 1776808933, 711845894, 404158660, 937370163,
2058657199, 1973387981, 1642548899, 1501252996, 260152959, 1472713773, 824272813, 1662739668, 2025187190, 1967681095, 1850952926,
437116466, 1704365084, 1176911340, 638422090, 1943327684, 1953443376, 1876855542, 1069755936, 1237379107, 349517445, 588219756,
1856669179, 1057418418, 995706887, 1823089412, 1065103348, 625032172, 387451659, 1469262009, 1562402336, 298625210, 1295166342,
1057467587, 1799878206, 1555319301, 382697713, 476667372, 1070575321, 260401255, 296864819, 774044599, 697517721, 2001229904,
1950955939, 1335939811, 1797073940, 1756915667, 1065311705, 719346228, 846811127, 1414829150, 1307565984, 555996658, 324763920,
155789224, 231602422, 1389867269, 780821396, 619054081, 711645630, 195740084, 917679292, 2006811972, 1253207672, 570073850,
1414647625, 1635905385, 1046741222, 337739299, 1896306640, 1343606042, 1111783898, 446340713, 1197352298, 915256190, 1782280524,
846942590, 524688209, 700108581, 1566288819, 1371499336, 2114937732, 726371155, 1927495994, 292218004, 882160379, 11614769,
1682085273, 1662981776, 630668850, 246247255, 1858721860, 1548348142, 105575579, 964445884, 2118421993, 1520223205, 452867621,
1017679567, 1857962504, 201690613, 213801961, 822262754, 648031326, 1411154259, 1737518944, 282828202, 110613202, 114723506,
982936784, 1676902021, 1486222842, 950390868, 255789528, 1266235189, 1242608872, 1137949908, 1277849958, 777210498, 653448036,
1908518808, 1023457753, 364686248, 1309383303, 1129033333, 1329132133, 1280321648, 501772890, 1781999754, 150517567, 212251746,
1983690368, 364319529, 1034514500, 484238046, 1775473788, 624549797, 767066249, 1886086990, 739273303, 1750003033, 1415505363,
78012497, 552910253, 1671294892, 1344247686, 1795519125, 661761152, 474613996, 425245975, 1315209188, 235649157, 1448703729,
1679895436, 1545032460, 430253414, 861543921, 677870460, 932026304, 496060028, 828388027, 1144278050, 332266748, 1192707556,
31308902, 816504794, 820697697, 655858699, 1583571043, 559301039, 1395132002, 1186090428, 1974806403, 1473144500, 1739000681,
1498617647, 669908538, 1387036159, 12895151, 1144522535, 1812282134, 1328104339, 1380171692, 1113502215, 860516127, 777720504,
1543755629, 1722060049, 1455590964, 328298285, 70636429, 136495343, 1472576335, 402903177, 1329202900, 1503885238, 1219407971,
2416949, 12260289, 655495367, 561717988, 1407392292, 1841585795, 389040743, 733053144, 1433102829, 1887658390, 1402961682,
672655340, 1900553541, 400000569, 337453826, 1081174232, 1780172261, 1450956042, 1941690360, 410409117, 847228023, 1516266761,
1866000081, 1175526309, 1586903190, 2002495425, 500618996, 1989806367, 1184214677, 2004504234, 1061730690, 1186631626, 2016764524,
1717226057, 1748349614, 1276673168, 1411328205, 2137390358, 2009726312, 696947386, 1877565100, 1265204346, 1369602726, 1630634994,
1665204916, 1707056552, 564325578, 1297893529, 1010528946, 358532290, 1708302647, 1857756970, 1874799051, 1426819080, 885799631,
1314218593, 1281830857, 1386418627, 1156541312, 318561886, 1243439214, 70788355, 1505193512, 1112720090, 1788014412, 1106059479,
241909610, 1051858969, 1095966189, 104152274, 1748806355, 826047641, 1369356620, 970925433, 309198987, 887077888, 530498338,
873524566, 37487770, 1541027284, 1232056856, 1745790417, 1251300606, 959372260, 1025125849);
my $rand_count = $#randoms;
while (1) {
my $a1 = shift;
if (! defined $a1) { last; }
if ($a1 eq "-index") {
$index_only = 1;
} elsif ($a1 eq "-sort" ) {
$sort_rooms = 1;
} elsif ($a1 eq "-single" ) {
$cv_to_singlefile = 1;
$cv_to_multifile = 0;
} elsif ($a1 eq "-multi" ) {
$cv_to_singlefile = 0;
$cv_to_multifile = 1;
} elsif ($a1 eq "-debug" ) {
$debug = 1;
} elsif ($a1 eq "-f" ) {
$filename = shift;
} elsif ($a1 eq "-opt") {
$optimize = 1;
} else {
die "Argument $a1 not understood\n";
}
}
# ****************************************************************
# Compare a string against a string, or a string against a list of strings, in which
# case it returns true if the comparand matches anything on the list.
# It returns the value of the thing you passed in, or 0.
#
# We currently think all values must be numbers but if it turns out we're trying to pass
# random strings in we'll change the comparisons to work with strings.
#
# Pretty much all blanks are thrown away. The parsing is recursive descent (surprise).
#
# Lexing of '=' is sloppy indeed; it actually accepts '=', '==', '===', ...
#
# Expressions we interpret:
#
# expr == expr exprs evaluate to integers
# expr == [b,c,d...] True if expr is in the set [...].
# expr must be numeric. Returns true if expr==b OR expr==c OR expr==d OR...
# Note that the [] set construction does NOT NEST. You get one level. Be happy with it.
# expr {+,-.*,/,%} expr The obvious. Precedence is multiplication, addition, mod.
# ... (expr) ... Evaluates the inner expr then recurses. IOW they're parens, as you'd expect.
#
# THERE IS NO UNARY MINUS. Deal with it.
sub eval_expr ($$) {
my ($lineno, $expr) = @_;
my $result;
if ($debug) { print STDERR "Eval_expr at line $lineno: '$expr'\n"; }
if ($expr =~ /^\s* ! (.*)/x) { # Leading "!" is negation. Precedence is wobbly here.
return ! &eval_expr($lineno, $1);
} elsif ($expr =~ /^(.*?) != (.*)/x) { # Change embedded "!=" to "=", recurse, and negate the result
return ! &eval_expr ($lineno, "${1}=${2}");
}
if ($expr =~ /^\s*([0-9a-z_]+)\s*$/i) { # A simple value. If it's a number you'll get a number back.
my $term = $1;
if ($term !~ /^[0-9]+$/) { # If it's not a number you'll get barf.
die "eval_expr: Unexpected characters in term '$term' at line $lineno\n";
}
$result = $term;
} elsif ($expr =~ /^(.*) # Nested parens (we break these out to help with function calls, which are ragged)
(\( .*) \( ([^\(\)]+) \) (.* \))
(.*) $/x) {
my ($lhs1,$lhs2,$middle,$rhs2,$rhs1) = ($1,$2,$3,$4,$5);
$middle = &eval_expr ($lineno, $middle); # Evaluate the subexpression and recurse after pasting it back in
$result = &eval_expr ($lineno, "${lhs1}${lhs2}${middle}${rhs2}${rhs1}");
} elsif ($expr =~ /^\s*(.*)&scramble\s*\((.*?)\)(.*)/) { # SPECIAL -- "&scramble" returns a random value based on the input
my ($lhs,$subex,$rhs) = ($1,$2,$3);
$subex = &eval_expr($lineno, $subex);
if ($subex !~ /^[0-9]*$/) { die "eval_expr: Line $lineno: '$expr' has botched scramble argument -- evals to '$subex'\n"; }
my $rv = $randoms[$subex % scalar(@randoms)];
##my $rv = $subex; # Scrambling just made it all worse at 17, but it's OK at 13.
$result = &eval_expr($lineno, "${lhs}${rv}${rhs}");
} elsif ($expr =~ /^(.*) # A parenthesized subexpression
\( ([^\(\)]+) \)
(.*) $/x) {
my ($lhs,$middle,$rhs) = ($1,$2,$3);
$middle = &eval_expr ($lineno, $middle); # Evaluate the subexpression and recurse after pasting it back in
$result = &eval_expr ($lineno, "${lhs}${middle}${rhs}");
} elsif ($expr =~ /^\s* (.*?) \s* =+ \s* # Expr on the left
\[\s* (.*?) \s* \] # Set on the right
\s*$/x) {
$result = 0;
my ($lhs, $rhs) = ($1, $2);
$lhs = &eval_expr ($lineno, $lhs); # Recurse in case the LHS is more complicated than a simple number
foreach my $r (split /,/, $rhs) {
$r = &eval_expr ($lineno, $r); # Recurse in case we think of a use for an expression in a set
if ($lhs eq $r) {
$result = 1;
last;
}
}
} elsif ($expr =~ /^\s*(.*?)\s*=+\s*(.*?)\s*$/) { # Comparison of two simple values
my ($lhs,$rhs) = ($1,$2);
$lhs = &eval_expr ($lineno, $lhs);
$rhs = &eval_expr ($lineno, $rhs);
$result = $lhs == $rhs;
} elsif ($expr =~ /^\s*(.*?)(%)(.*?)\s*$/) { # Mod -- lowest precedence of the numeric operators
my ($lhs,$op,$rhs) = ($1,$2,$3);
$lhs = &eval_expr ($lineno, $lhs);
$rhs = &eval_expr ($lineno, $rhs);
$result = $lhs % $rhs;
} elsif ($expr =~ /^\s*(.*?)([-+])(.*?)\s*$/) { # Plus and minus
my ($lhs,$op,$rhs) = ($1,$2,$3);
$lhs = &eval_expr ($lineno, $lhs);
$rhs = &eval_expr ($lineno, $rhs);
if ($op eq "+") {
$result = $lhs + $rhs;
} else {
$result = $lhs - $rhs;
}
} elsif ($expr =~ /^\s*(.*?)([\*\/])(.*?)\s*$/) { # Times and division -- highest precedence of the ones we interpret
my ($lhs,$op,$rhs) = ($1,$2,$3);
$lhs = &eval_expr ($lineno, $lhs);
$rhs = &eval_expr ($lineno, $rhs);
if ($op eq "\*") {
$result = $lhs * $rhs;
} else {
$result = $lhs / $rhs;
}
} else {
die "eval_expr: near line $lineno: Don't understand '$expr'\n";
}
if ($debug) { print STDERR "Eval_expr returning $result\n"; }
return $result;
}
# ****************************************************************
# Define a macro, with or without an argument list
my %state_names; # Map of state names to numbers
my @state_ilists; # Inventory lists for the states, indexed by state number
my %simple_macros; # All macros are entered in the simple-macros table
my %macro_args; # A macro with arguments has an entry in the args table, as well
my $last_state_number = -1;
sub define_macro ($) {
my ($inp) = @_;
if ($inp =~ /^\s*(.*?)\((.*?)\)\s+(.*?)\s*$/) { # Macro with an argument list
my ($al,$to) = ($2,$3);
if ($debug) { print STDERR "define_macro: Defining '$1' with args '$al' and body '$to'\n"; }
$simple_macros{$1} = $3;
my @args = split /,/, $al;
$macro_args{$1} = \@args;
} elsif ($inp =~ /^\s*(.*?)\s+(.*?)\s*$/) { # Macro with no argument list
if ($debug) { print STDERR "define_macro: Defining '$1' with body '$2'\n"; }
$simple_macros{$1} = $2;
} else {
die "define_macro: Didn't understand '$inp'\n";
}
}
# ****************************************************************
# Preprocess macro definitions in one line. Returns true if it eats the line.
my $pp_defining = 0;
my $pp_defining_state = 0;
my $pp_name = undef;
my $pp_text = "";
my $pp_state_name;
my $pp_base_state_name;
my $pp_new_inventory = [""]; # One blank line for the header text
my %pp_new_inventory_flags;
sub preprocess_defs ($$) {
my ($lineno, $line) = @_;
if ($debug) { print STDERR "preprocess_defs(line $lineno) processing '$line'\n"; }
if ($pp_defining) {
if ($line =~ /^#defend/) { # If it's the end of the def, record it.
if ($pp_defining_state) {
die "preprocess_defs (line=$lineno): Defining a state, encountered a defend: '$line'\n";
}
$simple_macros{$pp_name} = $pp_text;
$pp_name = undef;
$pp_text = "";
$pp_defining = 0;
} elsif ($line =~ /^#endstate/) {
if (! $pp_defining_state) {
die "preprocess_defs (line=$lineno): Defining a macro, encountered an endstate: '$line'\n";
}
my $pp_state_number = $state_names{$pp_state_name}; # Get the state number for the state
if (! defined $pp_state_number) { # Allocate it if necessary
$last_state_number++;
$pp_state_number = $last_state_number;
$state_names{$pp_state_name} = $pp_state_number;
}
my $in_header = 0;
my $htext;
my $share_ilist = 1; # If there are no changes to the ilist we should share it
foreach my $l (split /\n/,$pp_text) {
$share_ilist = 0; # Anything at all in the state => no sharing possible
if ($in_header) {
if ($l =~ /^\s*([-+]):\s*(.*?)\s*$/x) {
$in_header = 0;
if ($1 eq "+") {
push @$pp_new_inventory, $2;
$pp_new_inventory_flags{$2} = 1;
} else {
$pp_new_inventory_flags{$2} = -1;
}
} else {
$htext .= "\n$l";
}
} elsif ($l =~ /^\s* h: \s* (.*?) \s*$/x) {
$in_header = 1;
$htext = $1;
} elsif ($l =~ /^\s* \+: \s* (.*?) \s*$/x) {
push @$pp_new_inventory, $1;
$pp_new_inventory_flags{$1} = 1;
} elsif ($l =~ /^\s* \-: \s* (.*?) \s*$/x) {
$pp_new_inventory_flags{$1} = -1;
} else {
die "Parsing state (line=$lineno): Didn't understand '$l'\n";
}
}
if ($pp_base_state_name) {
my $osn = $state_names{$pp_base_state_name}; # Old state number
my $oin = $shared_ilist_for_state{$osn}; # The old inventory number
if (! defined $oin) { # If it isn't sharing, use its state number
$oin = $osn;
}
if ($share_ilist) { # Are we sharing the base state's list?
$shared_ilist_for_state{$pp_state_number} = $oin;
}
my $old_inventory = $state_ilists[$osn];
if (!defined $old_inventory) {
die "Line $lineno: Building state $pp_state_name from $pp_base_state_name: No old inventory list found\n";
}
$$pp_new_inventory[0] = $$old_inventory[0]; # Default to the header text from the old inventory list
foreach my $i (1..$#$old_inventory) { # Any old inventory item which hasn't already been added or deleted, we should add.
my $item = $$old_inventory[$i];
if (! $pp_new_inventory_flags{$item}) {
push @$pp_new_inventory, $item;
}
}
} elsif ($share_ilist) {
die "State ${pp_state_name}, ${pp_state_number}: We're supposed to share the ilist but there's no base state\n";
}
if ($htext) { # If we have new header text, pick it up now (overwrite any old header)
$$pp_new_inventory[0] = $htext;
}
if ($debug) { print STDERR "Set state $pp_state_name inventory to $#$pp_new_inventory elements, first is $$pp_new_inventory[0]\n"; }
if (!$share_ilist) {
if ($debug) { print STDERR "State $pp_state_name, number $pp_state_number, got its own inventory list\n"; }
$state_ilists[$pp_state_number] = $pp_new_inventory; # Save the list if we're not sharing.
} elsif ($debug) {
if ($debug) { print STDERR "State $pp_state_name, number $pp_state_number, is sharing ilist $shared_ilist_for_state{$pp_state_number} \n"; }
}
$pp_text = "";
$pp_defining = 0;
$pp_defining_state = 0;
$pp_state_name = undef;
$pp_base_state_name = undef;
$pp_new_inventory = [""];
%pp_new_inventory_flags = ();
} elsif ($line =~ /^#/ &&
($line !~ /^#(?:end)?if/ && $line !~ /#\?/ && $line !~ /^#else/)) { # We skip the conditionals for now.
die "Line $lineno: Unexpected preprocessor directive while defining a macro or state: '$line'\n";
} else {
if ($pp_text ne "") {
$pp_text .= "\n";
}
$pp_text .= "${line}";
}
return 1;
} elsif ($line =~ /^#defbegin/) {
if ($line =~ /^#defbegin\s+(.*?)\((.*?)\)\s*$/) { # Macro with an argument list
my ($al) = ($2);
$pp_name = $1;
if ($debug) { print STDERR "preprocess_defs: Defining macro '$pp_name' with args '$al'\n"; }
my @args = split /,/, $al;
$macro_args{$pp_name} = \@args;
$pp_defining = 1;
} elsif ($line =~ /^#defbegin\s+(.*?)\s*$/) {
$pp_name = $1;
if ($debug) { print STDERR "preprocess_defs: Defining macro '$pp_name' with no arguments\n"; }
$pp_defining = 1;
} else {
die "preprocess_defs: Line $lineno: Didn't understand line $line\n";
}
return 1;
} elsif ($line =~ /^#define\s*(.*)/) {
define_macro ($1);
return 1;
} elsif ($line =~ /^\#state\s* \( # State begin tag
\s* (.*?) \s* # State name
(?:,\s* (.*?) \s*)? # Optional name of state on which to build it
\)\s*$/x) {
$pp_state_name = $1;
$pp_base_state_name = $2;
$pp_defining_state = 1;
$pp_defining = 1;
} elsif ($line =~ /^#(?:end)?if/ || $line =~ /#\?/ || $line =~ /^#else/) { # We skip the conditionals for now.
return 0;
} elsif ($line =~ /^#[a-zA-Z]/) {
die "preprocess_defs: line $lineno: Didn't understand '$line'\n";
}
return 0;
}
# ****************************************************************
# Expand all macros in an array of lines and return the newly expanded
# array along with a flag indicating whether we did anything
#
sub expand_macros_once ($$$) {
my ($lineno,$input,$show) = @_;
my $expanded_something = 0;
my @output = ();
foreach my $i (0..$#$input) { # We don't use "foreach <elt> (array)" 'cause it gives us a live copy of the line, I think
my $l = $$input[$i];
my $expanded_this_line = 0;
#
# Expand the simple ones...
#
foreach my $k (keys %simple_macros) {
if ($l !~ /\Q$k\E\b/) { next; } # We do NOT require a word break BEFORE the macro, only after
$expanded_this_line = 1;
my $v = $simple_macros{$k};
if ($macro_args{$k}) {
if ($l !~ /(\Q$k\E\s*\((.*?)\))/) {
die "Argument list missing, expanding macro '$k' near line $lineno\n";
}
my $sub_for = $1;
my @actuals = split /,/,$2;
my $formals = $macro_args{$k};
if ($#$formals != $#actuals) { die "expand_macro_once(line ~ $lineno) Wrong number of arguments for '$k', expanding '$l'\n"; }
foreach my $j (0..$#$formals) { # As with the macro defs, we don't require a word break *before* the arg.
my $formal = $$formals[$j];
my $actual = $actuals[$j];
$v =~ s/\Q${formal}\E\b/${actual}/g; # Substitute into the macro definition
}
$l =~ s/\Q$k\E\s*\(.*?\)/$v/; # Do NOT use '/g' here, as we need to re-eval the actuals every time
} else {
$l =~ s/\Q$k\E\b/$v/g;
}
}
foreach my $k (keys %state_names) {
if ($l !~ /\Q$k\E\b/) { next; } # We require a word boudary at the END of the state name, not the beginning!!
my $v = $state_names{$k};
$l =~ s/\Q$k\E\b/$v/g;
}
# We may have a multiline expansion. To make sure things work right we need to split it into lines.
if (!$expanded_this_line) { # If we didn't expand it just put it on the output intact
push @output, $l; # If we try to split it we kill blank lines.
} else {
$expanded_something = 1; # If we expanded it, split it into lines
my @lines = split /\n/,$l;
foreach my $j (0..$#lines) {
chomp (my $piece = $lines[$j]);
if ($j != $#lines || $piece ne "") { # Do NOT push the 'extra' blank line we get courtesy of split
push @output, "$piece";
}
}
}
}
return ($expanded_something,\@output);
}
# ****************************************************************
# Expand all macros in an array of lines repeatedly until it settles down
sub expand_macros ($$$) {
my ($lineno,$lines,$show) = @_;
my $expanded_something = 1;
while ($expanded_something) {
$expanded_something = 0;
($expanded_something, $lines) = expand_macros_once ($lineno, $lines, $show);
}
return $lines;
}
# ****************************************************************
# Handle conditionals. We do this very late so all expansions have already
# been done, and we can, consequently, conditionalize on the current state.
# We do this a line at a time because the room parser is doing its own substitutions
# a line at a time, and we need to get hold of the lines *after* that substitution
# has been done, since that's what puts the current state values into the text.
#
# An if/else/endif can span macro boundaries, which is weird; it's because we
# process the conditionals after macro expansion.
#
# Returns 1 if it eats the line, 0 otherwise.
#
# Nesting is the obvious, with else/endif associated with the nearest if.
#
#
# We're not called from a recursive descent parser here and we have no convenient way
# to recurse so we're just going to handle the nesting manually
#
my $pp_nest_level = 0;
my @saved_pp_start_line;
my @saved_pp_condition_skipping;
my @saved_pp_inside_if;
my @saved_pp_skip_else;
my @saved_pp_skip_whole_thing;
my $pp_start_line;
my $pp_condition_skipping;
my $pp_inside_if;
my $pp_skip_else;
my $pp_skip_whole_thing; # Set => we're skipping a nested if construct
sub handle_conditions ($$) {
my ($lineno, $line) = @_;
my $result = 0;
if ($debug) { print STDERR "handle_conditions(lineno=$lineno): '$line'\n"; }
if ($line =~ /^#if\b/ && $pp_inside_if) { # Whoops -- recursion time!
push @saved_pp_start_line, $pp_start_line;
push @saved_pp_condition_skipping, $pp_condition_skipping;
push @saved_pp_inside_if, $pp_inside_if;
push @saved_pp_skip_else, $pp_skip_else;
push @saved_pp_skip_whole_thing, $pp_skip_whole_thing;
if ($pp_condition_skipping) { $pp_skip_whole_thing = 1; } # If not skipping leave this set to whatever it was set to
$pp_start_line = undef;
$pp_condition_skipping = undef;
$pp_inside_if = undef;
$pp_skip_else = undef;
$pp_nest_level++;
}
if (! $pp_inside_if) {
if ($line =~ /^#else/ || $line =~ /^#endif/) {
die "WHOOPS -- line $lineno, text '$line': else or endif not inside an if\n";
}
if ($line =~ /^#if\b/) {
if ($debug) { print STDERR "Found 'if' at line $lineno\n"; }
$pp_start_line = $lineno;
$result = 1;
$pp_inside_if = 1;
$pp_skip_else = 1;
if ($line =~ /^#if\s*\((.*)\)\s*$/) {
my $ok = eval_expr ($lineno,$1);
if (! $ok) {
$pp_condition_skipping = 1;
$pp_skip_else = 0; # We will NOT skip the else
}
} else {
die "handle_conditions: Somewhere around $lineno: Didn't understand '$line'\n";
}
}
} elsif ($line =~ /^#elseif\b/) {
$result = 1;
if ($line =~ /^#elseif\s*\((.*)\)\s*$/) {
$pp_condition_skipping = $pp_skip_else; # If we had an accepted if, then we're skipping all else clauses
if (! $pp_condition_skipping) { # Not just skipping -> check the condition
$pp_condition_skipping = ! eval_expr ($lineno,$1);
}
if (! $pp_condition_skipping) {
$pp_skip_else = 1; # We want to skip any further 'else' clauses in this if, if we don't skip this one.
}
} else {
die "handle_conditions: Somewhere around $lineno: Didn't understand '$line'\n";
}
} elsif ($line =~ /^#else\b/) {
$pp_condition_skipping = $pp_skip_else;
$result = 1;
} elsif ($line =~ /^#endif\b/) {
$pp_condition_skipping = 0;
$pp_skip_else = 0;
$pp_inside_if = 0;
$result = 1;
if ($pp_nest_level) { # Time to pop the old state back
$pp_start_line = pop @saved_pp_start_line;
$pp_condition_skipping = pop @saved_pp_condition_skipping;
$pp_inside_if = pop @saved_pp_inside_if;
$pp_skip_else = pop @saved_pp_skip_else;
$pp_skip_whole_thing = pop @saved_pp_skip_whole_thing;
$pp_nest_level --;
}
} elsif ($pp_condition_skipping || $pp_skip_whole_thing) {
$result = 1;
} else { # In this case we're in an 'if' but we're not skipping it.
$result = 0;
}
return $result;
}
# ****************************************************************
# Call as: parse_room_worker ($sub_for, $to_term, $room_p, $room_start_line, $show);
#
# We have one (or more) builtin macro(s) as well. We expand them here after all
# other expansions are done, because we want to be able to look up room names, and
# the room names are built from the state tag which is substituted in here when
# we instantiate the room.
#
# .RINDEX (room-name) ... Expands into the (final, optimized) room number for "room-name"
# Note that if "room-name" is written as "Smoof-fooble-X" where "X" is
# the current room state, "X" must be substituted BEFORE .RINDEX is
# expanded, or it won't work.
#
my $roomno;
##my @room;
my @from_strings;
my @to_strings;
sub parse_room_worker ($$$) {
my ($room, $room_start_line, $show) = @_;
my $lineno = $room_start_line;
my $get_room_name = 1;
my $parsed_room = "";
if ($debug) {
print STDERR "parse_room_worker called for room at line $room_start_line with $#from_strings translations\n";
foreach my $t (0..$#from_strings) {
print STDERR " $from_strings[$t] --> $to_strings[$t]\n";
}
}
$roomno++;
foreach my $raw_l (@$room) {
$lineno++;
my $line = $raw_l; # Copy it so we don't bash the original
if (scalar(@from_strings)) { # If we've got some substitutions to perform
foreach my $s (0..$#from_strings) {
my $f = $from_strings[$s];
my $t = $to_strings[$s];
$line =~ s/\b${f}\b/${t}/g; # For the state tags we require a word break before AND after
}
}
if (handle_conditions ($lineno,$line)) {
next;
}
if ($get_room_name) {
if ($line =~ /=/) { # If there's an equals, it's a substitution spec and not a room name.
next;
}
$get_room_name = 0;
if ($line !~ /^(?: \|\{\{ )?
( [a-z0-9-_]+ )
(?: \}\}= )?
/xi) {
if ($line =~ /Dummy/) { # This is the dummy room at the end. Discard it.
$get_room_name = 0;
$roomno --;
next;
}
die "Room name not found where expected on line $lineno\n";
}
my $rn = $1;
if (!$show) {
if ($room_names{$rn}) {
die "Room $rn already defined at line $lineno\n";
}
if ($debug) { print STDERR "Setting $rn to number $roomno\n"; }
$room_names{$rn} = $roomno;
} else {
if (!defined $room_names{$rn}) {
die "On show pass, and room $rn not defined when we got to it\n";
}
if ($roomno != $room_names{$rn}) {
die "On show pass, and room number of '$rn' changed from $room_names{$rn} to $roomno\n";
}
}
if ($cv_to_singlefile) {
if ($show && $optimize) {
$parsed_room .= "|$alpha_room_ids{$rn}=\n";
} else {
$parsed_room .= "|{{TheStenchIndex|${rn}}}=\n";
}
} else {
$parsed_room .= "$rn\n";
}
} else {
#
# Expand our builtin macro(s). They (or it) are wired in, hard coded, right here.
#
my $rname_macro = ".RINDEX"; # That's "Room Index"
while ($line =~ /${rname_macro}\s*\((.*?)\)/) {
my $rn = $1;
my $rnum = $alpha_room_ids{$rn};
if (! defined $rnum) {
if ($show) { die "expand_macros_once, at line ${lineno}: ${rname_macro}(${1}): Room not defined\n"; }
$rnum = -1;
}
$room_used{$rn} = 1; # Macro argument counts as a use of the room name
$line =~ s/${rname_macro}\s*\((.*?)\)/$rnum/;
}
$rname_macro = ".ILIST"; # Inventory list for a state
while ($line =~ /${rname_macro}\s*\((.*?)\)/) {
my $inum = $shared_ilist_for_state{$1};
if (! defined $inum) {
$inum = $1;
}
$line =~ s/${rname_macro}\s*\((.*?)\)/$inum/;
}
if ($line =~ /(.*?) \[\[\.\.\/ # Lex out a link. Start with stuff on the line before the link
(.*?) \| (.*) # The pipe link itself ($2 and $3)
\]\] (.*)/x) { # Trailing stuff on the line
my $line_prefix = $1;
my $rname = ucfirst($2);
my $link_text = $3;
my $line_suffix = $4;
if ($show && !defined($room_names{$rname})) {
print STDERR "Whoops! Room '$rname' at line $lineno is not defined!\n";
}
$room_used{$rname} = 1; # Note that someone links to this room
if ($cv_to_singlefile) {
if (! $optimize) {
$parsed_room .= "${line_prefix}{{Goto|{{TheStenchIndex|${rname}}}|${link_text}}}${line_suffix}\n";
} elsif ($show && defined $room_names{$rname}) {
$parsed_room .= "${line_prefix}{{Goto|$alpha_room_ids{$rname}|${link_text}}}${line_suffix}\n";
} else {
$parsed_room .= "${line_prefix}{{Goto|-1|${link_text}}}${line_suffix}\n";
}
} else {
$parsed_room .= "${line_prefix}[[../${rname}|${link_text}]]${line_suffix}\n";
}
} else {
$parsed_room .= "${line}\n";
}
}
}
push @room_array, $parsed_room;
}
# ****************************************************************
#
# Args:
#
# \@room_line_array
# \@subst_arrays
# $room_start_line
#
# Pass in an array of lines, which is the room spec, and an array of substititions
# to be made, where each sub is a pair "string", "string".
# It also takes a starting line number, so it can figure out what line number to complain
# about when there's an error.
sub parse_room ($$$) {
my ($room, $room_start_line, $show) = @_;
#
# I have no idea why, but we're getting an undef in slot 0 of room, apparently coming from nowhere.
# --> I was setting the list to undef (aka "(undef)") instead of "()" whoops
# if (0 && !defined $$room[0]) { shift @$room; }
if ($debug) {
my $n = 0;
print STDERR "Room (with $#$room + 1 lines):\n";
foreach my $l (@$room) {
$n++;
print STDERR "Line $n: '${l}'\n";
}
print STDERR "End room.\n";
}
$room = expand_macros ($room_start_line, $room, $show); # Expand everything in it so we can parse it after expansion
if ($debug) {
my $n = 0;
print STDERR "Room after macro expansion (with $#$room + 1 lines):\n";
foreach my $l (@$room) {
$n++;
print STDERR "Line $n: '${l}'\n";
}
print STDERR "End room.\n";
}
@from_strings = ();
@to_strings = ();
my @to_string_list;
my $line = $$room[0];
if ($debug) {
my $rl = scalar (@$room);
print STDERR "Room lines: $rl; Line: $line\n";
}
if ($line =~ /^\s*(.+?)\s*=\s*(.+?)\s*(?:,(.*))?/) { # Sub strings of form "A = B, ..."
my $how_many;
while (defined $line && $line =~ /^ \s*(.+?)\s* = \s*(.+?)\s*
(?:;(.*))? $/x
) {
my $sub_for = $1;
my @sub_to_list = split /,/,$2;
if (!defined $how_many) {
$how_many = scalar(@sub_to_list); # Note this. We use the number on the first list.
}
push @from_strings, $sub_for; # Array of from strings
push @to_string_list, \@sub_to_list; # Array of target lists
$line = $3; # discard the head, which we've already used
}
foreach my $t (0..$how_many-1) { # For each target, set up the translations for one instantiation
foreach my $i (0..$#from_strings) { # Set up the global "to" list
my $tl = $to_string_list[$i];
$to_strings[$i] = $$tl[$t]; # Pick the current translation off the list for this 'from' variable
if (! defined $to_strings[$i]) {
$to_strings[$i] = $$tl[0]; # Use the first one if we've run off the end.
}
}
if ($debug) {
print STDERR "Substituting for one instantiation:\n";
foreach my $s (0..$#from_strings) {
print STDERR " $from_strings[$s] --> $to_strings[$s]\n";
}
}
parse_room_worker ($room, $room_start_line, $show);
}
} else {
if ($debug) { print STDERR "Calling parse_room_worker with no translation\n"; }
parse_room_worker ($room, $room_start_line, $show);
}
}
# ****************************************************************
sub parse_file ($) {
my ($show) = @_;
print STDERR "parse_file(show=$show)\n";
@room_array = ();
$roomno = -1;
my $room_line_no = 0;
my $lineno = 0;
my @room = ();
my $in_room = 0;
my $input_room_count = 0;
open INFILE, $filename or die "Can't open input file";
while (my $line = <INFILE>) {
chomp($line);
$lineno++;
if (preprocess_defs($lineno,$line)) {
next;
}
if ($line =~ /^(<!--\s*)?=============*(\s*-->)?$/) {
if ($debug) { print STDERR "Found a room divider\n"; }
$in_room = 1;
if (scalar(@room) > 0) { # Do we have an old one which needs to be parsed?
if ($debug) { print STDERR "At line $lineno: Looks like room was defined, with last line $#room, so we're parsing it\n"; }
parse_room (\@room, $room_line_no, $show);
$input_room_count ++;
}
@room = ();
$room_line_no = $lineno; # Starting line number for the next room
} elsif ($in_room) {
if ($debug) { print STDERR "Pushing line '$line' into room\n"; }
push @room, $line;
}
}
close INFILE or die "Error closing file $filename";
# If we had a last room which wasn't appended, append it now
if (scalar(@room) > 0) {
if ($debug) { print STDERR "At line $lineno: Looks like room was defined so we're parsing it\n"; }
parse_room (\@room, $room_line_no, $show);
$input_room_count ++;
@room = ();
}
#
# And now, add a "room" for each state, containing the inventory for that state
#
foreach my $s (sort keys %state_names) {
$room_line_no = $lineno;
@room = ();
my $snumber = $state_names{$s};
my $inv = $state_ilists[$snumber];
if (defined $shared_ilist_for_state{$snumber}) {
next; # Skip states that share someone else's inventory list
}
if (!defined $inv) {
print STDERR "Shared ilists:\n";
foreach my $k (keys %shared_ilist_for_state) {
print STDERR " $k --> $shared_ilist_for_state{$k}\n";
}
die "State $s, number $snumber, has no inventory list\n";
}
push @room, "================================================================";
$lineno++;
push @room, "01-Inventory-${snumber}";
$lineno++;
push @room, "<!-- Inventory for state $snumber -->";
$lineno++;
foreach my $line (@$inv) {
push @room, $line;
$lineno++;
}
push @room, ".NOINVENTORY";
$lineno++;
parse_room (\@room, $room_line_no, $show);
}
if ($show) {
if ($cv_to_singlefile) {
if (defined $simple_macros{".TITLE"}) {
chomp(my $ttext = $simple_macros{".TITLE"});
print "{{title|${ttext}}}\n";
}
print "{{#switch:{{Get}}\n";
}
my $output_room_count = 0;
foreach my $k (sort keys %room_names) {
if ($debug) { print STDERR "Printing room $k, key $room_names{$k}\n"; }
my $r = $room_array [$room_names{$k}];
$output_room_count++;
if ($cv_to_singlefile) {
if (! $optimize) {
print "<!--================================================================-->\n";
}
} else {
print "================================================================\n";
}
print $r;
}
if ($cv_to_singlefile) {
$output_room_count++;
print "<!--================================================================-->\n";
print "<!-- Dummy room to soak up gotos that get lost in space -->\n";
print "|\n";
if (defined $simple_macros{".DUMMY"}) {
my $rv = $simple_macros{".DUMMY"};
print "$rv\n";
} else {
print "''You have fallen through a hole in the air.''\n";
print "\n";
print "''This room does not exist.''\n";
print "\n";
print "''There is just one exit from the room.''\n";
print "\n";
print "* [[Special:Random|Leave the room]]\n";
print "* {{Goto|0|Start over}}\n";
print "\n";
print "{{TheStench}}\n";
}
print "}}\n";
}
print STDERR "Read $input_room_count rooms, emitted $output_room_count rooms\n";
}
}
# ****************************************************************
parse_file (0); # Build the index
# Build the sorted index, which we use for optimized output as well as for dumping the index
my $room_no = 0;
foreach my $r (sort keys %room_names) {
$alpha_room_ids{$r} = $room_no;
$room_no++;
}
# If we're building the file, do that now
if (! $index_only) {
parse_file (1);
my $had_orphans = 0;
foreach my $k (sort keys %room_names) {
if (! defined $room_used{$k}) {
if (!$had_orphans) {
print STDERR "Orphaned rooms:\n";
}
$had_orphans++;
print STDERR " ${k}\n";
}
}
my $had_undefs = 0;
foreach my $k (sort keys %room_used) {
if (! defined $room_names{$k}) {
if (!$had_undefs) {
print STDERR "Undefined rooms:\n";
}
$had_undefs ++;
print STDERR " ${k}\n";
}
}
print STDERR "Total undefined rooms: ${had_undefs}; Total orphaned rooms: ${had_orphans}\n";
}
# If we're building the index, dump it
if ($index_only) {
print "<noinclude>Template to convert room name to index</noinclude>\n";
print "{{#switch: {{{1}}}\n";
foreach my $r (sort keys %room_names) {
print "| $r = $alpha_room_ids{$r}\n";
$room_no++;
}
print "| -1}}\n\n";
print "<noinclude>[[Category:Templates]]</noinclude>\n";
}