#!/usr/bin/perl
#
# macros.pl
#
# This converts bml (Bridge Macro Language) code
# into HTML. It is somewhat patterned after a version
# done in m4 in early 1996. This one doesn't do all
# the same things and has different problems, but is
# much more portable.
#
# Version: 0.1
# April 11, 1996, initial write
# Version: 0.2
# April 17, 1996
# Added escaped parentheses
# Version: 0.3
# January 5, 1998
# Many changes, including sizes for pips, Smileys, more.
# Version: 0.4
# April 14, 1998
# Fixed missing quotes
# Version: 0.5
# September 8, 1998
# Added SPADE, HEART, DIAMOND, CLUB macros
# for Tom Carmichael.
# Version: 0.6
# Unknown date
# VOID
# 13-card hand counter
# Version: 0.7
# October 24, 2002
# VOID
# Improved panelist
# Table tool
# Version: 0.8
# January 29, 2003
# HEADER and FOOTER
# Version: 0.9
# October 29, 2014
# HHAND14
# Fixed shebang line
# Version: 0.10
# February 26, 2020
# Replace smart quotes
#
# Macros:
# CARD(text)
# changes 1S or SA to what you expect.
# HHAND(s,h,d,c)
# prints a hand horizontally
# HHAND14(s,h,d,c)
# allows non-13-card-hands
# VHAND(s,h,d,c)
# prints a hand vertically
# HAND(north,west,east,south)
# prints a hand diagram
# BIDDING(player1,player2,bid,bid,bid,...)
# draws a bidding sequence between two players
# BID4(p1,p2,p3,p4,bid,bid,bid,...)
# draws a bidding sequence among all four players
# SUIT(north,south)
# draws a simple single-dummy suit combination
# FOOTNOTE(text)
# draws footnote markers
# PANELIST(text)
# draws panelist headers in small caps
# OBIDDING,OBID4
# similar to old macros; doesn't consider
# its arguments to be bids; you have to say CARD()
# of everything. Somewhat helpful when you have
# very complex things with stuff inside the bids.
# SMILEY
# obvious
# SPADE, HEART, DIAMOND, CLUB
# expand to suit symbols; same as CARD(S), CARD(H), etc.
# DASH or VOID
# -> an em dash
# MAKETABLE
# TABLEROW
# ENDTABLE
# convenient for vote tabulations
# HEADER(title)
# FOOTER()
#
# Known problems:
# It doesn't handle nested parentheses right.
# Escaping parens helps: \( and \) are
# handled fine, although FOO(BAR()) might
# not be. The precedence rules are pretty
# good about that, though.
# It doesn't do the old line-by-line bidding macros.
# It doesn't really need them.
#
# Improvements over m4 version:
# No quoting "shift" needed.
# No include line needed.
# Unlimited number of arguments in bidding macros,
# which will mostly make the line-by-line macros obsolete.
# Can handle doubles and redoubles in CARD
# Way fast.
#
# Unknown problems:
# If I knew them, they would be known problems, silly!
#require "ctime.pl";
use Time::localtime;
$simg = 'gifs/s.gif';
$himg = 'gifs/h.gif';
$dimg = 'gifs/d.gif';
$cimg = 'gifs/c.gif';
$swidth = 13; # width of pips (hardcoded)
$sheight = 11; # height of pips (hardcoded)
$smiley = 'gifs/smiley.gif';
$tmarker = 'gifs/table11.gif';
@_ = <>;
$_ = join("",@_);
# remove escaped parens
&hide_parens($_);
&bridge_trans($_);
&bridge_trans($_);
&bridge_trans($_);
&bridge_trans($_);
&bridge_trans($_);
&replace_parens($_);
print $_;
# Bridge formatting stuff
# hide_parens converts \( and \) into QUOTED_PAREN and QUOTED_THESIS
sub hide_parens
{
$_[0] =~ s/\\\(/_QUOTED_PAREN_/g;
$_[0] =~ s/\\\)/_QUOTED_THESIS_/g;
}
# replace_parens reverses above and removes escapes
sub replace_parens
{
$_[0] =~ s/_QUOTED_PAREN_/(/g;
$_[0] =~ s/_QUOTED_THESIS_/)/g;
}
# this takes a string and does replacement
sub bridge_trans
{
$_[0] =~ s/{}FOOTNOTE\(([^)]*)\)/&footnote($1)/ge;
$_[0] =~ s/{}CARD\(([^)]*)\)/&card($1)/ge;
$_[0] =~ s/{}HHAND\(([^)]*)\)/&hhand($1)/ge;
$_[0] =~ s/{}HHAND14\(([^)]*)\)/&hhand14($1)/ge;
$_[0] =~ s/{}OBIDDING\(([^)]*)\)/&old_bidding($1)/ge;
$_[0] =~ s/{}OBID4\(([^)]*)\)/&old_bid4($1)/ge;
$_[0] =~ s/{}BIDDING\(([^)]*)\)/&bidding($1)/ge;
$_[0] =~ s/{}BID4\(([^)]*)\)/&bid4($1)/ge;
$_[0] =~ s/{}VHAND\(([^)]*)\)/&vhand($1)/ge;
$_[0] =~ s/{}HAND\(([^)]*)\)/&hand($1)/ge;
$_[0] =~ s/{}SUIT\(([^)]*)\)/&suit($1)/ge;
$_[0] =~ s/{}PANELIST\(([^)]*)\)/&panelist($1)/ge;
$_[0] =~ s/{}SMILEY/\/g;
$_[0] =~ s/{}SPADE/\/g;
$_[0] =~ s/{}HEART/\/g;
$_[0] =~ s/{}DIAMOND/\/g;
$_[0] =~ s/{}CLUB/\/g;
$_[0] =~ s/{}DASH/\/g;
$_[0] =~ s/{}VOID/\/g;
$_[0] =~ s/{}MAKETABLE\(([^)]*)\)/&maketable($1)/ge;
$_[0] =~ s/{}TABLEROW\(([^)]*)\)/&tablerow($1)/ge;
$_[0] =~ s/{}ENDTABLE\(([^)]*)\)/&endtable($1)/ge;
$_[0] =~ s/{}HEADER\(([^)]*)\)/&header($1)/ge;
$_[0] =~ s/{}FOOTER\(([^)]*)\)/&footer($1)/ge;
$_[0] =~ s/FOOTNOTE\(([^)]*)\)/&footnote($1)/ge;
$_[0] =~ s/CARD\(([^)]*)\)/&card($1)/ge;
$_[0] =~ s/HHAND\(([^)]*)\)/&hhand($1)/ge;
$_[0] =~ s/HHAND14\(([^)]*)\)/&hhand14($1)/ge;
$_[0] =~ s/OBIDDING\(([^)]*)\)/&old_bidding($1)/ge;
$_[0] =~ s/OBID4\(([^)]*)\)/&old_bid4($1)/ge;
$_[0] =~ s/BIDDING\(([^)]*)\)/&bidding($1)/ge;
$_[0] =~ s/BID4\(([^)]*)\)/&bid4($1)/ge;
$_[0] =~ s/VHAND\(([^)]*)\)/&vhand($1)/ge;
$_[0] =~ s/HAND\(([^)]*)\)/&hand($1)/ge;
$_[0] =~ s/SUIT\(([^)]*)\)/&suit($1)/ge;
$_[0] =~ s/PANELIST\(([^)]*)\)/&panelist($1)/ge;
$_[0] =~ s/SMILEY/\/g;
$_[0] =~ s/SPADE/\/g;
$_[0] =~ s/HEART/\/g;
$_[0] =~ s/DIAMOND/\/g;
$_[0] =~ s/CLUB/\/g;
$_[0] =~ s/DASH/\/g;
$_[0] =~ s/VOID/\/g;
$_[0] =~ s/MAKETABLE\(([^)]*)\)/&maketable($1)/ge;
$_[0] =~ s/TABLEROW\(([^)]*)\)/&tablerow($1)/ge;
$_[0] =~ s/ENDTABLE\(([^)]*)\)/&endtable($1)/ge;
$_[0] =~ s/HEADER\(([^)]*)\)/&header($1)/ge;
$_[0] =~ s/FOOTER\(([^)]*)\)/&footer($1)/ge;
$_[0] =~ s/\\'/'/g;
# get rid of nasty smart quotes
$_[0] =~ s/‘/‘/g;
$_[0] =~ s/’/’/g;
$_[0] =~ s/“/“/g;
$_[0] =~ s/”/”/g;
}
# this replaces 1H, 2S, CA, etc. with appropriate stuff
sub card
{
$_ = $_[0];
if (/Dbl/i || /Pass/i)
{
$_;
}
elsif (/^S/i)
{
s/^.//;
''.$_.'';
}
elsif (/^H/i)
{
s/^.//;
''.$_.'';
}
elsif (/^D/i)
{
s/^.//;
''.$_.'';
}
elsif (/^C/i)
{
s/^.//;
''.$_.'';
}
elsif (/S$/i)
{
s/.$//;
''.$_.'';
}
elsif (/H$/i)
{
s/.$//;
''.$_.'';
}
elsif (/D$/i)
{
s/.$//;
''.$_.'';
}
elsif (/C$/i)
{
s/.$//;
''.$_.'';
}
elsif (/Sx$/i)
{
s/..$//;
''.$_.'x';
}
elsif (/Hx$/i)
{
s/..$//;
''.$_.'x';
}
elsif (/Dx$/i)
{
s/..$//;
''.$_.'x';
}
elsif (/Cx$/i)
{
s/..$//;
''.$_.'x';
}
elsif (/Sxx$/i)
{
s/...$//;
''.$_.'xx';
}
elsif (/Hxx$/i)
{
s/...$//;
''.$_.'xx';
}
elsif (/Dxx$/i)
{
s/...$//;
''.$_.'xx';
}
elsif (/Cxx$/i)
{
s/...$//;
''.$_.'xx';
}
else
{
$_;
}
}
# hhand draws a horizontal hand
# it takes four arguments: spades, hearts, diamonds, clubs
# the reason to start with a space is that some versions of
# netscape have a bug vertically aligning images and text
# on the same line when the image comes first.
sub hhand
{
$ncards = &handsize($_[0]);
@args = split(/,/,$_[0]);
$result =
' '.$args[0].
' '.$args[1].
' '.$args[2].
' '.$args[3].'';
if (!($ncards == 13)) {$result .= " <-- hand has $ncards cards. ";}
$result;
}
# this version allows non-13-card hands
sub hhand14
{
$ncards = &handsize($_[0]);
@args = split(/,/,$_[0]);
$result =
' '.$args[0].
' '.$args[1].
' '.$args[2].
' '.$args[3].'';
$result;
}
# vhand draws a vertical hand
# it takes four arguments: spades, hearts, diamonds, clubs
sub vhand
{
@args = split(/,/,$_[0]);
' '.$args[0].'
'.
' '.$args[1].'
'.
' '.$args[2].'
'.
' '.$args[3];
}
# hand draws a full hand diagram
# it takes four arguments, North, West, East, South
sub hand
{
@args = split(/,/,$_[0]);
"
\n".
" | ".$args[0]." | |
\n".
"".$args[1]." | | ".$args[2]." |
\n".
" | ".$args[3]." | |
\n".
"
\n";
}
# Two player bidding
sub bidding
{
@args = split(/,/,$_[0]);
$output = "\n";
# first two args are players
$output .= '';
$output .= $args[0];
$output .= ' | ';
$output .= $args[1];
$output .= " |
\n";
# remaining args are bids
for ($i=2;$i<$#args+1;$i+=2)
{
$output .= ''.&card(@args[$i]).
' | '.&card(@args[$i+1])." |
\n";
}
$output .= "
\n";
$output;
}
sub old_bidding
{
@args = split(/,/,$_[0]);
$output = "\n";
# first two args are players
$output .= '';
$output .= $args[0];
$output .= ' | ';
$output .= $args[1];
$output .= " |
\n";
# remaining args are bids
for ($i=2;$i<$#args+1;$i+=2)
{
$output .= ''.@args[$i].
' | '.@args[$i+1]." |
\n";
}
$output .= "
\n";
$output;
}
# four player bidding
sub bid4
{
local($td1,$td2,$td3);
@args = split(/,/,$_[0]);
$output = "\n";
# first four args are players
$output .= '';
$output .= $args[0];
$output .= ' | ';
$output .= $args[1];
$output .= ' | ';
$output .= $args[2];
$output .= ' | ';
$output .= $args[3];
$output .= " |
\n";
$td1 = "td";
$td2 = "td";
$td3 = "td";
# remaining args are bids
for ($i=4;$i<$#args+3;$i+=4)
{
# handle "All pass" well.
if ($args[$i ] =~ /All\s*Pass/i) {$td1 = "td colspan = 4";}
if ($args[$i+1] =~ /All\s*Pass/i) {$td2 = "td colspan = 3";}
if ($args[$i+2] =~ /All\s*Pass/i) {$td3 = "td colspan = 2";}
$output .= "<$td1>". &card($args[$i]).
"<$td2>".&card($args[$i+1]).
"<$td3>".&card($args[$i+2]).
"".&card($args[$i+3]).
" |
\n";
}
$output .= "
\n";
$output;
}
sub old_bid4
{
@args = split(/,/,$_[0]);
$output = "\n";
# first four args are players
$output .= '';
$output .= $args[0];
$output .= ' | ';
$output .= $args[1];
$output .= ' | ';
$output .= $args[2];
$output .= ' | ';
$output .= $args[3];
$output .= " |
\n";
# remaining args are bids
for ($i=4;$i<$#args+3;$i+=4)
{
$output .= ''. @args[$i].
' | '.@args[$i+1].
' | '.@args[$i+2].
' | '.@args[$i+3].
" |
\n";
}
$output .= "
\n";
$output;
}
# suit draws a two-player suit combination
sub suit
{
@args = split(/,/,$_[0]);
"\n".
"Dummy |
\n".
"".$args[0]." |
\n".
" |
\n".
"Declarer |
\n".
"".$args[1]." |
\n".
"
\n";
}
# Footnote draws superscripts
sub footnote
{
"".$_[0]."";
}
# Panelist is strange: all caps, but different size fonts
sub panelist
{
$param = $_[0];
$param =~ tr/a-z/A-Z/; # upcase
# upcase anything after a space or carat (and remove the carat)
@chars = split(//,$param);
$result = "";
$currently = 1; # big
for ($i=0;$i<$#chars+1;$i++)
{
$big = 0;
# is it big or little?
if ($i == 0) {$big = 1;}
elsif ($chars[$i] eq '^') {$i++; $big = 1;}
elsif ($chars[$i] eq ' ') {$i++; $result .= ' '; $big = 1;}
if ($currently != $big)
{
if ($big) {$result .= "";}
else {$result .= "";}
}
$result .= $chars[$i];
$currently = $big;
}
# if it is small, add an end font
if (!$currently) {$result .= "";}
$result .= "";
$result;
}
# Start table and do header row
sub maketable
{
$result = "\n";
$result .= "\n";
$result .= "\n";
$result .= "\n";
@params = split(/,/,$_[0]);
for ($i=0;$i<=$#params;$i++) {$result .= "".$params[$i]." | "; }
$result .= " \n";
$result;
}
# add a row to a table
sub tablerow
{
$result = "";
@params = split(/,/,$_[0]);
for ($i=0;$i<=$#params;$i++)
{
if ($params[$i] =~ /^\*/)
{$result .= "".substr($params[$i],1)." | ";}
elsif ($params[$i] =~ /^\=/) # push right
{$result .= "".substr($params[$i],1)." | ";}
else
{$result .= "".$params[$i]." | ";}
}
$result .= " \n";
$result;
}
# end table
sub endtable
{
$result = " ";
$result .= " |
";
$result;
}
# How many cards in the hand? It knows about 10 and T and VOID
sub handsize
{
$cards=$_[0];
$cards =~ s/,//g;
$cards =~ s/ //g;
$cards =~ s/VOID//g;
$cards =~ s/10/T/g;
length($cards);
}
# header
sub header
{
$result = "".$_[0]."".
"".$_[0]."
";
$result;
}
# footer
sub footer
{
$result = "
";
if ($_[0] ne "")
{$result .= "Answers
";}
$result .= " Jeff Goldsmith, ";
$result .= &okDate;
$result .= "";
$result;
}
# reasonable date
sub okDate
{
$date = &ctime(time);
($weekday,$month,$day,$time,$year) = split(/\s+/,$date);
$month." ".$day.", ".$year;
}