#!/usr/local/bin/perl
# $country        = '7';
# $representative = '7';
# $host           = 'random.chem.psu.edu';
# $port           = 1618;
#
# $Id: pei,v 2.9.3 1997/06/08 20:09:50 root Exp root $
#
#                           Perl Empire Interface
#        
#               Based on eif by Doug Hay and hpc by Drake Diedrich
#
#                           written by Ken Stevens
#
#
# DESCRIPTION:
# pei is a powerful, fully extendible smart client for playing Empire.
# Features include:
#   - Tools for managing and feeding civs, auto-exploring, setting dist-paths,
#     calculating the new efficiency of ships/planes/units, reading/sending
#     telegrams and announcements, keeping track of enemy sectors and units,
#     and managing production deltas.
#   - Full perl integration.  You can type perl commands at the empire prompt.
#   - File redirection, piping, exec (read commands from a file), and
#     runfeed (pipe the output of a program to pei).
#   - Aliases.  You can define your own aliases with arguments.
#   - !!, !abc, and ^a^b history substitution
#   - Multiple games.  You may be connected to more than one game at a time,
#     and switch between them.
#   - Function mapping.  You can call your own perl functions from within
#     the client by defining your own function maps.
#   - Output parsing.  Server output is automatically parsed and stored in
#     an internal database.  There is a separate database for each game
#     connected to.
#   - Interrupt handling.  ^C should be handled properly in most cases.
#
# INSTALLATION:
# Before you run this script, make sure that you have typed:
# chmod +x pei
# where pei is the name of this file.
#
# HOW TO RUN IT:
# To get the client up and running, do one of the following:
# 1) Uncomment and edit the four variables $country, $representative,
#    $host, and $port at the top of this file.  Then in your operating system,
#    type 'pei'.
#
# Or, you can specify these four variables as command-line arguments:
# 2) In your operating system type:
#    pei [country] [rep] [host] [port]
#
# Or, you can specify the information in a file:
# 3) Copy example.peirc to ~/.peirc and edit the game data at the bottom
#    of the file.  Then type 'pei'.
#
# Once in the client, type "help" for more information.  The man page
# for pei may be found at the bottom of this file.
#
# BUG REPORTS:
# mail your bug-reports and comments to:
# tetherow@nol.org (Sam Tetherow)

#       --- Glossary ---
#
# These are the terms used in the # comments in this program:
# cprompt           The main Empire command prompt  ($mode eq $C_PROMPT).
# sprompt           An Empire sub-prompt            ($mode eq $C_FLUSH).
# prompt            Either a cprompt or an sprompt.
# ecommand          An Empire command sent to a cprompt.
# scommand          Text sent to a sub-prompt.
# command           A command understood by this client (includes ecommands).
# commandline       A command with its arguments.
# line              One or more commandlines separated by semicolons.

#       --- Global variables ---
#
# User defined variables:
# $EDITOR           Name of system editor to use while in pei.
# $PAGER            Name of sytem pager to use while in pei.
# $PEIPATH          The name of the directory containing all the pei files
# $TIMEOUT          How long do we wait for output from the server?
# $mailflag         If true, mail.pl will not remove mail from the
#                   empire server.
# $mailflag{$game}  Same as mailflag, but you can set it just for a game.
# $debug            If true, then print connect debug info.
# $maxhistorysize   The number of commands stored in the history list.
#
# Connection stuff:
# $games{$name}     A list of games we can connect to.
# $game             The name of the game I am currently connected to.
# $country          The name of our country.
# $representative   The password for our country.
# $host             The host we're connected to.
# $port             The port we're connected to.
# $socket{$game}    The name of the socket filehandle for $game if open.
# $S                The socket for the current game.
#
# Error recovery:
# $depth            How deep in input loops am I?
# $status           If not zero, drop what we're doing and &recover
#                   $status us a bitfield.  The following bits can be
#                   set in $status:
# $S_EOF            The server closed the connection.
# $S_TIMEOUT        We waited longer than $TIMEOUT seconds for output
#                   from the server.
# $S_INTERRUPT      The player hit ^C
# $S_BROKEN_PIPE    Broken pipe (most likely hit 'q' in 'more')
# $S_NEW_GAME       The player is switching games.
#
# Tools:
# $coun             My country number.
# $functionmap{"command"} = "&function"
#                   When player types "command" call &function.
# $parsemap{"ecommand"} = "&parse_function"
#                   Defined in parse.pl -- parse the output of this ecommand
#
# These i/o variables are used in the main loop:
# $serverline       A line of output from the server
# @history          A history of recent commands (used only in mainloop)
# $command          The current command
# $commandarg       The arguments of the current command
# $prev_FH{$FH}     Linked list of filehandles.  Used for output redirection.
# $new_filehandle   Incremented to generate unique filehandle names.
# $quoted_material
# $semicolon_in_quotes These two string constants are used to parse 
#                   Semicolons in quotes in command lines.
#
# These three variables are sent by the server every time we get a cprompt:
# $timeused         The amount of time we've been playing so far
# $btus             How many btu's we have left
# $mode             See &init for details.
# $tele{$game}             A string used in our main promt showing if we have
#                   any telegrams


#      --- Functions ---
#
# Main:
# connectloop       Whenever we connect to a new game, we enter this loop
# load_tools        Load tools.pl, parse.pl, and simu.pl for $game
# greate_game_name  Create name for game that is valid package name
#
# Initialization functions:
# init              Initialize variables
# parse_argv        Parse command line arguments to this program
#
# Read and parse user input:
# parse_commandline Parse a single commandline
# singlecommand     Send a single ecommand to the server unparsed
# parse_line        Parse one line of input
# mainloop          Read lines from standard input and parse them
# fmainloop         Read lines from a file and parse them
#
# Read and parse server output:
# parse_serverline  Parse output from the server
# slurp             Read & parse server output until prompt
# suck              Read & parse server output until cprompt
#
# Local prompt:
# local_prompt      Dispatch local commands.
# localloop         Prompt user to select a new game.
# prompt            Prompt the user for input
# set_game          Set the coun, rep, host, and port from game name
#
# Connect to server:
# connect_to_server Connect to the server
# got_from_server   Print an error message if we didn't get what we expected
#
# Error Recovery:
# dokill            Called when user hits ^C
# broken_pipe       Called when a pipe breaks
# ih_print          Special print function used by error recovery subroutines
# ask_to_die        Things are very bad.  Ask user to kill pei.
# recover           Recover from error (called when $status is non-zero).
# recover_data      Recover from user interrupt when there's data on the server
# flush_server_output Flush server output without parsing
# nofind_error      Pei can't locate a file.  Tell the user where we looked.
# abort_command     Abort ecommand
# close_connection  Close the connection to the server for the current game.
# do_exit           Exit nicely
#
# Read init file:
# read_peirc        Read variables, aliases, and games from ~/.peirc
# convert_eifrc_to_peirc Make a new ~/.peirc out of a ~/.eifrc
#
# Function maps:
# execfile          Execute lines from a file
# runfeed           Run a program and pipe the output to pei
# runcommands       Called by execfile and runfeed;
# history           print history of our lines
#
# String subroutines:
# quotify           Put something in quotes so that it can be eval'ed
# strip_quotes      Remove bracketing quotes from text

#       --- Main ---
#
# This is the entry point for pei.  It all starts here...

$version = '2.9.3';

print "\nWelcome to PEI\nPerl Empire Interface version $version\n\n";

&init;				# initialize variables
&read_peirc;			# read & parse commands from ~/.peirc
&parse_argv;			# parse command-line arguments
&connectloop;			# the main loop
&do_exit;			# exit nicely

# This is the main pei loop.  Whenever we switch between games, we re-enter
# this loop.
sub connectloop {
  while (1) {
    $status &= ~$S_INTERRUPT;	# Clear the interrupt flag
    
    # If $country has not been defined yet (e.g. in this file or ~/.peirc),
    # enter local prompt and ask for a game to connect to.
    &localloop unless $country || $status & $S_NEW_GAME;
    $status &= ~$S_NEW_GAME;	# Clear the new game flag

    # $game is used as a part of the pei prompt
    # Create the game name from the country name
    # Note that $game must be a valid perl package name.
    &create_game_name unless $game || $game !~ /[A-Za-z]+[1-9]*/;

    # Each game has its own socket.

    if ($S = $socket{$game}) { # We are already connected to that game
      print STDERR "Reconnected.\n";
      $status &= ~$S_EOF;
      $mode = $C_PROMPT;
    } elsif (&connect_to_server) {
      # If we are able to connect to the host, start playing.
      print STDERR "\nplay\n" if $debug;
      print $S "play\n";	# start playing!
      if (!&got_from_server("$C_INIT 2")) {&close_connection; next;}
      print STDERR "Connected!\n";
      &slurp;			# read and print motd
      if ($status) {&close_connection; next;}
      if (!$no_tools) {
	# Load tools for this game.
	&load_tools;
	if ($status) {&close_connection; next;}
	if ($tools_loaded) {
	  &find_country_number;	# find my country number -- needed for tools.pl
	  if ($status) {&close_connection; next;}
	  &tools_init;
	  if ($status) {&close_connection; next;}
	  eval ("&".$game."'load_DB");
	  if ($status) {&close_connection; next;}
	}
	if (-f "start.exec") {	# if there is a start.exec file
	  &parse_commandline("exec start.exec"); # execute it
	  if ($status) {&close_connection; next;}
	}
      }
    } else {			# we weren't able to connect
      &close_connection;
      next;
    }

    # Set these four variables for the current game.
    eval('$country = $'.$game."'country");
    eval('$coun = $'.$game."'coun");
    eval('$btus = $'.$game."'btus");
    eval('$timeused = $'.$game."'timeused");
    eval('$nstatus = $'.$game."'nstatus");

    if (!(-t STDIN)) {		# if pei was called as "pei < filename"
      &fmainloop(STDIN);	# read commands using fmainloop
    } else {                    # otherwise
      &mainloop;                # read commands using standard input
    }
  }
}

# Load the perl modules which are specific to being connected to a game.
# These modules are loaded within the package $game to keep the namespace
# of separate games distinct.
sub load_tools {
  return unless $game;

  undef $tools_loaded;
  eval("package $game; do 'tools.pl';");
  &nofind_error("tools.pl",$@) unless $tools_loaded;
  undef $parse_loaded;
  eval("package $game; do 'parse.pl';");
  &nofind_error("parse.pl",$@) unless $parse_loaded;
  undef $simu_loaded;
  eval("package $game; do 'simu.pl';");
  &nofind_error("simu.pl",$@,1) unless $simu_loaded;
}

# Create a game name from a country name, making sure that $game is a valid
# perl package name.
sub create_game_name {
  ($game) = ($country =~ /([A-Za-z]+[1-9]*)/);
  if (!$game) {		# game had no letters in it e.g. '7'
    ($game) = ($country =~ /([A-Za-z1-9]*)/);
    $game = 'game_'.$game;
  }
}

#       --- Initialization Functions ---
#
# Initialize variables
sub init {
  local ($me, $sym_link, $pwd);

  # The following code adds directories to the system array @INC.
  # This is where perl looks for tools.pl, mail.pl, ...
  # pei will also look in these directories for help files.
  if (-l $0) {
    $sym_link = readlink($0);
    $sym_link =~ s|/[^/]+$||;
    if ($sym_link =~ m|^/|) {	# absolute link
      $me = $sym_link;
    } else {
      $me = $0;			# relative link
      $me =~ s|[^/]+$|$sym_link|;
    }
    push(@INC, $me);
  }

  # This code adds the directory from which pei was run to @INC:
  if ($0 =~ m|^(\S+)/[^/]+$|) {
    push(@INC, $1);
  } else {
    chop($pwd = `pwd`);
    push(@INC, $pwd);
  }
  eval("require 'mail.pl'");
  &nofind_error("mail.pl", $@) unless $mail_loaded;

  # Socket connection code
  eval ("require 'sock.pl';");
  &nofind_error('sock.pl', $@) unless defined &sock'open; #'

  # pei Local% commands
  eval ("require 'local.pl';");
  &nofind_error('local.pl', $@) unless $local_loaded;

  # Map the following commands to perl functions:
  $functionmap{"eval"} = '$_';
  $functionmap{"print"} = '$_';
  $functionmap{"undef"} = '$_';
  $functionmap{"exec"} = '&execfile';
  $functionmap{"runfeed"} = '&runfeed';
  $functionmap{"history"} = '&history';

  # This is the length of our history list.
  $maxhistorysize = 100 unless $maxhistorysize;
  # Beep when we are informed of an incoming telegram
  $beep = 1;

  # If we recieve an interrupt, call &dokill
  $SIG{'INT'} = 'dokill';	# call &dokill if the user types ^C

  # If our pipe breaks, call &broken_pipe
  $SIG{'PIPE'}='broken_pipe';
  
  # These variables are reserved words and are used for parsing:
  $quoted_material = "_Q_M_";	     # material between quotes
  $semicolon_in_quotes = "_S_I_Q_";  # semicolon inside quotes
  
  # Special perl variables
  $; = ',';   # array multiple index emulation
  $, = ", ";  # for printing arrays

  # The following variables are used in &connect_to_server:
  eval("require 'sys/errno.ph'");
  if (!defined(&ECONNREFUSED)) {
    eval 'sub TCP_EBASE {100;}';
    eval 'sub ENETUNREACH {(17+ &TCP_EBASE);}';
    eval 'sub EISCONN {(22+ &TCP_EBASE);}';
    eval 'sub ETIMEDOUT {(26+ &TCP_EBASE);}';
    eval 'sub ECONNREFUSED {(27+ &TCP_EBASE);}';
  }

  # If any of the following bits are set in $status, then pei will drop
  # what it was doing and try to recover gracefully.
  $S_EOF         = 1 << 0;	# Server EOF
  $S_TIMEOUT     = 1 << 1;      # S_read timed out waiting for server output
  $S_INTERRUPT   = 1 << 2;      # The user typed ^C
  $S_BROKEN_PIPE = 1 << 3;      # A pipe broke (usually 'q' in 'more')
  $S_NEW_GAME    = 1 << 4;      # The user wants to start a new game

  # We start with the socket closed.
  $status = $S_EOF;

  # This name is incremented to get unique filehandle names.
  $new_filehandle = 'pei00000001';

  &local_init;

$C_CMDOK   = '0';
$C_DATA    = '1';
$C_INIT    = '2';
$C_EXIT    = '3';
$C_FLUSH   = '4';
$C_NOECHO  = '5';
$C_PROMPT  = '6';
$C_ABORT   = '7';
$C_REDIR   = '8';
$C_PIPE    = '9';
$C_CMDERR  = 'a';
$C_BADCMD  = 'b';
$C_EXECUTE = 'c';
$C_FLASH   = 'd';
$C_INFORM  = 'e';
$C_SYNC    = 'f';
}

# Parse command-line arguments to this program
sub parse_argv {
  if (@ARGV[0] eq '-n') {
    $no_tools = 1;
    shift(@ARGV);
  }
  local ($c,$r,$h,$p) = @ARGV;

  if ($games{$c}) {
    &set_game($c);
    return;
  }
  if ($p) {
    $country = $c;
    $representative = $r;
    $host = $h;
    $port = $p;
  } elsif ($c) {
    print STDERR <<'EOF';
Usage:
  pei [ -n ] [country] [representative] [host] [port]
    -or-
  pei [ -n ] [gamename]
EOF
    &do_exit;
  }  
  if (!$country &&
      $ENV{'COUNTRY'} &&
      $ENV{'PLAYER'} &&
      $ENV{'EMPIREHOST'} &&
      $ENV{'EMPIREPORT'}) {
    $country = $ENV{'COUNTRY'};
    $representative = $ENV{'PLAYER'};
    $host = $ENV{'EMPIREHOST'};
    $port = $ENV{'EMPIREPORT'};
  }
}

#       --- Read and Parse User Input ---

# Parse a single commandline
sub parse_commandline {
  local ($_) = @_;		# the command to be parsed
  local ($redir) = '';		# output redirection
  local ($al);			# expanded alias
  local ($FH);			# filehandle for redirected output

  return if /^\s*$/;		# if commandline is all whitespace
    
  s/^\s*(\S.*)$/$1/;		# remove whitespace at beginning
  s/^(.*\S)\s*$/$1/;		# remove whitespace at end

# Send scommand to sprompt if there is one:
  if ($mode eq $C_FLUSH) {		# sprompt
    if (/^\?/) {		# commandline starts with ?
      print $S "$'\n";		# send text to server
      &slurp;			# read & parse server output until prompt
      return;
    }
    else {
      local ($commandline) = $_; # store $_ temprarily because &suck changes it
      &abort_command; 		# send abort to server and read until cprompt
      $_ = $commandline;
    }
  } elsif (/^\?/) {           # ? encountered but not in sprompt
    return;			# ignore the command
  }

  if (/^:/) {			# command starts with :
    local ($syscommand) = $';
    $syscommand =~ s/~/$ENV{'HOME'}/g; # replace ~ with value of $HOME
    system($syscommand);	# send the command to the system
    return;
  }

# Split command and commandargs:
  ($command,$commandarg)=split(/\s+/,$_,2);

# Parse Aliases:
  if (defined($alias{$command}) && # is the command aliased to anything?
      !grep(/^$command$/, @expanded)) { # we haven't expanded this alias yet
    push(@expanded, $command);	# add $command to list of expanded aliases
    $al = $alias{$command};	# expand the alias
    # Check if there are any $1 type variables in the alias:
    if ($al =~ /\@_\[[0-9]\]/ ||   # If there are $1 or $+ variables in
	$al =~ /\@\{_\[[0-9]\]/ || # the expansion
	$al =~ /\$\+/) {
      /^\s*\S+\s+(\S.*)$/;	# prepare for $+ substitution
      split;			# prepare for $1, $2, ... substitution 
      $al = eval(''.&quotify($al)); # substitute $1, $2, ... and $variables
    } else {
      &strip_quotes(*al);	# remove bracketing quotes from expansion
      if ($commandarg) {	# if we called the alias with arguments
	$al = "$al $commandarg"; # put the arguments after the expansion
      }
    }
    &parse_line($al);	# send the expanded alias to the line parser
    pop(@expanded);
    return;
  }

# File Redirection
  # If we have a > or a | which is not in quotes, redirect the output:
  if (/ (>!|>>|>|\|)/ &&
      !/\"[^\"]*[>|\|][^\"]*\"/ &&
      !/\'[^\']*[>|\|][^\']*\'/) {
    local ($commandline, $rtype, $fname) = ($`, $1, $');
    $fname =~ s/~/$ENV{'HOME'}/g; # replace ~ with $HOME
    # If redirecting to file, check for valid unix filename
    if ($rtype ne '|') {
      if ($fname =~ /^\s*([\/\.\w]+)\s*$/) {
	$fname = $1;
      } else {
	print STDERR "Illegal filename: \"$fname\".  Redirection failed.\n";
	return;
      }
    }
    # If you want to overwrite an existing file, you must use >!
    if ($rtype eq '>' && -e $fname && $fname ne '/dev/null') {
      print STDERR "Error: File \"$fname\" exists.  Use >! to overwrite\n";
      print STDERR "       or >> to append.  Redirection failed.\n";
      return;
    }
    elsif ($rtype eq '>!') {
      $rtype = '>';
    }
    $redir= $rtype . $fname;	# where we will send output

    $FH = ++$new_filehandle;
    # Open the file or pipe:
    if (open($FH, $redir)) {
      $_ = $commandline;	# our commandline
      $prev_FH{$FH} = select($FH); # send all new output to $FH
      ($command,$commandarg)=split(/\s+/,$_,2); # split command and commandargs
      if ($command ne "echo") {
	print "$_\n";		# print our commandline to $FH
      }
    } else {
      print STDERR "Unable to open $redir\n";
      return;
    }
  }

  if ($command =~ /^[\$\&\@\%\{]/) { # if command starts with $, &, @, %, or {
    eval($_);			     # it's perl -- eval it!
  } elsif (defined($functionmap{$command})) { # is my command a function map?
    if ($functionmap{$command} =~ /^&tools_/) {
      # Call the function in the tool package for this game
      if ($terse) {
        $| = 1;
	*UNTERSEOUT = select(DEVNULL);
	eval('&'.$game."'tools_".$'); 
	select(UNTERSEOUT);
	$| = 0;
	print UNTERSEOUT "\n";
      } else {
	eval('&'.$game."'tools_".$'); 
      }
    } else {
      eval($functionmap{$command});
    }
  } elsif (/\S/) {		# if my commandline is not empty
    if (!&local_prompt) {    # Parse local command
# The commandline is an ecommand.  Put double quotes around it and eval it.
# This will ensure that any $variables will get replaced with their values
      print $S eval(''.&quotify($_)) . "\n"; # send the command to the server
      &slurp;			# read & parse server output until prompt
    }
  }
  if ($FH) {			# do we have file redirection?
    select($prev_FH{$FH});	# revert to the old filehandle output
    close($FH);			# close the file or pipe (this will flush)
  }
}

# Send a single ecommand to the server
sub singlecommand {
  local ($commandline) = @_;
  print $S "$commandline\n";	# Go directly to server.  Do not collect $200
  ($command,$commandarg)=split(/\s+/,$commandline,2);
  &suck;			# read & parse server output until cprompt
}

# Parse one line of input.  We assume the newline has been stripped.
# This function splits a line into commandlines separated by
# semicolons.  Most of the work in this function consists of taking
# special care of semicolons that are in quotes.
sub parse_line {
  local ($_) = @_;		# put the line in $_
  local (@stack);		# what was between quotes
  local (@commandlines);	# commandlines delimited by semi-colon

  return if /^\s*\#/;		# ignore comments

  if (!/;/) {			# line has no semi-colons.  parse it!
    &parse_commandline($_);	# the line is a commandline
    return;
  }

  # Replace semicolons in ' quotes by the value of $semicolon_in_quotes:
  while (s/(\'[^\']*\')/$quoted_material/) {
    push(@stack, $1);		# put the ' quoted stuff into @stack
  }
  for (@stack) {
    s/;/$semicolon_in_quotes/g;	# codify semi-colons in ' quotes
  }
  s/$quoted_material/shift(@stack)/ge; # put quoted stuff back in

# The following few lines replace semicolons inside quotes by the value
# of $semicolon_in_quotes:
  while (s/(\"[^\"]*\")|(\'[^\']*\')/$quoted_material/) {
    push(@stack, $1);		# put the quoted stuff onto @stack
  }
  for (@stack) {
    s/;/$semicolon_in_quotes/g;	# codify semi-colons in quotes
  }
  s/$quoted_material/shift(@stack)/ge; # put quoted stuff back in

  @commandlines = split(/;/);	# chop the line into commandlines
  for (@commandlines) {
    s/$semicolon_in_quotes/;/g;	# put the semicolons back
    &parse_commandline($_);	# parse each commandline
    last if $status;
  }
}

# Note that ^C from main prompt doesn't work.
# Read input lines from standard input and parse them
sub mainloop {
  local ($rin, $rout);
  local ($line);		# line of my input
  
  ++$depth;			# used by &recover
  &prompt;
  while (1) {
    if ($sock'TelnetBuffer{$S}) { #'
      while ($sock'TelnetBuffer{$S}) { #'
        $_ = &sock'S_read($S, $TIMEOUT); #'
        last if $status;
        &parse_serverline;
      }
    }
    vec($rin, fileno($S), 1)=1;
    vec($rin, fileno(STDIN), 1)=1;
    select($rout=$rin, undef, undef, undef);
    if ($status) {
      last if &recover(1);
      &prompt;
      next;
    }
    if (vec($rout, fileno($S), 1)) { # Async server data
      $_ = &sock'S_read($S, $TIMEOUT); #'
      if ($status) {last if &recover;}
      &parse_serverline;
    }
    if (vec($rout, fileno(STDIN), 1)) {
      sysread(STDIN, $line, 2048);
      if ($line) {
	chop($line);
        if ($mode eq $C_PROMPT) {
          &history_expansion;
          @expanded = ();		# reset expanded alias array
          &parse_line($line); # parse our line of input
	  if ($status) {last if &recover(1);}
        } elsif ($mode eq $C_FLUSH) {	# sprompt
          print $S "$line\n";
          &slurp;			# read & parse server output until prompt
	  if ($status) {last if &recover(1);}
	} else {
	  print STDERR "Lost sync with server.\n";
	  if ($status) {last if &recover;}
	  &suck;
	  if ($status) {last if &recover;}
	}
      } else {
	if ($mode eq $C_PROMPT) {
	  if ($status) {last if &recover(1);}
	} elsif ($mode eq $C_FLUSH) {
          if ($status) {last if &recover(1);}
	  &abort_command;
	  if ($status) {last if &recover;}
	} else {
	  print STDERR "Lost sync with server.\n";
	  &suck;
	  if ($status) {last if &recover;}
	}
      }
      &prompt;
    }
  }
  --$depth;
}

# The following code does !!, !abc, and ^a^b history expansion:
sub history_expansion {
  local ($i);			# history index

  if ($line eq '!!' && @history) { # repeat last command
    $line = $history[$#history];
    print "$line\n";
  } else {
    if ($line =~ /^!(\S+)/) {	# !abc type substitution
      local ($search) = $1;
      local ($histarg) = $';
      for ($i = $#history; $i >= 0; --$i) {
	if ($history[$i] =~ /^$search/) {
	  $line = $history[$i].$histarg;
	  print "$line\n";
	  last;
	}
      }
    } elsif ($line =~ /^\^(.*)\^(.*)$/) { # ^a^b type substitution
      local ($replace) = $2;
      $line = $history[$#history];
      $line =~ s/$1/$replace/;
      print "$line\n";
    }
    if ($line =~ s/!!/$history[$#history]/ge) {
      print "$line\n";
    }
    # Put $line on our history list
    shift(@history) if @history == $maxhistorysize;
    push(@history, $line);
  }
}

# Read lines from a file and parse them
sub fmainloop {
  local ($FH) = @_;		# The filehandle we're reading input from

  ++$depth;			# used by &recover
  while (<$FH>) {		# while there's still lines to read in the file
    if ($mode eq $C_PROMPT && !$quiet) { # if cprompt and not quiet
      print "\n$tele{$game}$game [$timeused:$btus]% $_"; # print cprompt and line
    }
    chop;			# remove newline
    @expanded = ();		# reset expanded alias array
    &parse_line($_);		# parse our line of input
    if ($status) {last if &recover;}
  }
  --$depth;
}


#       --- Read and Parse Server Output ---
#
# This command assumes that the serverline is in $_.  It sets the
# $mode, $timeused, and $btus.  It returns 1 if $_ is a prompt.
# Otherwise it prints $_.
sub parse_serverline {
  local ($lastmode) = $mode;
  local ($lastserverline) = $serverline;
  $mode = substr($_,0,1);		# grab the mode
  $serverline = $_ = substr($_,2); # cut the mode part off

  if (defined($parsemap{$command})) {
    eval("&".$game."'$parsemap{$command}");
  }

  if ($mode eq $C_DATA) {		# normal data
    print $_;
    return 0;
  }
  if ($mode eq $C_PROMPT) {		# cprompt
    ($timeused,$btus) = split(' ');
	return 1;
  }

  return 1 if $mode eq $C_FLUSH;	# sprompt

  if ($mode eq $C_EXIT) {		# server error
    print STDERR "$_";
    &close_connection(1);
    return 1;
  }

  if ($mode eq $C_FLASH) {		# "flash" telegram from another player
    $mode = $lastmode;
    $serverline = $lastserverline;
    print "\n$_";
    return ($mode eq $C_PROMPT || $mode eq $C_FLUSH);
  }

  if ($mode eq $C_SYNC) {	# "flash" telegram from another player
    $mode = $lastmode;
    $serverline = $lastserverline;
    print "\n$_";
    return ($mode eq $C_PROMPT || $mode eq $C_FLUSH);
  }

  if ($mode eq $C_INFORM) {		# We just got a telegram
    chop($tele{$game} = $_);
    if ($tele{$game}) {    
      print "\07" if $beep;
      $tele{$game} = "$tele{$game} ";
    }
    $mode = $lastmode;
    $serverline = $lastserverline;
    if ($mode eq $C_PROMPT) {
      &prompt;
    } elsif ($mode eq $C_FLUSH) {
      $| = 1;
      print "\n$tele{$game}$serverline";
      $| = 0;
    }
    return ($mode eq $C_PROMPT || $mode eq $C_FLUSH);
  }

  if ($mode eq $C_EXECUTE) {
    s/~/$ENV{'HOME'}/g;
    if (open(EXEC, "<$_")) {
      print $S $_ while (<EXEC>);
      close EXEC;
    } else {
      print STDERR "Unable to open file \"$_\" for input\n";
    }
    print $S "ctld\n";
    &slurp;
    return 1;
  }

  if (!$mode) {
    $status |= $S_EOF;
    return 1;
  }
  print STDERR "Strange Empire packet: mode = ($mode) data = ($_)\n";
}

# This function reads lines from the server until a prompt
# is encountered.  If we have written a perl function to parse the output
# of our command, then we call that function.  Note that when the parsemap
# is called, the serverline is in $_.
sub slurp {
  while (1) {
    $mode = -1;			# waiting for output from server
    $_ = &sock'S_read($S, $TIMEOUT);
    last if $status;
    last if &parse_serverline;	# we got a prompt
    last if $status;		# socket closed
  }
}

# This function is exactly the same as &slurp except that it ignores sprompts.
sub suck {
  do {
    while (1) {
      $mode = -1;		# waiting for output from server
      $_ = &sock'S_read($S, $TIMEOUT);
      last if $status;
      last if &parse_serverline; # we got a prompt.
      last if $status;
    }
    last if $status;
    if ($mode ne $C_PROMPT) {		# if not a cprompt
      print $S "aborted\n";	# send abort to server
    }
  } while ($mode eq $C_FLUSH);	# while we're at an sprompt
}

#      --- Local Prompt ---
#
# Parse local commands
sub local_prompt {
  if (defined $lcommand{$command}) {
    eval($lcommand{$command});
    return 1;
  }
  0;
}

# We enter this loop at the start and when we leave a game.
# Local commands are accepted.  We leave the loop as soon as the
# user has specified a game they want to connect to.
sub localloop {
  local ($line);  
  local ($rin, $rout);
  $status &= ~$S_NEW_GAME;	# Clear the new game flag.

  &do_exit unless -t STDIN;	# Don't ask tricky questions to a file
  do {
    &prompt;
    vec($rin, fileno(STDIN), 1)=1;
    select($rout=$rin, undef, undef, undef);
    if ($status & $S_INTERRUPT) {
      &ask_to_die;
      $status &= ~$S_INTERRUPT;
      &ih_print("And now back to your regularly scheduled Local prompt...\n");
    } elsif (vec($rout, fileno(STDIN), 1)) {
      sysread(STDIN, $line, 2048);
      chop($line);
      if ($line) {
	next if $line =~ /^\s*$/;
	($command,$commandarg) = split(/\s+/,$line,2);
	print "Not a local command, and not connected to a game.\n"
	  unless &local_prompt;
	$status &= ~$S_BROKEN_PIPE if $status & $S_BROKEN_PIPE;
      }
    }
  } while (!($status & $S_NEW_GAME));
}

# Prompt the user for input
sub prompt {
  $| = 1;
  if (@_) {
    print pop(@_) . ' ';
  } else {
    if (defined $game) {
      if ($mode eq $C_PROMPT) {
	print "\n$tele{$game}$game [$timeused:$btus]% ";
      } elsif ($mode eq $C_FLUSH) {
	chop($serverline);	# remove newline
	print "$serverline";
      } else {
	print "$game [mode = $mode]% ";
      }
    } else {
      print "Local% ";
    }
  }
  $| = 0;
}

# Given a value of %game, set the country, rep, host, port.  Also, try to 
# change directory to this game's directory.
sub set_game {
  ($game, $no_new_status) = @_;
  local ($dir);
  
  return unless $games{$game};
  split(/\s+/, $games{$game});	# the five fields are separated by a whitespace
  $country        = @_[0];
  $representative = @_[1];
  $host           = @_[2];
  $port           = @_[3];
  if ($port !~ /^\d+$/) {
    print STDERR "Error: Port is \"$port\" for game \"$game\", but it should be a number.\n";
    &close_connection;
    return;
  }
  # Change to the game's directory
  $dir = eval(''.&quotify(@_[4]));
  $dir =~ s/~/$ENV{'HOME'}/g;
  if (!chdir($dir)) {
    print STDERR "Cannot change directory to: \"".eval(''.&quotify(@_[4]))."\"\n";
  }

  $status |= $S_NEW_GAME unless $no_new_status;
}

#       --- Connect to Server ---
#
# Connect to the server
sub connect_to_server {
  local ($user, $line, $i);

  print STDERR "Opening socket..." if $debug;

  if ($firewall) {
    $S = &sock'open($proxyhost, $proxyport); #'
  } else {
    $S = &sock'open($host, $port); #'
  }
  if (!$S) {
    print "\n$!\n";
    print STDERR "Game is probably not up.\n" if $! == &ECONNREFUSED;
    print STDERR "Network problems?\n" if $! == &ETIMEDOUT;
    print STDERR "Good luck.\n" if $! == &ENETUNREACH;
    print STDERR "Already connected.\n" if $! == &EISCONN;
    return 0;
  }
  $socket{$game} = $S;
  print STDERR "done\n" if $debug;

  if ($firewall) {
    while ($i < $TIMEOUT) {
      $line = &sock'S_read($S, $TIMEOUT); #'
      return 0 if $status;
      chop($line);
      if ($line =~ m/^.*$proxyprompt.*$/) { #Its time to connect
	print $S "$host $port\n";           #Send the server address
	last;
      } else {
	++$i;
      }
    }
    return 0 unless $i < $TIMEOUT;
  }

  # Send my country information to the server:
  $user = $ENV{'USER'};
  $user = "nobody" unless $user;
  print STDERR "user $user\n" if $debug;
  print $S "user $user\n";
  return 0 unless &got_from_server(".*$C_INIT Empire server ready");
  print STDERR "\n";  
  return 0 unless &got_from_server("$C_CMDOK hello $user");

  print STDERR "client pei $version\n" if $debug;
  print $S "client pei $version\n";
  return 0 unless &got_from_server("$C_CMDOK talking to pei $version");

  print STDERR "coun $country\n" if $debug;
  print $S "coun $country\n";
  return &ask_sanc unless &got_from_server("$C_CMDOK country name $country");
  print STDERR "\n";

  print STDERR "pass $representative\n" if $debug;
  print $S "pass $representative\n";
  return &ask_sanc unless &got_from_server("$C_CMDOK password ok");
  print STDERR "        -=O=-\n";
  1;
}

sub ask_sanc {
  print $S "sanc\n";
  do {
    $_ = &sock'S_read($S, $TIMEOUT); #'
    last if /^$C_BADCMD/;
    print STDERR substr($_, 2);
  } while !/^$C_CMDOK/;
  0;
}

# return 1 if ok, return 0 if not ok.
sub got_from_server {
  local($expect) = (@_);

  $_ = &sock'S_read($S, $TIMEOUT); #'
  print "status = $status\n" if $debug;
  return 0 if $status;
  print STDERR "$_\n" if $debug;

  chop;
  return 1 if /^$expect$/;
  return 1 if /^$C_BADCMD Command client not found/; # Empire2 doesn't support

  if (/^$C_CMDERR / ||
      /^$C_BADCMD / ||
      /^$C_EXIT / && $expect eq '$C_INIT 2') {
    print "$'\n";
  } else {
    print STDERR "Expected: \"$expect\"\nGot: \"$_\"\n";
  }
  0;
}

#       --- Error Recovery ---
#
# Call this function if our process is killed
sub dokill { 
  $SIG{'INT'} = 'dokill';	# call &dokill if the user types ^C

  print STDERR "\n";
  if (!$coun || $nstatus eq 'DEITY') {
    &ih_print("DEITY interrupt <-+->\n");
    &ih_print("This must mean trouble...\n");
    print STDERR "pei shutting down...\n";
    exit;
  }
  &ih_print("pei interrupt handler invoked <-+->\n");

  # Abort wait
  $mode = 4 if $command eq 'wait';

  if ($status & $S_INTERRUPT) {
    &ih_print("Interrupt received while handling another interrupt.\n");
    &ask_to_die;
    &ih_print("Second interrupt ignored.\n");
    return;
  } else {
    $status |= $S_INTERRUPT;
  }
}
  
# Called when a pipe breaks
sub broken_pipe {
  $SIG{'PIPE'}='broken_pipe';
  $status |= $S_BROKEN_PIPE;
}

sub ih_print {
  print STDERR "<-+-> ".pop(@_);
}

sub ask_to_die {
  local ($char);

  &do_exit if !(-t STDIN);		# Don't ask tricky questions to a file

  $char = &getchar('Are you sure you want to exit pei? (y/n)');
  return unless $char eq 'y';
  &do_exit;
}

# When $status is not zero, loops and functions abort gracefully until
# We are at the top level input loop.  Then this function is called.
# If STDIN is not a file, then we check $status to see what all the fuss
# is about and then give the user a list of choices for how to deal with
# the interruption.
sub recover {
  local ($at_prompt) = @_;
  local ($char, $line, $rmask, $nfound);

  # Don't recover until we're at the top level input loop
  last if $depth > 1;

  # Go back to the connectloop to open a new game.
  return 1 if $status & $S_NEW_GAME;
  if ($status & $S_BROKEN_PIPE) {
    $status &= ~$S_BROKEN_PIPE;
    return 0;
  }
  if (!(-t STDIN)) {		# Don't ask tricky questions to a file
    $line = 'Server EOF' if $status & $S_EOF;
    $line = 'Server Timeout' if $status & $S_TIMEOUT;
    $line = 'Process Interrupted' if $status & $S_INTERRUPT;
    print STDERR "$line\n";
    &do_exit;
  }

  if ($status & $S_EOF) {
    if ($socket{$game}) {
      print STDERR "\nServer EOF\nGame died.\n";
      &close_connection(1);
    }
    return 1;
  }
  if ($status & $S_TIMEOUT) {
    print STDERR "\nNet lag.  Retry, Close connection, or More info? ";
    $char = &getchar('(r/c/m)');
    if ($char eq 'r') {
      &slurp;
      return &recover if $status;
      return 0;
    } elsif ($char eq 'c') {
      &close_connection(1);
      return 1;
    }
    print STDERR <<"EOF";
pei has not heard from the Empire Server in $TIMEOUT seconds.
Your last command was "$command".
The last line pei read from the server was:
"$serverline"
If you want to change the length of time that pei waits to hear from the
server, then set the value of \$TIMEOUT in the file "~/.peirc".  For example,
if you had a really bad lag, you might put the line:
\$TIMEOUT = 360
in the file ~/.peirc to let pei wait 360 seconds to receive output from the
server.
EOF
    return &recover;    
  }
  if ($status & $S_INTERRUPT) {
    $status &= ~$S_INTERRUPT;
    if ($at_prompt) {
      if ($mode eq $C_PROMPT) {
	&ih_print("Interrupt received at main prompt.\n");
	$char = &getchar('Close connection? (y/n)');
	if ($char eq 'y') {
	  &close_connection(1);
	  return 1;
	}
        return 0;
      } elsif ($mode eq $C_FLUSH) {
	&ih_print("Interrupted received at sub-prompt for the command \"$command\".\n");
	$char = &getchar("Abort $command? (y/n)");
	if ($char eq 'y') {
	  &abort_command; return &recover if $status; return 0;
	}
	return 0;
      }
    }
    if ($mode == -1) {
      &ih_print("Interrupted while waiting for data from server (pei will naturally\n");
      &ih_print("timeout after \$TIMEOUT = $TIMEOUT seconds).\n");
    } 
    &recover_data if $sock'TelnetBuffer{$S}; #'
    $rmask = "";
    vec($rmask, fileno($S), 1) = 1;
    ($nfound, $rmask) = select($rmask, undef, undef, 0.01);
    if ($nfound) {return 1 if &recover_data;}
    &ih_print("Would you like to: resync pei with the server\n");
    &ih_print("                   test the connection to the server\n");
    &ih_print("                   close the connection\n");
    $char = &getchar('(r/t/c)');
    if ($char eq 'r') {
      &flush_server_output; return &recover if $status; return 0;
    } elsif ($char eq 't') {
      &ih_print("Testing connection with 10 second timeout...");
      vec($rmask, fileno($S), 1) = 1;
      ($nfound, $rmask) = select($rmask, undef, undef, 10);
      print STDERR "done\n";
      if ($nfound) {
        return 1 if &recover_data
      }
      &ih_print("I think there's nobody home.\n");
      &close_connection(1); return 1;
    }
    &close_connection(1); return 1;
  }
  print STDERR "Internal error: Unknown status [$status]\n";
}

sub getchar {
  local ($line, $char);
  local ($rin, $rout);

  do {
    $status &= ~$S_INTERRUPT;
    &prompt(@_);
    vec($rin, fileno(STDIN), 1)=1;
    select($rout=$rin, undef, undef, undef);
    if ($status & $S_INTERRUPT) {
      &ask_to_die;
      &ih_print("And now back to your regularly scheduled interrupt recovery prompt...\n");
    } elsif (vec($rout, fileno(STDIN), 1)) {
      sysread(STDIN, $line, 2048);
      if ($line) {
	$char = substr($line,0,1);
      }
    }
  } while ($status & $S_INTERRUPT || !$char || $char !~ /^[a-z]$/);
  $char;
}

# There is data on the server.  Give the player appropriate choices
sub recover_data {
  &ih_print("There is output from the Empire Server waiting to be read.\n");
  &ih_print("Would you like to: read server output\n");
  &ih_print("                   flush server output without reading it\n");
  &ih_print("                   close the connection\n");
  $char = &getchar('(r/f/c)');
  if ($char eq 'r') {
    &slurp; return &recover if $status; return 0;
  } elsif ($char eq 'f') {
    &flush_server_output; return &recover if $status; return 0;
  }
  &close_connection(1);
  return 1;
}

# Flush server output without parsing
sub flush_server_output {
  local($syncronized, $wmask, $nfound);

  print STDERR "Testing to see if the server is listening...\n";
  print STDERR "Client: \"Hey server, you've got smelly sockets!\"\n";
  vec($wmask, fileno($S), 1) = 1;
  ($nfound, $wmask) = select(undef, $wmask, undef, $TIMEOUT);
  if (!$nfound) {
    print STDERR "Darn!  The server is ignoring us!\n";
    &close_connection(1);
  }
  print STDERR "Server: \"They smelled fine until you started sucking on them!\"\n";
  print STDERR "(The server is listenning.)\n";
  print STDERR "Flushing server output...";
  print $S "aborted\nspy -1\n";
  return if $status;
  $mode = -1;
  do {
    # flush server output until cprompt
    while (1) {
      $_ = &sock'S_read($S, $TIMEOUT);
      last if $status;
      $mode = substr($_,0,1);   # grab the mode
      if ($mode eq $C_FLUSH) {
	print $S "aborted\n";
	last if $status;
      }
      $syncronized = 1 if (/^1 Usage: spy / ||
			   /^1 "spy -1" is not a legal command/);
      last if ($mode eq $C_PROMPT);   # we got a cprompt
      $mode = -1;
    }
  } while (!$syncronized && !$status);
  return 1 if $status;
  &parse_serverline;          # set $timeused and $btus
  return 1 if $status;
  print STDERR "done\n";
}

sub nofind_error {
  local ($file, $error, $no_exit) = @_;

  print STDERR "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
  if ($error) {
    print STDERR "ERROR: pei could not load the file \"$file\" because of the following error:\n";
    warn $error;
  }
  print STDERR "WARNING:  pei could not locate the file \"$file\"\n";
  print STDERR "          pei looked in the following directories for \"$file\":\n";
  for (@INC) {
    print STDERR "          $_\n";
  }
  print STDERR "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n";
  exit 1 unless $no_exit;
}
    
# Send an abort to the server, read & parse server output until cprompt.
# Assume we're at an sprompt of $command.
sub abort_command {
  print STDERR "$command aborted.\n";
  print $S "aborted\n";
  &suck;
}

sub close_connection {
  local ($save_DB) = @_;

  if ($save_DB) {
    &set_game($game, 1);
    eval ("&".$game."'save_DB");
  }

  if ($socket{$game}) {
    print STDERR "Closing connection for $game...";
    &sock'close($socket{$game}); #'
    print STDERR "*CLICK*\n\n";
  }
  undef $socket{$game};
  undef $S;
  undef $country;
  undef $game;
  $status |= $S_EOF;
}

# Exit nicely
sub do_exit {
  for $game (keys %socket) {
    next unless $socket{$game};
    &close_connection(1);
  }
  &sock'close_all; #' Just in case we missed one.
  print STDERR "pei shutting down...\n";
  exit;
}

#       --- Read Init File ---
#
# Read commands from ~/.peirc.  If there is no ~/.peirc, then try to make
# one from an existing ~/.eifrc.
sub read_peirc {
  local (*PEIRC);

  if (!(-r "$ENV{'HOME'}/.peirc")) {   # if no ~/.peirc
    if (-r "$ENV{'HOME'}/.eifrc") { # if there is a ~/.eifrc
      &convert_eifrc_to_peirc;
    }
  }

  if (open(PEIRC,"<$ENV{'HOME'}/.peirc")) {
    $quiet = 1;			# don't print commandlines read from ~/.peirc
    while (<PEIRC>) {
      if (/^\s*$/ || /^\s*#/) {	# if all whitespace or commant
        next;			# skip this line of input
      }
      if (/^[\$\&\@\%\{]/) {	# if it's perl
	eval($_);		# evaluate it
	next;
      }
      chop;			# remove newline
      ($command,$commandarg)=split(/\s+/,$_,2); # split command and commandargs
      &local_prompt;
      if ($status & $S_INTERRUPT) {
	&ih_print("closing ~/.peirc");
	$status &= ~$S_INTERRUPT;
	last;
      }
    }
    $quiet = 0;			# print commandlines read from future files
    close (PEIRC);
  } else {
    print STDERR "Unable to open ~/.peirc for input\n";
  }

  &set_game($AUTOGAME) if defined($AUTOGAME) && defined($games{$AUTOGAME});
  if (!defined($PEIPATH)) {
    if (defined($ENV{'PEIPATH'})) {
      $PEIPATH = $ENV{'PEIPATH'};
    } else {
      for (@INC) {
	if (-r "$_/help.pei") {
	  $PEIPATH = $_;
	  last;
	}
      }
      &nofind_error("help.pei", 0, 1) unless $PEIPATH;
    }
  }

  chop($PEIPATH = `pwd`) if $PEIPATH eq '.';
  if (!(-r "$PEIPATH/help.pei")) {
    print STDERR <<EOF;
Unable to find the file "help.pei" in the directory
\$PEIPATH = "$PEIPATH".  Perhaps you should set \$PEIPATH explicitely
in your ~/.peirc file.
EOF
  }
  push(@INC, $PEIPATH);

  if (!defined($EDITOR)) {
    if (defined($ENV{'EDITOR'})) {
      $EDITOR = $ENV{'EDITOR'};
    } else {
      $EDITOR = "vi";
    }
  }
  if (!defined($PAGER)) {
    if (defined($ENV{'PAGER'})) {
      $PAGER = $ENV{'PAGER'};
    } else {
	$PAGER = "more";
    }
  }
  $TIMEOUT = 120 unless defined $TIMEOUT;
}

sub convert_eifrc_to_peirc {
  local (*PEIRC, *EIFRC);

  print STDERR "Converting ~/.eifrc to ~/.peirc ...";
  if (!open(EIFRC,"<$ENV{'HOME'}/.eifrc")) { # open ~/.eifrc for read
    print STDERR "Unable to open ~/.eifrc for input\n";
    return;
  }
  if (!open(PEIRC,">$ENV{'HOME'}/.peirc")) { # open #/.peirc for write
    print STDERR "Unable to open ~/.eifrc for output\n";
    close(EIFRC);
    return;
  }
  # convert ~/.eifrc to ~/.peirc
  while (<EIFRC>) {
    next if /connect/;
    s/;\s*&/; ?/g;		# replace sub-prompts
    s/@/:/g;		# replace @ with :
    # Replace ${1:x} variables where possible.  Warn if not possible.
    s/\${(\w+):\+}([\s|\"|\'])/\$$1$2/g;
    s/\${(\w+):\+}/${$1}/g;
    if (s/\${(\w+):\-([^}]+)}/".(\$$1?\$$1:"$2")."/g) {
      s/^\s*alias\s+(\w+)\s+(.+)$/alias $1 $2/;
    }
    if (/(\${.*:.*\})/) {
      print STDERR "Unable to convert string \"$&\" on line number $.:\n$_\n\n";
      next;
    }
    if (/^\s*addgame/) {	# convert addgame lines
      split;
      $_ = "addgame @_[1] @_[4] @_[5] @_[2] @_[3] @_[6]\n";
    } elsif (/^\s*setvar\s+(\S+)\s+(\S.*)\n$/) { # convert setvar lines
      $_ = "\$$1 = " . &quotify($2) . "\n";
    }
    print PEIRC;
  }
  close(PEIRC);
  close(EIFRC);
  print STDERR "done\n";
}

#        --- Function Maps ---
#
# This function reads and parses commands from a specified file
sub execfile {
  local ($filename) = $commandarg;
  $filename =~ s/~/$ENV{'HOME'}/g;
  &runcommands ("<$filename");
  print STDERR "End of file \"$filename\"\n" unless $quiet;
}

# This function runs a program and pipes the output to pei
sub runfeed {
  local ($filename) = $commandarg;
  $filename =~ s/~/$ENV{'HOME'}/g;
  eval('&runcommands("'.$filename.'|")');
  print STDERR "End of runfeed \"$filename\"\n";
}

# This function runs commands from a file or pipe
sub runcommands {
  local($FH) = ++$new_filehandle;

  if (!open($FH,pop(@_))) {
    print STDERR "Unable to open $commandarg\n";
    return;
  }
  &fmainloop($FH);		# parse the commands using fmainloop
  close($FH);
}

# Print a history of recent commands.
sub history {
  local ($n) = $commandarg?$commandarg:$maxhistorysize;
  local ($i);			# history index

  $n = $#history if $#history < $n;
  for ($i = $#history - $n; $i < $#history; ++$i) {
    printf "%-4d  %s\n", $i, $history[$i];
  }
}

#       --- Strings Subroutines ---
#
# Prepare a commandline for evaluation by putting double quotes around it.
sub quotify {
  local ($val) = @_;
  if ($val =~ /^\"|^\'/) {	# it's already enclosed in quotes
    return $val;		# leave it alone
  }
  $val =~ s/\"/\\\"/g;		# \ escape internal quotes
  return '"' . $val . '"';	# enclose in double quotes
}

# Remove bracketing quotes from text
sub strip_quotes {
  local (*s) = @_;

  $s =~ s/^\"(.*)\"$/$1/;
  $s =~ s/^\'(.*)\'$/$1/;
}

sub line_prompt {
  local ($line);
  local ($rin, $rout);

  do {
    $status &= ~$S_INTERRUPT;
    &prompt(@_);
    vec($rin, fileno(STDIN), 1)=1;
    select($rout=$rin, undef, undef, undef);
    if ($status & $S_INTERRUPT) {
      &ask_to_die;
      &ih_print("And now back to your regularly scheduled prompt...\n");
    } elsif (vec($rout, fileno(STDIN), 1)) {
      sysread(STDIN, $line, 2048);
      chop($line);
    }
  } while ($status & $S_INTERRUPT);
  $line;
}

sub bug {print STDERR "#$_[0]#\n";}

1;
