David Rose 25 лет назад
Родитель
Сommit
28b9f21a54

+ 14 - 12
ppremake/Makefile.am

@@ -1,15 +1,17 @@
 bin_PROGRAMS = ppremake
 
-ppremake_SOURCES =						\
-    check_include.cxx check_include.h filename.cxx filename.h	\
-    find_searchpath.cxx find_searchpath.h			\
-    gnu_getopt.c gnu_getopt.h					\
-    ppCommandFile.cxx ppCommandFile.h ppDependableFile.cxx	\
-    ppDependableFile.h ppDirectory.cxx				\
-    ppDirectory.h ppDirectoryTree.cxx ppDirectoryTree.h		\
-    ppMain.cxx ppMain.h						\
-    ppFilenamePattern.cxx					\
-    ppFilenamePattern.h ppNamedScopes.cxx ppNamedScopes.h	\
-    ppScope.cxx ppScope.h ppSubroutine.cxx ppSubroutine.h	\
-    ppremake.cxx ppremake.h tokenize.cxx			\
+ppremake_SOURCES =							\
+    check_include.cxx check_include.h filename.cxx filename.h		\
+    find_searchpath.cxx find_searchpath.h				\
+    gnu_getopt.c gnu_getopt.h						\
+    ppCommandFile.cxx ppCommandFile.h ppDependableFile.cxx		\
+    ppDependableFile.h ppDirectory.cxx					\
+    ppDirectory.h ppDirectoryTree.cxx ppDirectoryTree.h			\
+    ppMain.cxx ppMain.h							\
+    ppFilenamePattern.cxx						\
+    ppFilenamePattern.h ppNamedScopes.cxx ppNamedScopes.h		\
+    ppScope.cxx ppScope.h ppSubroutine.cxx ppSubroutine.h		\
+    ppremake.cxx ppremake.h sedAddress.cxx sedAddress.h sedCommand.cxx	\
+    sedCommand.h sedContext.cxx sedContext.h sedProcess.cxx		\
+    sedProcess.h sedScript.cxx sedScript.h tokenize.cxx			\
     tokenize.h

+ 1 - 1
ppremake/configure.in

@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 AC_INIT(ppremake.cxx)
-AM_INIT_AUTOMAKE(ppremake, 0.52)
+AM_INIT_AUTOMAKE(ppremake, 0.54)
 AM_CONFIG_HEADER(config.h)
 
 AC_PREFIX_DEFAULT(/usr/local/panda)

+ 48 - 7
ppremake/ppremake.cxx

@@ -8,6 +8,7 @@
 #include "ppScope.h"
 #include "check_include.h"
 #include "tokenize.h"
+#include "sedProcess.h"
 
 #ifdef HAVE_GETOPT
 #include <getopt.h>
@@ -30,6 +31,7 @@ usage() {
     "\n"
     "ppremake [opts] subdir-name [subdir-name..]\n"
     "ppremake\n"
+    "ppremake -s 'sed-command' <input >output\n"
     "\n"
     "This is Panda pre-make: a script preprocessor that scans the source\n"
     "directory hierarchy containing the current directory, looking for\n"
@@ -51,6 +53,11 @@ usage() {
     "generated.  If no parameter is given, then all directories will be\n"
     "processed.\n\n"
 
+    "ppremake -s is a special form of the command that runs as a very limited\n"
+    "sed.  It has nothing to do with building makefiles, but is provided mainly\n"
+    "so platforms that don't have sed built in can still portably run simple sed\n"
+    "scripts.\n\n"
+
     "Options:\n\n"
 
     "  -h           Display this page.\n"
@@ -66,7 +73,11 @@ usage() {
     "               depend on (need) the named subdirectory.  Options -d and\n"
     "               -n may be combined, and you may also name multiple\n"
     "               subdirectories to scan at once.\n\n"
-    "  -p platform  Build as if for the indicated platform name.\n\n";
+    "  -p platform  Build as if for the indicated platform name.\n"
+    "  -c config.pp Read the indicated user-level config.pp file after reading\n"
+    "               the system config.pp file.  If this is omitted, the value\n"
+    "               given in the environment variable PPREMAKE_CONFIG is used\n"
+    "               instead.\n\n";
 }
 
 static void
@@ -188,13 +199,17 @@ main(int argc, char *argv[]) {
   string progname = argv[0];
   extern char *optarg;
   extern int optind;
-  const char *optstr = "hVPD:p:dn";
+  const char *optstr = "hVPD:dnp:c:s:";
 
   bool any_d = false;
   bool dependencies_stale = false;
-  string platform = PLATFORM;
   bool report_depends = false;
   bool report_needs = false;
+  string platform = PLATFORM;
+  string ppremake_config;
+  bool got_ppremake_config = false;
+  string sed_command;
+  bool got_sed_command = false;
   int flag = getopt(argc, argv, optstr);
 
   while (flag != EOF) {
@@ -220,10 +235,6 @@ main(int argc, char *argv[]) {
       any_d = true;
       break;
 
-    case 'p':
-      platform = optarg;
-      break;
-
     case 'd':
       report_depends = true;
       break;
@@ -232,6 +243,20 @@ main(int argc, char *argv[]) {
       report_needs = true;
       break;
 
+    case 'p':
+      platform = optarg;
+      break;
+
+    case 'c':
+      ppremake_config = optarg;
+      got_ppremake_config = true;
+      break;
+
+    case 's':
+      sed_command = optarg;
+      got_sed_command = true;
+      break;
+
     default:
       exit(1);
     }
@@ -241,6 +266,15 @@ main(int argc, char *argv[]) {
   argc -= (optind-1);
   argv += (optind-1);
 
+  if (got_sed_command) {
+    SedProcess sp;
+    if (!sp.add_script_line(sed_command)) {
+      exit(1);
+    }
+    sp.run(cin, cout);
+    exit(0);
+  }
+
   // If the user supplied one or more -d parameters, then we should
   // not continue unless some of the dependencies were stale.
   if (any_d) {
@@ -257,6 +291,13 @@ main(int argc, char *argv[]) {
   global_scope.define_variable("PACKAGE_FILENAME", PACKAGE_FILENAME);
   global_scope.define_variable("SOURCE_FILENAME", SOURCE_FILENAME);
 
+  if (got_ppremake_config) {
+    // If this came in on the command line, define a variable as such.
+    // Otherwise, the system scripts can pull this value in from the
+    // similarly-named environment variable.
+    global_scope.define_variable("PPREMAKE_CONFIG", ppremake_config);
+  }
+
   PPMain ppmain(&global_scope);
   if (!ppmain.read_source(".")) {
     exit(1);

+ 152 - 0
ppremake/sedAddress.cxx

@@ -0,0 +1,152 @@
+// Filename: sedAddress.cxx
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "sedAddress.h"
+#include "sedContext.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedAddress::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedAddress::
+SedAddress() {
+  _address_type = AT_invalid;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedAddress::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedAddress::
+~SedAddress() {
+  if (_address_type == AT_re) {
+    regfree(&_re);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedAddress::parse_address
+//       Access: Public
+//  Description: Scans the indicated string beginning at the indicated
+//               character position for an address specification,
+//               e.g. a number, $, or a regular expression.  If a
+//               correct address is found, increments p to the first
+//               non-whitespace character past it and returns true;
+//               otherwise, returns false.
+////////////////////////////////////////////////////////////////////
+bool SedAddress::
+parse_address(const string &line, size_t &p) {
+  assert(p < line.length());
+  if (line[p] == '$') {
+    p++;
+    _address_type = AT_last;
+
+  } else if (isdigit(line[p])) {
+    const char *str = line.c_str() + p;
+    char *end;
+    _number = strtol(str, &end, 10);
+    _address_type = AT_numeric;
+    p += (end - str);
+
+  } else {
+    // It must be a regular expression.
+    size_t p0 = p;
+    char delimiter = line[p];
+    p++;
+    if (p < line.length() && delimiter == '\\') {
+      // A backslash might escape the opening character.
+      delimiter = line[p];
+      p++;
+    }
+
+    size_t begin = p;
+    while (p < line.length() && line[p] != delimiter) {
+      if (line[p] == '\\') {
+	p++;
+	// A backslash could escape the closing character.
+      }
+      p++;
+    }
+
+    if (p >= line.length()) {
+      cerr << "Could not find terminating character '" << delimiter
+	   << "' in regular expression: " << line.substr(p0) << "\n";
+      return false;
+    }
+
+    string re = line.substr(begin, p - begin);
+    p++;
+
+    int error = regcomp(&_re, re.c_str(), REG_NOSUB);
+    if (error != 0) {
+      static const int errbuf_size = 512;
+      char errbuf[errbuf_size];
+      regerror(error, &_re, errbuf, errbuf_size);
+
+      cerr << "Invalid regular expression: " << re << "\n"
+	   << errbuf << "\n";
+      return false;
+    }
+
+    _address_type = AT_re;
+  }
+
+  // Skip whitespace following the address.
+  while (p < line.length() && isspace(line[p])) {
+    p++;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedAddress::matches
+//       Access: Public
+//  Description: Returns true if this address exactly matches the
+//               current pattern space.
+////////////////////////////////////////////////////////////////////
+bool SedAddress::
+matches(const SedContext &context) const {
+  switch (_address_type) {
+  case AT_invalid:
+    cerr << "Internal error!\n";
+    assert(false);
+    return false;
+
+  case AT_numeric:
+    return (_number == context._line_number);
+
+  case AT_last:
+    return context._is_last_line;
+
+  case AT_re:
+    return (regexec(&_re, context._pattern_space.c_str(), 0, (regmatch_t *)NULL, 0) == 0);
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedAddress::precedes
+//       Access: Public
+//  Description: Returns true if this address exactly matches the
+//               current line or refers to a previous line.  This
+//               never returns true if the address is a regular
+//               expression type.
+////////////////////////////////////////////////////////////////////
+bool SedAddress::
+precedes(const SedContext &context) const {
+  if (_address_type == AT_numeric) {
+    return (_number <= context._line_number);
+  }
+
+  return false;
+}
+

+ 44 - 0
ppremake/sedAddress.h

@@ -0,0 +1,44 @@
+// Filename: sedAddress.h
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SEDADDRESS_H
+#define SEDADDRESS_H
+
+#include "ppremake.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+class SedContext;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : SedAddress
+// Description : This represents a single address in a sed command,
+//               something like a line number or a regular expression.
+////////////////////////////////////////////////////////////////////
+class SedAddress {
+public:
+  SedAddress();
+  ~SedAddress();
+
+  bool parse_address(const string &line, size_t &p);
+
+  bool matches(const SedContext &context) const;
+  bool precedes(const SedContext &context) const;
+
+private:
+  enum AddressType {
+    AT_invalid,
+    AT_numeric,
+    AT_last,
+    AT_re,
+  };
+  AddressType _address_type;
+
+  int _number;
+  regex_t _re;
+};
+
+#endif

+ 341 - 0
ppremake/sedCommand.cxx

@@ -0,0 +1,341 @@
+// Filename: sedCommand.cxx
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "sedCommand.h"
+#include "sedAddress.h"
+#include "sedContext.h"
+#include "sedScript.h"
+
+#include <regex.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedCommand::
+SedCommand() {
+  _addr1 = (SedAddress *)NULL;
+  _addr2 = (SedAddress *)NULL;
+  _command = '\0';
+  _flags = 0;
+  _active = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedCommand::
+~SedCommand() {
+  if (_addr1 != (SedAddress *)NULL) {
+    delete _addr1;
+  }
+  if (_addr2 != (SedAddress *)NULL) {
+    delete _addr2;
+  }
+  if ((_flags & F_have_re) != 0) {
+    regfree(&_re);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::parse_command
+//       Access: Public
+//  Description: Scans the indicated string at the given character
+//               position for a legal command.  If a legal command is
+//               found, stores it and increments p to the first
+//               non-whitespace character after the command, returning
+//               true.  Otherwise, returns false.
+////////////////////////////////////////////////////////////////////
+bool SedCommand::
+parse_command(const string &line, size_t &p) {
+  // First, skip initial whitespace.
+  while (p < line.length() && isspace(line[p])) {
+    p++;
+  }
+
+  // Now, check for an address.
+  if (p < line.length() && 
+      (isdigit(line[p]) || line[p] == '/' || line[p] == '\\')) {
+    _addr1 = new SedAddress;
+    if (!_addr1->parse_address(line, p)) {
+      return false;
+    }
+
+    if (p < line.length() && line[p] == ',') {
+      // Another address.
+
+      // Skip the comma and more whitespace.
+      p++;
+      while (p < line.length() && isspace(line[p])) {
+	p++;
+      }
+
+      _addr2 = new SedAddress;
+      if (!_addr2->parse_address(line, p)) {
+	return false;
+      }
+    }
+  }
+
+  if (p >= line.length()) {
+    // It's a null command, which is acceptable; ignore it.
+    return true;
+  }
+
+  _command = line[p];
+
+  // Skip more whitespace after the command letter.
+  p++;
+  while (p < line.length() && isspace(line[p])) {
+    p++;
+  }
+
+  // At the moment, we only accept a small subset of sed commands.  We
+  // can add more later as we see the need.
+  switch (_command) {
+  case 'd':
+    // No arguments.
+    return true;
+
+  case 's':
+    // /regexp/repl/flags
+    return parse_s_params(line, p);
+
+  default:
+    cerr << "Unknown command: " << _command << "\n";
+    return false;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::run
+//       Access: Public
+//  Description: Runs the script command, modifying the context and/or
+//               the script position as appropriate.
+////////////////////////////////////////////////////////////////////
+void SedCommand::
+run(SedScript &script, SedContext &context) {
+  // First, see if this command matches the pattern space.
+  bool matches = false;
+
+  if (_addr1 != (SedAddress *)NULL && _addr2 != (SedAddress *)NULL) {
+    // If the user supplied two addresses, all lines inclusive between
+    // the lines matched by the two addresses are considered matching.
+    if (_active) {
+      // We have previously matched _addr1.  Therefore this line is
+      // in, but are the rest of the lines following this one?
+      matches = true;
+      if (_addr2->matches(context)) {
+	// If this line matches addr2, that's the end of our range for
+	// next time.
+	_active = false;
+      }
+    } else {
+      // We have not yet matched _addr1.  This line and subsequent
+      // lines are in only if we match now.
+      if (_addr1->matches(context)) {
+	matches = true;
+	if (!_addr2->precedes(context)) {
+	  _active = true;
+	}
+      }
+    }
+
+  } else if (_addr1 != (SedAddress *)NULL) {
+    // If the user supplied only one address, only those lines that
+    // exactly match the address are considered matching.
+    matches = _addr1->matches(context);
+
+  } else {
+    // If the user supplied no addresses, all lines are considered
+    // matching.
+    matches = true;
+  }
+
+  if (matches) {
+    do_command(script, context);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::parse_s_params
+//       Access: Private
+//  Description: Parses the /regexp/replacement/flags parameters that
+//               follow the 's' command.
+////////////////////////////////////////////////////////////////////
+bool SedCommand::
+parse_s_params(const string &line, size_t &p) {
+  size_t p0 = p;
+  char delimiter = line[p];
+  p++;
+  if (p < line.length() && delimiter == '\\') {
+    // A backslash might escape the opening character.
+    delimiter = line[p];
+    p++;
+  }
+  
+  size_t begin = p;
+  while (p < line.length() && line[p] != delimiter) {
+    if (line[p] == '\\') {
+      p++;
+      // A backslash could escape the closing character.
+    }
+    p++;
+  }
+  
+  if (p >= line.length()) {
+    cerr << "Could not find terminating character '" << delimiter
+	 << "' in regular expression: " << line.substr(p0) << "\n";
+    return false;
+  }
+
+  string re = line.substr(begin, p - begin);
+  p++;
+
+  int error = regcomp(&_re, re.c_str(), 0);
+  if (error != 0) {
+    static const int errbuf_size = 512;
+    char errbuf[errbuf_size];
+    regerror(error, &_re, errbuf, errbuf_size);
+    
+    cerr << "Invalid regular expression: " << re << "\n"
+	 << errbuf << "\n";
+    return false;
+  }
+  _flags |= F_have_re;
+
+  // Get the replacement string.
+  begin = p;
+  while (p < line.length() && line[p] != delimiter) {
+    if (line[p] == '\\') {
+      p++;
+      // A backslash could escape the closing character.
+    }
+    p++;
+  }
+
+  if (p >= line.length()) {
+    cerr << "Could not find terminating character '" << delimiter
+	 << "' in replacement string: " << line.substr(p0) << "\n";
+    return false;
+  }
+
+  _string2 = line.substr(begin, p - begin);
+
+  // Skip the final delimiter.
+  p++;
+  if (p < line.length() && line[p] == 'g') {
+    // Global flag.
+    p++;
+    _flags |= F_g;
+  }
+
+  // Skip any more whitespace after the parameters.
+  while (p < line.length() && isspace(line[p])) {
+    p++;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::do_command
+//       Access: Private
+//  Description: Actually invokes the command, once it has been
+//               determined that the command applied to the current
+//               pattern space.
+////////////////////////////////////////////////////////////////////
+void SedCommand::
+do_command(SedScript &script, SedContext &context) {
+  switch (_command) {
+  case '\0':
+    // Null command.
+    return;
+
+  case 'd':
+    // Delete line.
+    context._deleted = true;
+    script._next_command = script._commands.end();
+    return;
+
+  case 's':
+    // Substitute.
+    do_s_command(context);
+    return;
+  }
+
+  cerr << "Undefined command: " << _command << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedCommand::do_s_command
+//       Access: Private
+//  Description: Invokes the s command, which performs a
+//               pattern/replacement substitution.
+////////////////////////////////////////////////////////////////////
+void SedCommand::
+do_s_command(SedContext &context) {
+  size_t nmatch = _re.re_nsub + 1;
+  regmatch_t pmatch[nmatch];
+
+  string result;
+  const char *str = context._pattern_space.c_str();
+  int error = regexec(&_re, str, nmatch, pmatch, 0);  
+  while (error == 0) {
+    // Here's a match.  Determine the replacement.
+    string repl;
+
+    size_t p = 0;
+    while (p < _string2.length()) {
+      if (_string2[p] == '\\') {
+	p++;
+	if (p < _string2.length()) {
+	  if (isdigit(_string2[p])) {
+	    // Here's a subexpression reference.
+	    const char *numstr = _string2.c_str() + p;
+	    char *numend;
+	    int ref = strtol(numstr, &numend, 10);
+	    p += (numend - numstr);
+	    if (ref <= 0 || ref >= (int)nmatch) {
+	      cerr << "Invalid subexpression number: " << ref << "\n";
+	    } else {
+	      repl += string(str + pmatch[ref].rm_so,
+			     pmatch[ref].rm_eo - pmatch[ref].rm_so);
+	    }
+	  } else {
+	    // Here's an escaped character.
+	    repl += _string2[p];
+	    p++;
+	  }
+	}
+      } else {
+	// Here's a normal character.
+	repl += _string2[p];
+	p++;
+      }
+    }
+
+    // Store the result so far.
+    result += string(str, pmatch[0].rm_so);
+    result += repl;
+    str += pmatch[0].rm_eo;
+
+    if ((_flags & F_g) == 0) {
+      // If we don't have the global flag set, stop after the first iteration.
+      result += str;
+      context._pattern_space = result;
+      return;
+    }
+
+    error = regexec(&_re, str, nmatch, pmatch, 0);  
+  }
+
+  // All done.
+  result += str;
+  context._pattern_space = result;
+}

+ 57 - 0
ppremake/sedCommand.h

@@ -0,0 +1,57 @@
+// Filename: sedCommand.h
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SEDCOMMAND_H
+#define SEDCOMMAND_H
+
+#include "ppremake.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+class SedScript;
+class SedContext;
+class SedAddress;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : SedCommand
+// Description : This represents a single command (of several
+//               possible, separated by semicolons) to a SedProgram.
+////////////////////////////////////////////////////////////////////
+class SedCommand {
+public:
+  SedCommand();
+  ~SedCommand();
+
+  bool parse_command(const string &line, size_t &p);
+
+  void run(SedScript &script, SedContext &context);
+
+private:
+  bool parse_s_params(const string &line, size_t &p);
+  void do_command(SedScript &script, SedContext &context);
+  void do_s_command(SedContext &context);
+
+  SedAddress *_addr1;
+  SedAddress *_addr2;
+  char _command;
+
+  string _text;
+
+  regex_t _re;
+  string _string1;
+  string _string2;
+
+  enum Flags {
+    F_have_re  = 0x001,
+    F_g        = 0x002,
+  };
+
+  int _flags;
+
+  bool _active;
+};
+
+#endif

+ 19 - 0
ppremake/sedContext.cxx

@@ -0,0 +1,19 @@
+// Filename: sedContext.cxx
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "sedContext.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedContext::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedContext::
+SedContext(ostream &out) : _out(out) {
+  _line_number = 0;
+  _is_last_line = false;
+  _deleted = false;
+}

+ 31 - 0
ppremake/sedContext.h

@@ -0,0 +1,31 @@
+// Filename: sedContext.h
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SEDCONTEXT_H
+#define SEDCONTEXT_H
+
+#include "ppremake.h"
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : SedContext
+// Description : This contains the current context of the sed process
+//               as it is running: the pattern space, the hold space,
+//               and the current line numbers, etc.  It is updated as
+//               each line is read in and as each command is executed.
+////////////////////////////////////////////////////////////////////
+class SedContext {
+public:
+  SedContext(ostream &out);
+
+  int _line_number;
+  bool _is_last_line;
+  string _pattern_space;
+  string _hold_space;
+  bool _deleted;
+
+  ostream &_out;
+};
+
+#endif

+ 70 - 0
ppremake/sedProcess.cxx

@@ -0,0 +1,70 @@
+// Filename: sedProcess.cxx
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "sedProcess.h"
+#include "sedContext.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedProcess::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedProcess::
+SedProcess() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedProcess::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedProcess::
+~SedProcess() {
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedProcess::add_script_line
+//       Access: Public
+//  Description: Appends the indicated line to the end of the script
+//               that will be executed for each line of the input
+//               stream.  This may be called as many times as you
+//               like.
+//
+//               The return value is true if the line was added
+//               successfully, or false if there was an error in the
+//               line (in which case, some commands on the line might
+//               have been added, and others not).
+////////////////////////////////////////////////////////////////////
+bool SedProcess::
+add_script_line(const string &line) {
+  return _script.add_line(line);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedProcess::run
+//       Access: Public
+//  Description: Reads the input stream and executes the script once
+//               for each line on the input stream.  Output is written
+//               to the indicated output stream.
+////////////////////////////////////////////////////////////////////
+void SedProcess::
+run(istream &in, ostream &out) {
+  SedContext context(out);
+
+  string line;
+  getline(in, line);
+  while (!in.fail() && !in.eof()) {
+    context._pattern_space = line;
+    context._line_number++;
+    getline(in, line);
+
+    if (in.eof()) {
+      context._is_last_line = true;
+    }
+    _script.run(context);
+  }
+}

+ 30 - 0
ppremake/sedProcess.h

@@ -0,0 +1,30 @@
+// Filename: sedProcess.h
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SEDPROCESS_H
+#define SEDPROCESS_H
+
+#include "ppremake.h"
+#include "sedScript.h"
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : SedProcess
+// Description : This supervises the whole sed process, from beginning
+//               to end.
+////////////////////////////////////////////////////////////////////
+class SedProcess {
+public:
+  SedProcess();
+  ~SedProcess();
+
+  bool add_script_line(const string &line);
+
+  void run(istream &in, ostream &out);
+
+private:
+  SedScript _script;
+};
+
+#endif

+ 97 - 0
ppremake/sedScript.cxx

@@ -0,0 +1,97 @@
+// Filename: sedScript.cxx
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "sedScript.h"
+#include "sedCommand.h"
+#include "sedContext.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedScript::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedScript::
+SedScript() {
+  _quit = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedScript::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SedScript::
+~SedScript() {
+  Commands::iterator ci;
+  for (ci = _commands.begin(); ci != _commands.end(); ++ci) {
+    delete (*ci);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedScript::add_line
+//       Access: Public
+//  Description: Adds the indicated script line to the script.
+//               Returns true if it is a valid line, false if there is
+//               an error.
+////////////////////////////////////////////////////////////////////
+bool SedScript::
+add_line(const string &line) {
+  size_t p = 0;
+  SedCommand *command = new SedCommand;
+  if (!command->parse_command(line, p)) {
+    // That's an invalid command.
+    delete command;
+    return false;
+  }
+  _commands.push_back(command);
+
+  while (p < line.length()) {
+    // There's more to the line.
+    if (line[p] != ';') {
+      // But it's an error.
+      cerr << "Invalid character at: " << line.substr(p) << "\n";
+      return false;
+    }
+    p++;
+
+    command = new SedCommand;
+    if (!command->parse_command(line, p)) {
+      // That's an invalid command.
+      delete command;
+      return false;
+    }
+    _commands.push_back(command);
+  }
+
+  // Everything parsed ok.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SedScript::run
+//       Access: Public
+//  Description: Runs the script, modifying the context as
+//               appropriate.  Returns true if the process should
+//               continue with the next line, or false if we have quit
+//               and we should terminate.
+////////////////////////////////////////////////////////////////////
+bool SedScript::
+run(SedContext &context) {
+  context._deleted = false;
+  _next_command = _commands.begin();
+  while (!_quit && _next_command != _commands.end()) {
+    SedCommand *command = (*_next_command);
+    ++_next_command;
+    command->run(*this, context);
+  }
+
+  if (!context._deleted) {
+    context._out << context._pattern_space << "\n";
+  }
+
+  return !_quit;
+}

+ 39 - 0
ppremake/sedScript.h

@@ -0,0 +1,39 @@
+// Filename: sedScript.h
+// Created by:  drose (24Oct00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SEDSCRIPT_H
+#define SEDSCRIPT_H
+
+#include "ppremake.h"
+
+#include <vector>
+
+class SedCommand;
+class SedContext;
+
+///////////////////////////////////////////////////////////////////
+// 	 Class : SedScript
+// Description : This is a complete sed script: a linear list of
+//               commands that are to be applied for each line read
+//               from input.
+////////////////////////////////////////////////////////////////////
+class SedScript {
+public:
+  SedScript();
+  ~SedScript();
+
+  bool add_line(const string &line);
+
+  bool run(SedContext &context);
+
+public:
+  bool _quit;
+
+  typedef vector<SedCommand *> Commands;
+  Commands _commands;
+  Commands::const_iterator _next_command;
+};
+
+#endif