diff --git a/Makefile.in b/Makefile.in
index 620cd35..10b2b1f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -20,7 +20,7 @@ objs = buffer.o carg_parser.o global.o io.o main.o main_loop.o regex.o signal.o
 all : $(progname) r$(progname)
 
 $(progname) : $(objs)
-	$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(objs)
+	$(CC) $(CFLAGS) -o $@ $(objs) $(LDFLAGS)
 
 r$(progname) : r$(progname).in
 	cat $(VPATH)/r$(progname).in > $@
@@ -53,7 +53,7 @@ Makefile : $(VPATH)/configure $(VPATH)/Makefile.in
 	./config.status
 
 check : all
-	@$(VPATH)/testsuite/check.sh $(VPATH)/testsuite $(pkgversion)
+	@$(VPATH)/testsuite/check.sh $(VPATH)/testsuite $(pkgversion) $(test_pcre2)
 
 install : install-bin install-info install-man
 install-strip : install-bin-strip install-info install-man
diff --git a/configure b/configure
index 223c9dc..539be32 100755
--- a/configure
+++ b/configure
@@ -25,6 +25,7 @@ CC=gcc
 CPPFLAGS=
 CFLAGS='-Wall -W -O2'
 LDFLAGS=
+test_pcre2=yes
 
 # checking whether we are using GNU C.
 /bin/sh -c "${CC} --version" > /dev/null 2>&1 || { CC=cc ; CFLAGS=-O2 ; }
@@ -66,6 +67,7 @@ while [ $# != 0 ] ; do
 		echo "  --infodir=DIR         info files directory [${infodir}]"
 		echo "  --mandir=DIR          man pages directory [${mandir}]"
 		echo "  --program-prefix=NAME install program and documentation prefixed with NAME"
+                echo "  --disable-pcre2       Do not compile in PCRE2 support even if detected."
 		echo "  CC=COMPILER           C compiler to use [${CC}]"
 		echo "  CPPFLAGS=OPTIONS      command line options for the preprocessor [${CPPFLAGS}]"
 		echo "  CFLAGS=OPTIONS        command line options for the C compiler [${CFLAGS}]"
@@ -94,6 +96,7 @@ while [ $# != 0 ] ; do
 	--mandir=*)            mandir=${optarg} ;;
 	--program-prefix=*) program_prefix=${optarg} ;;
 	--no-create)              no_create=yes ;;
+        --disable-pcre2)       test_pcre2=no ;;
 
 	CC=*)              CC=${optarg} ;;
 	CPPFLAGS=*)  CPPFLAGS=${optarg} ;;
@@ -119,6 +122,17 @@ while [ $# != 0 ] ; do
 	fi
 done
 
+# Look for PCRE2
+if [ "$test_pcre2" = yes ]; then
+    if command -v pcre2-config >/dev/null; then
+        CPPFLAGS="$CPPFLAGS -DHAVE_PCRE2 $(pcre2-config --cflags)"
+        LDFLAGS="$LDFLAGS $(pcre2-config --libs8)"
+    else
+        echo "pcre2-config not found."
+        pcre2_test=no
+    fi
+fi
+
 # Find the source files, if location was not specified.
 srcdirtext=
 if [ -z "${srcdir}" ] ; then
@@ -193,6 +207,7 @@ CC = ${CC}
 CPPFLAGS = ${CPPFLAGS}
 CFLAGS = ${CFLAGS}
 LDFLAGS = ${LDFLAGS}
+test_pcre2 = ${test_pcre2}
 EOF
 cat "${srcdir}/Makefile.in" >> Makefile
 
diff --git a/doc/ed.texi b/doc/ed.texi
index 7753c7b..c90d219 100644
--- a/doc/ed.texi
+++ b/doc/ed.texi
@@ -387,6 +387,10 @@ This version number should be included in all bug reports.
 Use extended regular expressions instead of the basic regular expressions
 mandated by POSIX.
 
+@item -P
+@itemx --perl-regexp
+Use Perl-compatible regular expressions, if built with support for PCRE2.
+
 @item -G
 @itemx --traditional
 Forces backwards compatibility. This affects the behavior of the
@@ -674,6 +678,11 @@ maximal string of alphanumeric characters, including the underscore (_).
 @itemx \'
 Unconditionally matches the beginning @samp{\`} or ending @samp{\'} of a line.
 
+@item \|
+
+Alternation; @samp{a\|b} matches either the regular expression
+@samp{a} or @samp{b}.
+
 @item \?
 Optionally matches the single character regular expression or
 subexpression immediately preceding it. For example, the regular
@@ -704,6 +713,9 @@ Matches any character that is not a word-constituent.
 
 @end table
 
+The @samp{-E} option instead uses POSIX Extended Regular Expression
+syntax, and, if supported, the @samp{-P} option uses Perl-compatible
+regular expressions.
 
 @node Commands
 @chapter Commands
diff --git a/ed.h b/ed.h
index e1d52b4..f71569b 100644
--- a/ed.h
+++ b/ed.h
@@ -110,6 +110,8 @@ void unmark_unterminated_line( const line_t * const lp );
 
 /* defined in main.c */
 bool extended_regexp( void );
+bool perl_regexp( void );
+bool utf8_text( void );
 bool is_regular_file( const int fd );
 bool may_access_filename( const char * const name );
 bool restricted( void );
diff --git a/main.c b/main.c
index c5b459f..6af7b9f 100644
--- a/main.c
+++ b/main.c
@@ -37,6 +37,7 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <locale.h>
+#include <wchar.h>
 
 #include "carg_parser.h"
 #include "ed.h"
@@ -47,6 +48,8 @@ static const char * const program_year = "2021";
 static const char * invocation_name = "ed";		/* default value */
 
 static bool extended_regexp_ = false;	/* if set, use EREs */
+static bool perl_regexp_ = false;	/* if set, use PCREs */
+static bool utf8_text_ = false;         /* If set, files and input is UTF-8 */
 static bool restricted_ = false;	/* if set, run in restricted mode */
 static bool scripted_ = false;		/* if set, suppress diagnostics,
 					   byte counts and '!' prompt */
@@ -55,11 +58,21 @@ static bool traditional_ = false;	/* if set, be backwards compatible */
 
 /* Access functions for command line flags. */
 bool extended_regexp( void ) { return extended_regexp_; }
+bool perl_regexp( void ) { return perl_regexp_; }
+bool utf8_text( void ) { return utf8_text_; }
 bool restricted( void ) { return restricted_; }
 bool scripted( void ) { return scripted_; }
 bool strip_cr( void ) { return strip_cr_; }
 bool traditional( void ) { return traditional_; }
 
+/* Taken from gnulib's localeinfo.c */
+/* Return true if the locale uses UTF-8.  */
+static bool is_using_utf8( void )
+  {
+  wchar_t wc;
+  mbstate_t mbs = {0};
+  return mbrtowc( &wc, "\xc4\x80", 2, &mbs ) == 2 && wc == 0x100;
+  }
 
 static void show_help( void )
   {
@@ -75,6 +88,7 @@ static void show_help( void )
           "  -h, --help                 display this help and exit\n"
           "  -V, --version              output version information and exit\n"
           "  -E, --extended-regexp      use extended regular expressions\n"
+          "  -P, --perl-regexp          use perl-style regular expressions\n"
           "  -G, --traditional          run in compatibility mode\n"
           "  -l, --loose-exit-status    exit with 0 status even if a command fails\n"
           "  -p, --prompt=STRING        use STRING as an interactive prompt\n"
@@ -146,7 +160,6 @@ bool may_access_filename( const char * const name )
   return true;
   }
 
-
 int main( const int argc, const char * const argv[] )
   {
   int argind;
@@ -155,6 +168,7 @@ int main( const int argc, const char * const argv[] )
   const struct ap_Option options[] =
     {
     { 'E', "extended-regexp",      ap_no  },
+    { 'P', "perl-regexp",          ap_no  },
     { 'G', "traditional",          ap_no  },
     { 'h', "help",                 ap_no  },
     { 'l', "loose-exit-status",    ap_no  },
@@ -183,6 +197,7 @@ int main( const int argc, const char * const argv[] )
     switch( code )
       {
       case 'E': extended_regexp_ = true; break;
+      case 'P': perl_regexp_ = true; break;
       case 'G': traditional_ = true; break;	/* backward compatibility */
       case 'h': show_help(); return 0;
       case 'l': loose = true; break;
@@ -197,7 +212,31 @@ int main( const int argc, const char * const argv[] )
       }
     } /* end process options */
 
+#ifndef HAVE_PCRE2
+  if( perl_regexp_ )
+    {
+    show_error( "-P is not supported.", 0, false );
+    return 3;
+    }
+#endif
+
+  if( extended_regexp_ && perl_regexp_ )
+    {
+    show_error( "cannot use both extended and perl regular expressions.",
+                0, false );
+    return 3;
+    }
+
   setlocale( LC_ALL, "" );
+
+  if( is_using_utf8() ) utf8_text_ = true;
+
+  if( perl_regexp_ && MB_CUR_MAX > 1 && !utf8_text_ )
+    {
+    show_error( "-P supports only unibyte and UTF-8 locales.", 0, false );
+    return 3;
+    }
+
   if( !init_buffers() ) return 1;
 
   while( argind < ap_arguments( &parser ) )
diff --git a/regex.c b/regex.c
index 3f88966..9b11131 100644
--- a/regex.c
+++ b/regex.c
@@ -24,12 +24,31 @@
 #include <stdlib.h>
 #include <string.h>
 
+#ifdef HAVE_PCRE2
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+union re_type {
+  regex_t posix_re;
+  struct {
+    pcre2_code *re;
+    pcre2_match_data *md;
+  } pcre;
+};
+
+pcre2_compile_context *ccontext_ = 0;
+
+#else
+union re_type {
+  regex_t posix_re;
+};
+#endif
+
 #include "ed.h"
 
 
 static const char * const inv_pat_del = "Invalid pattern delimiter";
 static const char * const no_match = "No match";
-static regex_t * subst_regex_ = 0;	/* regex of previous substitution */
+static union re_type * subst_regex_ = 0;	/* regex of previous substitution */
 
 static char * rbuf = 0;		/* replacement buffer */
 static int rbufsz = 0;		/* replacement buffer size */
@@ -106,11 +125,11 @@ static char * extract_pattern( const char ** const ibufpp, const char delimiter
 
 /* return pointer to compiled regex from command buffer, or to previous
    compiled regex if empty RE. return 0 if error */
-static regex_t * get_compiled_regex( const char ** const ibufpp,
+static union re_type * get_compiled_regex( const char ** const ibufpp,
                                      const bool test_delimiter )
   {
-  static regex_t store[2];		/* space for two compiled regexes */
-  static regex_t * exp = 0;
+  static union re_type store[2];   /* space for two compiled regexes */
+  static union re_type * exp = 0;
   const char * pat;
   const char delimiter = **ibufpp;
   int n;
@@ -126,14 +145,54 @@ static regex_t * get_compiled_regex( const char ** const ibufpp,
   if( !pat ) return 0;
   if( test_delimiter && delimiter != **ibufpp )
     { set_error_msg( "Missing pattern delimiter" ); return 0; }
+
+#ifdef HAVE_PCRE2
+  if( perl_regexp() )
+    {
+    int errornumber;
+    PCRE2_SIZE erroroffset;
+
+    if( !ccontext_ && !utf8_text() )
+      {
+      /* Compile PCRE2 character tables for the current locale if not
+       * in UTF-8 mode */
+      const unsigned char *tables = pcre2_maketables(0);
+      ccontext_ = pcre2_compile_context_create(0);
+      pcre2_set_character_tables(ccontext_, tables);
+      }
+
+    /* exp compiled && not copied */
+    if( exp && exp != subst_regex_ )
+      {
+      pcre2_code_free( exp->pcre.re );
+      pcre2_match_data_free( exp->pcre.md );
+      exp->pcre.re = 0; exp->pcre.md = 0;
+      }
+    else exp = ( &store[0] != subst_regex_ ) ? &store[0] : &store[1];
+
+    exp->pcre.re = pcre2_compile( (PCRE2_SPTR)pat, PCRE2_ZERO_TERMINATED,
+                                  utf8_text() ? PCRE2_UTF : 0, &errornumber,
+                                  &erroroffset, ccontext_ );
+    if( !exp->pcre.re )
+      {
+      PCRE2_UCHAR buf[256];
+      pcre2_get_error_message(errornumber, buf, sizeof buf);
+      set_error_msg( (const char *)buf );
+      exp = 0;
+      }
+    else exp->pcre.md = pcre2_match_data_create_from_pattern( exp->pcre.re, 0 );
+    return exp;
+    }
+#endif
+
   /* exp compiled && not copied */
-  if( exp && exp != subst_regex_ ) regfree( exp );
+  if( exp && exp != subst_regex_ ) regfree( &(exp->posix_re) );
   else exp = ( &store[0] != subst_regex_ ) ? &store[0] : &store[1];
-  n = regcomp( exp, pat, extended_regexp() ? REG_EXTENDED : 0 );
+  n = regcomp( &(exp->posix_re), pat, extended_regexp() ? REG_EXTENDED : 0 );
   if( n )
     {
     char buf[80];
-    regerror( n, exp, buf, sizeof buf );
+    regerror( n, &(exp->posix_re), buf, sizeof buf );
     set_error_msg( buf );
     exp = 0;
     }
@@ -143,13 +202,24 @@ static regex_t * get_compiled_regex( const char ** const ibufpp,
 
 bool set_subst_regex( const char ** const ibufpp )
   {
-  regex_t * exp;
+  union re_type * exp;
 
   disable_interrupts();
   exp = get_compiled_regex( ibufpp, true );
   if( exp && exp != subst_regex_ )
     {
-    if( subst_regex_ ) regfree( subst_regex_ );
+    if( subst_regex_ )
+      {
+        if ( perl_regexp() )
+          {
+#ifdef HAVE_PCRE2
+          pcre2_code_free( subst_regex_->pcre.re );
+          pcre2_match_data_free( subst_regex_->pcre.md );
+          subst_regex_->pcre.re = 0; subst_regex_->pcre.md = 0;
+#endif
+          }
+          else regfree( &(subst_regex_->posix_re) );
+      }
     subst_regex_ = exp;
     }
   enable_interrupts();
@@ -161,7 +231,7 @@ bool set_subst_regex( const char ** const ibufpp )
 bool build_active_list( const char ** const ibufpp, const int first_addr,
                         const int second_addr, const bool match )
   {
-  const regex_t * exp;
+  const union re_type * exp;
   const line_t * lp;
   int addr;
   const char delimiter = **ibufpp;
@@ -178,7 +248,16 @@ bool build_active_list( const char ** const ibufpp, const int first_addr,
     char * const s = get_sbuf_line( lp );
     if( !s ) return false;
     if( isbinary() ) nul_to_newline( s, lp->len );
-    if( match == !regexec( exp, s, 0, 0, 0 ) && !set_active_node( lp ) )
+    if( perl_regexp() )
+      {
+#ifdef HAVE_PCRE2
+        if( match == (pcre2_match( exp->pcre.re, (PCRE2_SPTR)s,
+                                    PCRE2_ZERO_TERMINATED, 0, 0, exp->pcre.md,
+                                   0 ) > 0) && !set_active_node( lp ) )
+          return false;
+#endif
+      }
+    else if( match == !regexec( &(exp->posix_re), s, 0, 0, 0 ) && !set_active_node( lp ) )
       return false;
     }
   return true;
@@ -189,7 +268,7 @@ bool build_active_list( const char ** const ibufpp, const int first_addr,
    given direction. wrap around begin/end of editor buffer if necessary */
 int next_matching_node_addr( const char ** const ibufpp, const bool forward )
   {
-  const regex_t * const exp = get_compiled_regex( ibufpp, false );
+  const union re_type * const exp = get_compiled_regex( ibufpp, false );
   int addr = current_addr();
 
   if( !exp ) return -1;
@@ -201,7 +280,14 @@ int next_matching_node_addr( const char ** const ibufpp, const bool forward )
       char * const s = get_sbuf_line( lp );
       if( !s ) return -1;
       if( isbinary() ) nul_to_newline( s, lp->len );
-      if( !regexec( exp, s, 0, 0, 0 ) ) return addr;
+      if( perl_regexp() )
+        {
+#ifdef HAVE_PCRE2
+          if( pcre2_match( exp->pcre.re, (PCRE2_SPTR)s, PCRE2_ZERO_TERMINATED,
+                           0, 0, exp->pcre.md, 0) > 0 ) return addr;
+#endif
+        }
+      else if( !regexec( &(exp->posix_re), s, 0, 0, 0 ) ) return addr;
       }
     }
   while( addr != current_addr() );
@@ -255,6 +341,108 @@ bool extract_replacement( const char ** const ibufpp, const bool isglobal )
   }
 
 
+#ifdef HAVE_PCRE2
+/* Produce replacement text from matched text and replacement template.
+   Return new offset to end of replacement text, or -1 if error. */
+static int replace_matched_text_pcre2( char ** txtbufp, int * const txtbufszp,
+                                 const char * const txt,
+                                 pcre2_match_data *md, int offset,
+                                 const int re_nsub )
+  {
+  int i;
+  PCRE2_SIZE *rm;
+
+  rm = pcre2_get_ovector_pointer(md);
+
+  for( i = 0 ; i < rlen; ++i )
+    {
+    int n;
+    if( rbuf[i] == '&' )
+      {
+      int j = rm[0]; int k = rm[1];
+      if( !resize_buffer( txtbufp, txtbufszp, offset + k - j ) ) return -1;
+      while( j < k ) (*txtbufp)[offset++] = txt[j++];
+      }
+    else if( rbuf[i] == '\\' && rbuf[++i] >= '1' && rbuf[i] <= '9' &&
+             ( n = rbuf[i] - '0' ) <= re_nsub )
+      {
+      int j = rm[n * 2]; int k = rm[(n * 2) + 1];
+      if( !resize_buffer( txtbufp, txtbufszp, offset + k - j ) ) return -1;
+      while( j < k ) (*txtbufp)[offset++] = txt[j++];
+      }
+    else		/* preceding 'if' skipped escaping backslashes */
+      {
+      if( !resize_buffer( txtbufp, txtbufszp, offset + 1 ) ) return -1;
+      (*txtbufp)[offset++] = rbuf[i];
+      }
+    }
+  if( !resize_buffer( txtbufp, txtbufszp, offset + 1 ) ) return -1;
+  (*txtbufp)[offset] = 0;
+  return offset;
+  }
+
+
+/* Produce new text with one or all matches replaced in a line, PCRE2
+   version.  Return size of the new line text, 0 if no change, -1 if
+   error */
+static int line_replace_pcre2( char ** txtbufp, int * const txtbufszp,
+                         const line_t * const lp, const int snum )
+  {
+  int nsub = 0;
+  char * txt = get_sbuf_line( lp );
+  const char * eot;
+  int i = 0, offset = 0;
+  const bool global = ( snum <= 0 );
+  bool changed = false;
+
+  if( !txt ) return -1;
+  if( isbinary() ) nul_to_newline( txt, lp->len );
+  eot = txt + lp->len;
+  if( (nsub = pcre2_match( subst_regex_->pcre.re, (PCRE2_SPTR)txt,
+                           PCRE2_ZERO_TERMINATED, 0, 0,
+                           subst_regex_->pcre.md, 0 ) ) > 0 )
+    {
+    int matchno = 0;
+    bool infloop = false;
+    do {
+      PCRE2_SIZE *rm = pcre2_get_ovector_pointer( subst_regex_->pcre.md );
+      if( global || snum == ++matchno )
+        {
+        changed = true; i = rm[0];
+        if( !resize_buffer( txtbufp, txtbufszp, offset + i ) ) return -1;
+        if( isbinary() ) newline_to_nul( txt, rm[1] );
+        memcpy( *txtbufp + offset, txt, i ); offset += i;
+        offset = replace_matched_text_pcre2( txtbufp, txtbufszp, txt,
+                                             subst_regex_->pcre.md, offset,
+                                             nsub );
+        if( offset < 0 ) return -1;
+        }
+      else
+        {
+        i = rm[1];
+        if( !resize_buffer( txtbufp, txtbufszp, offset + i ) ) return -1;
+        if( isbinary() ) newline_to_nul( txt, i );
+        memcpy( *txtbufp + offset, txt, i ); offset += i;
+        }
+      txt += rm[1];
+      if( global && rm[1] == 0 )
+        { if( !infloop ) infloop = true;	/* 's/^/#/g' is valid */
+          else { set_error_msg( "Infinite substitution loop" ); return -1; } }
+      }
+    while( *txt && ( !changed || global ) &&
+           (nsub = pcre2_match( subst_regex_->pcre.re, (PCRE2_SPTR)txt,
+                                PCRE2_ZERO_TERMINATED, 0, PCRE2_NOTBOL,
+                                subst_regex_->pcre.md, 0 ) ) > 0 );
+    i = eot - txt;
+    if( !resize_buffer( txtbufp, txtbufszp, offset + i + 2 ) ) return -1;
+    if( isbinary() ) newline_to_nul( txt, i );
+    memcpy( *txtbufp + offset, txt, i );		/* tail copy */
+    memcpy( *txtbufp + offset + i, "\n", 2 );
+    }
+  return ( changed ? offset + i + 1 : 0 );
+  }
+#endif
+
 /* Produce replacement text from matched text and replacement template.
    Return new offset to end of replacement text, or -1 if error. */
 static int replace_matched_text( char ** txtbufp, int * const txtbufszp,
@@ -305,10 +493,14 @@ static int line_replace( char ** txtbufp, int * const txtbufszp,
   const bool global = ( snum <= 0 );
   bool changed = false;
 
+#ifdef HAVE_PCRE2
+  if( perl_regexp() ) return line_replace_pcre2( txtbufp, txtbufszp, lp, snum );
+#endif
+
   if( !txt ) return -1;
   if( isbinary() ) nul_to_newline( txt, lp->len );
   eot = txt + lp->len;
-  if( !regexec( subst_regex_, txt, se_max, rm, 0 ) )
+  if( !regexec( &(subst_regex_->posix_re), txt, se_max, rm, 0 ) )
     {
     int matchno = 0;
     bool infloop = false;
@@ -320,7 +512,7 @@ static int line_replace( char ** txtbufp, int * const txtbufszp,
         if( isbinary() ) newline_to_nul( txt, rm[0].rm_eo );
         memcpy( *txtbufp + offset, txt, i ); offset += i;
         offset = replace_matched_text( txtbufp, txtbufszp, txt, rm, offset,
-                                       subst_regex_->re_nsub );
+                                       subst_regex_->posix_re.re_nsub );
         if( offset < 0 ) return -1;
         }
       else
@@ -336,7 +528,7 @@ static int line_replace( char ** txtbufp, int * const txtbufszp,
           else { set_error_msg( "Infinite substitution loop" ); return -1; } }
       }
     while( *txt && ( !changed || global ) &&
-           !regexec( subst_regex_, txt, se_max, rm, REG_NOTBOL ) );
+           !regexec( &(subst_regex_->posix_re), txt, se_max, rm, REG_NOTBOL ) );
     i = eot - txt;
     if( !resize_buffer( txtbufp, txtbufszp, offset + i + 2 ) ) return -1;
     if( isbinary() ) newline_to_nul( txt, i );
diff --git a/testsuite/check.sh b/testsuite/check.sh
index ac00006..8374070 100755
--- a/testsuite/check.sh
+++ b/testsuite/check.sh
@@ -89,6 +89,74 @@ for i in "${testdir}"/*.ed ; do
 	rm -f out.o out.log
 done
 
+echo "Testing ed -E regular expressions"
+
+# Run the .err scripts through ed -E with a regular file connected to standard
+# input, since these don't generate output; they exit with non-zero status.
+for i in "${testdir}"/*.err ; do
+	if "${ED}" -E -s test.txt < "$i" > /dev/null 2>&1 ; then
+		echo "*** The script $i exited abnormally ***"
+		fail=127
+	fi
+done
+
+# Run the .edE scripts with ed -E and compare their output against the .r files,
+# which contain the correct output.
+# The .edE scripts should exit with zero status.
+for i in "${testdir}"/*.edE ; do
+	base=`echo "$i" | sed 's,^.*/,,;s,\.edE$,,'`	# remove dir and ext
+	if "${ED}" -E -s test.txt < "$i" > /dev/null 2> out.log ; then
+		if cmp -s out.o "${testdir}"/${base}.r ; then
+			true
+		else
+			mv -f out.o ${base}.o
+			echo "*** Output ${base}.o of script $i is incorrect ***"
+			fail=127
+		fi
+	else
+		mv -f out.log ${base}.log
+		echo "*** The script $i exited abnormally ***"
+		fail=127
+	fi
+	rm -f out.o out.log
+done
+
+if [ "$3" = yes ]; then
+    echo "Testing ed -P regular expressions"
+
+
+    # Run the .err scripts through ed -P with a regular file
+    # connected to standard input, since these don't generate output;
+    # they exit with non-zero status.
+    for i in "${testdir}"/*.err ; do
+	if "${ED}" -P -s test.txt < "$i" > /dev/null 2>&1 ; then
+	    echo "*** The script $i exited abnormally ***"
+	    fail=127
+	fi
+    done
+
+    # Run the .edE scripts with ed -P and compare their output against
+    # the .r files, which contain the correct output.  The .edE
+    # scripts should exit with zero status.
+    for i in "${testdir}"/*.edE ; do
+	base=`echo "$i" | sed 's,^.*/,,;s,\.edE$,,'`	# remove dir and ext
+	if "${ED}" -P -s test.txt < "$i" > /dev/null 2> out.log ; then
+	    if cmp -s out.o "${testdir}"/${base}.r ; then
+		true
+	    else
+		mv -f out.o ${base}.o
+		echo "*** Output ${base}.o of script $i is incorrect ***"
+		fail=127
+	    fi
+	else
+	    mv -f out.log ${base}.log
+	    echo "*** The script $i exited abnormally ***"
+	    fail=127
+	fi
+	rm -f out.o out.log
+    done
+fi
+
 rm -f test.txt test.bin zero
 
 if [ ${fail} = 0 ] ; then
diff --git a/testsuite/e4.edE b/testsuite/e4.edE
new file mode 100644
index 0000000..5ce96b7
--- /dev/null
+++ b/testsuite/e4.edE
@@ -0,0 +1,5 @@
+H
+e test.bin
+# modifying the last line of a binary file adds a newline
+$s/x/x/
+w out.o
diff --git a/testsuite/e5.edE b/testsuite/e5.edE
new file mode 100644
index 0000000..5c548be
--- /dev/null
+++ b/testsuite/e5.edE
@@ -0,0 +1,7 @@
+H
+e test.bin
+# modifying the last line of a binary file adds a newline
+$s/x/x/
+# but undo restores the line to its previous state
+u
+w out.o
diff --git a/testsuite/g.edE b/testsuite/g.edE
new file mode 100644
index 0000000..0781e61
--- /dev/null
+++ b/testsuite/g.edE
@@ -0,0 +1,44 @@
+H
+g/./m0
+g//a\
+hello world
+# lines beginning with a `#' should be ignored
+g/hello /# even in global commands \
+s/lo/p!/\
+a\
+order\
+.\
+# and in the command list \
+i\
+caos\
+.\
+-1s/l/L
+u
+u
+17,33g/[A-I]/-1d\
++1c\
+hello world\
+.\
+47
+;d
+# don't change current address if no match
+g/xxx/1d
+;j
+g/heLp! world//caos/d\
+-;/order/;d
+# split lines
+g/./s/hello world/hello\
+world/
+# use replacement from last substitution
+g/./s/animated/%/
+g/./s/difficulty/%
+# to be undone
+g/./s//x/g
+u
+w out.o
+a
+hello
+.
+g/hello/Q
+# not reached
+w out.o
diff --git a/testsuite/m_addr.edE b/testsuite/m_addr.edE
new file mode 100644
index 0000000..28c77e5
--- /dev/null
+++ b/testsuite/m_addr.edE
@@ -0,0 +1,20 @@
+H
+3 ---- 2,1 2 3m;,,;;
+-2,.m7
+. ++,+++m.
+-1,m.
+.-4m-2
+-2m-1
+1,2;3,4,$;5,6,7
+;.m0
+u
+u
++;m7
+-m-6
++1m+4
+-3m?all?
+/their/-m-2
+# to be undone
+,1m$
+u
+,w out.o
diff --git a/testsuite/s.edE b/testsuite/s.edE
new file mode 100644
index 0000000..5e4aa37
--- /dev/null
+++ b/testsuite/s.edE
@@ -0,0 +1,64 @@
+H
+s/fam//
+s/il/%/
+s/ies/off\&%1spr\ing/
+1s/of/01/2
++10s/and/02/g
+# set current address to last modified line (6)
+,s/way/03
+u
+u
+s,man,04,1p
+10szallzf&z1
+-3s!no!%&%!
+9s'it'&05&'gp
+12s|of|%|p1
+s/([^ ][^ ]*)/(\1)/pg
+2s
+/4/sp
+/(No)/sr
+/(.)/sgpr
+,s&$&$&
+5s//%/lg
+,s%^%^%
+5i
+hello/[]world
+.
+s/[/]/ /
+s/[[:digit:][]/ /
+s/[]]/ /
+7s=(\(03\)) =\1\
+=
+-1s($($(
+2s/a/1/l
+s/a/2/n
+s/a/3/p
+s/a/4/ln
+s/e/5/lp
+s/e/6/np
+s/e/7/lnp
+s/i/8
+s/u/%/
+s/u/%
+3s/ /_/lnp3
+s0e0103
+4sg
+6s5
+9sp
+10s
+11sg
+12sg
+,s/ \(nature\)/$\
+(nature)/
++12s/ \(for\)/$\
+(for)/
+6s/^/#/g
+6s/$/!\
+\
+/g
+7s,^,// line 7,g
+8s,^$,// line 8,g
+# to be undone
+,s/./x/g
+u
+w out.o
