Added silc_subst, regex matching and substitution with the
authorPekka Riikonen <priikone@silcnet.org>
Sun, 13 Jan 2008 15:24:26 +0000 (15:24 +0000)
committerPekka Riikonen <priikone@silcnet.org>
Sun, 13 Jan 2008 15:24:26 +0000 (15:24 +0000)
familiar Sed syntax.

lib/silcutil/silcerrno.c
lib/silcutil/silcerrno.h
lib/silcutil/silcregex.c
lib/silcutil/silcregex.h
lib/silcutil/tests/test_silcbuffmt.c
lib/silcutil/tests/test_silcregex.c

index ff809c5f87831e35296623988c77986bd627c72f..5f415e94aa3fccf3ceea959bd629a6328ca10410 100644 (file)
@@ -350,7 +350,7 @@ const char *silc_errno_strings[] =
   "Interrupted",
   "Not valid",
   "Limit reached",
-  "",
+  "Syntax error",
   "",
   "",
   "",
index f8e8f15f5649556ec79d9ac9b9bad3b787481b4d..ebf329285fba82bd89e4872e2dfbe93f1869882d 100644 (file)
@@ -94,6 +94,7 @@ typedef enum {
   SILC_ERR_INTERRUPTED                 = 23,  /* Interrupted */
   SILC_ERR_NOT_VALID                   = 24,  /* Not valid */
   SILC_ERR_LIMIT                       = 25,  /* Limit reached */
+  SILC_ERR_SYNTAX                      = 26,  /* Syntax error */
 
   /* File, directory and device errors */
   SILC_ERR_NO_SUCH_FILE                = 40,  /* No such file */
index f08c1b61d4247df100ff2688a2e0eb320899fa01..dba552b7e34b9fb976fcc9b893b6dec6d18a0267 100644 (file)
@@ -1193,7 +1193,7 @@ SilcResult silc_re_compile_pattern(unsigned char *regex, int size,
   int op;
   int current_level;
   int level;
-  int opcode;
+  int opcode = 0;
   int pattern_offset = 0, alloc;
   int starts[NUM_LEVELS * MAX_NESTING];
   int starts_base;
@@ -2444,8 +2444,10 @@ SilcBool silc_regex_va(const char *string, SilcUInt32 string_len,
 
   /* Return matches */
   for (i = 0; i < c; i++) {
-    if (m[i].start == -1)
+    if (m[i].start == -1) {
+      silc_buffer_set(rets[i], NULL, 0);
       continue;
+    }
     silc_buffer_set(rets[i], (unsigned char *)string + m[i].start,
                    m[i].end - m[i].start);
   }
@@ -2506,3 +2508,224 @@ SilcBool silc_regex_buffer(SilcBuffer buffer, const char *regex,
 
   return ret;
 }
+
+/***************************** Substitution API *****************************/
+
+/* Regexp to parse sed substitution command syntax */
+#define SILC_REGEXP_SUBST \
+  "^(/?.+/?[^!s]|[0-9]+|\\$)?(!?s)(/)(.*[^\\])?(/)(.*[^\\])?(/)(!?.+)?"
+
+/* Substitution context */
+typedef struct {
+  SilcInt32 addr_number;           /* Line number to match, -1 for last line */
+  SilcUInt32 line;                 /* Current line number */
+  char *str_regexp;                /* REGEXP to match */
+  SilcBufferRegexFlags match_flags; /* Match flags */
+  SilcBufferRegexFlags addr_flags;  /* ADDR flags */
+  SilcBuffer rep;                  /* REPLACEMENT */
+} SilcSubstContext;
+
+/* Function to check the ADDR match and do rest of the match and
+   substitution. */
+
+static int silc_subst_addr(SilcStack stack, SilcBuffer buffer, void *value,
+                          void *context)
+{
+  SilcSubstContext *ctx = context;
+
+  ctx->line++;
+
+  /* If NUMBER was set in ADDR, match for specific line number */
+  if (ctx->addr_number > 0 && ctx->addr_number != ctx->line &&
+      !(ctx->addr_flags & SILC_STR_REGEX_NOT))
+    return 0;
+  if (ctx->addr_number > 0 && ctx->addr_number == ctx->line &&
+      ctx->addr_flags & SILC_STR_REGEX_NOT)
+    return 0;
+
+  /* Check for last line if ADDR was '$' */
+  if (buffer->tail != buffer->end && ctx->addr_number == -1 &&
+      !(ctx->addr_flags & SILC_STR_REGEX_NOT))
+    return 0;
+  if (buffer->tail == buffer->end && ctx->addr_number == -1 &&
+      ctx->addr_flags & SILC_STR_REGEX_NOT)
+    return 0;
+
+  /* Match and replace */
+  return silc_buffer_format(buffer,
+                           SILC_STR_REGEX(ctx->str_regexp, ctx->match_flags),
+                             SILC_STR_REPLACE(silc_buffer_data(ctx->rep) ?
+                                              silc_buffer_data(ctx->rep) :
+                                              (unsigned char *)"",
+                                              silc_buffer_len(ctx->rep)),
+                           SILC_STR_END, SILC_STR_END);
+}
+
+/* Matching and substitution ala sed. */
+
+SilcBool silc_subst(SilcBuffer buffer, const char *subst)
+{
+  SilcSubstContext ctx;
+  SilcBufferStruct match, addr, command, exp_start, exp, exp_end;
+  SilcBufferStruct rep, rep_end, flags;
+  SilcBufferRegexFlags addr_flags = 0, match_flags = 0;
+  char *str_addr = "";
+  int ret = -1;
+
+  memset(&ctx, 0, sizeof(ctx));
+
+  if (!buffer || !subst) {
+    silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
+    goto out;
+  }
+
+  SILC_LOG_DEBUG(("Substitution '%s'", subst));
+
+  /* Parse the expression syntax */
+  if (!silc_regex(subst, SILC_REGEXP_SUBST, &match, &addr, &command,
+                 &exp_start, &exp, &exp_end, &rep, &rep_end, &flags, NULL)) {
+    silc_set_errno_reason(SILC_ERR_SYNTAX, "Invalid substitution expression");
+    goto out;
+  }
+
+  /* Check address syntax */
+  if (silc_buffer_len(&addr)) {
+    if (*silc_buffer_data(&addr) == '/') {
+      silc_buffer_pull(&addr, 1);
+      if (addr.tail[-1] != '/') {
+       silc_set_errno_reason(SILC_ERR_SYNTAX,
+                             "Invalid address syntax, missing '/'");
+       goto out;
+      }
+      silc_buffer_push_tail(&addr, 1);
+
+      if (!silc_buffer_len(&addr)) {
+       silc_set_errno_reason(SILC_ERR_SYNTAX,
+                             "Invalid address syntax, missing regular "
+                             "expression");
+       goto out;
+      }
+      str_addr = silc_memdup(silc_buffer_data(&addr),
+                            silc_buffer_len(&addr));
+
+    } else if (*silc_buffer_data(&addr) == '$' &&
+              silc_buffer_len(&addr) == 1) {
+      ctx.addr_number = -1;
+
+    } else if (isdigit((int)*silc_buffer_data(&addr))) {
+      ctx.addr_number = *silc_buffer_data(&addr) - '0';
+      silc_buffer_pull(&addr, 1);
+      while (silc_buffer_len(&addr) &&
+            isdigit((int)*silc_buffer_data(&addr))) {
+       ctx.addr_number *= 10;
+       ctx.addr_number += *silc_buffer_data(&addr) - '0';
+       silc_buffer_pull(&addr, 1);
+      }
+
+      if (silc_buffer_len(&addr)) {
+       silc_set_errno_reason(SILC_ERR_SYNTAX,
+                             "Invalid address syntax, not a number");
+       goto out;
+      }
+
+      if (ctx.addr_number == 0) {
+       silc_set_errno_reason(SILC_ERR_SYNTAX,
+                             "Invalid address syntax, line address is 0");
+       goto out;
+      }
+
+    } else {
+      silc_set_errno_reason(SILC_ERR_SYNTAX, "Unsupported address syntax");
+      goto out;
+    }
+  }
+
+  /* Check command syntax */
+  if (!silc_buffer_len(&command) || silc_buffer_len(&command) > 2) {
+    silc_set_errno_reason(SILC_ERR_SYNTAX, "Invalid commmand");
+    goto out;
+  }
+  if ((silc_buffer_len(&command) == 1 &&
+       !silc_buffer_memcmp(&command, "s", 1)) ||
+      (silc_buffer_len(&command) == 2 &&
+       !silc_buffer_memcmp(&command, "!s", 2))) {
+    silc_set_errno_reason(SILC_ERR_SYNTAX, "Invalid command");
+    goto out;
+  }
+  if (silc_buffer_len(&command) == 2)
+    addr_flags |= SILC_STR_REGEX_NOT;
+
+  /* Check REGEXP syntax */
+  if (!silc_buffer_len(&exp_start) ||
+      !silc_buffer_memcmp(&exp_start, "/", 1)) {
+    silc_set_errno_reason(SILC_ERR_SYNTAX,
+                         "Invalid substitution syntax, missing '/'");
+    goto out;
+  }
+  if (!silc_buffer_len(&exp_end) ||
+      !silc_buffer_memcmp(&exp_end, "/", 1)) {
+    silc_set_errno_reason(SILC_ERR_SYNTAX,
+                         "Invalid substitution syntax, missing '/'");
+    goto out;
+  }
+
+  /* Check FLAGS syntax */
+  if (silc_buffer_len(&flags)) {
+    if (silc_buffer_len(&flags) > 1) {
+      silc_set_errno_reason(SILC_ERR_SYNTAX, "Invalid flags");
+      goto out;
+    }
+
+    /* Check supported flags */
+    if (silc_buffer_len(&flags) == 1) {
+      if (silc_buffer_memcmp(&flags, "g", 1)) {
+       match_flags |= SILC_STR_REGEX_ALL;
+      } else {
+       silc_set_errno_reason(SILC_ERR_SYNTAX, "Unsupported flag");
+       goto out;
+      }
+    }
+  }
+
+  /* Set flags */
+  match_flags |= SILC_STR_REGEX_INCLUSIVE;
+  addr_flags |= SILC_STR_REGEX_NL | SILC_STR_REGEX_NO_ADVANCE;
+
+  ctx.str_regexp = silc_memdup(silc_buffer_data(&exp),
+                              silc_buffer_len(&exp));
+  ctx.addr_flags = addr_flags;
+  ctx.match_flags = match_flags;
+
+  /* Unescape escapes from REPLACEMENT */
+  ctx.rep = silc_buffer_copy(&rep);
+  if (!ctx.rep)
+    goto out;
+  if (silc_buffer_len(ctx.rep))
+    silc_buffer_format(ctx.rep,
+                      SILC_STR_REGEX("\\\\/", (SILC_STR_REGEX_ALL |
+                                               SILC_STR_REGEX_INCLUSIVE)),
+                        SILC_STR_REPLACE("/", 1),
+                      SILC_STR_END, SILC_STR_END);
+
+  /* If NUMBER or $ is specified, handle NOT flag in the silc_subst_addr */
+  if (ctx.addr_number)
+    addr_flags &= ~SILC_STR_REGEX_NOT;
+
+  SILC_LOG_DEBUG(("ADDR '%s' flags 0x%x, NUMBER %d", str_addr, addr_flags,
+                 ctx.addr_number));
+  SILC_LOG_DEBUG(("REGEXP '%s' flags 0x%x", ctx.str_regexp, match_flags));
+
+  /* Match and replace */
+  ret = silc_buffer_format(buffer,
+                          SILC_STR_REGEX(str_addr, addr_flags),
+                            SILC_STR_FUNC(silc_subst_addr, NULL, &ctx),
+                          SILC_STR_END, SILC_STR_END);
+
+ out:
+  if (str_addr && strlen(str_addr))
+    silc_free(str_addr);
+  silc_free(ctx.str_regexp);
+  silc_buffer_free(ctx.rep);
+
+  return ret >= 0 ? TRUE : FALSE;
+}
index 8b5a66544f26fd3893bb2c3717110eabae1c6d94..4047088f8a6767ac10899b2ed6c7f4522c1b8aae 100644 (file)
@@ -370,11 +370,6 @@ SilcBool silc_regex_buffer(SilcBuffer buffer, const char *regex,
  *    using the SILC_STR_REGEX in SILC Buffer Format API directly.  This
  *    function only provides basic matching and substitution.
  *
- * EXAMPLE
- *
- *    // Replace all foos with bar on all lines in the buffer
- *    silc_subst(buffer, "s/foo/bar/g");
- *
  ***/
 SilcBool silc_subst(SilcBuffer buffer, const char *subst);
 
index 052be536edc8460f2a1535ad900d8d05682409bb..44bac0ac44de196c4929678c46d849352650d9a9 100644 (file)
@@ -46,7 +46,7 @@ int main(int argc, char **argv)
   if (silc_buffer_format(&buf,
                         SILC_STR_REGEX("foo", SILC_STR_REGEX_ALL |
                                               SILC_STR_REGEX_INCLUSIVE),
-                          SILC_STR_STRING_APPEND("barbar"),
+                          SILC_STR_REPLACE("barbar", 6),
                         SILC_STR_END,
                         SILC_STR_END) < 0)
     goto err;
@@ -63,7 +63,7 @@ int main(int argc, char **argv)
   if (silc_buffer_format(&buf,
                         SILC_STR_REGEX("foo", SILC_STR_REGEX_ALL |
                                               SILC_STR_REGEX_INCLUSIVE),
-                          SILC_STR_DELETE(-1),
+                          SILC_STR_REPLACE("", 0),
                         SILC_STR_END,
                         SILC_STR_END) < 0)
     goto err;
index 41c51ad3fc969a19d036f053b76b48b9946071a3..c1c6b44b8093c297f872242d423ef2bb4d7dd816 100644 (file)
@@ -6,8 +6,8 @@ int main(int argc, char **argv)
 {
   SilcBool success = FALSE;
   SilcRegexStruct reg;
-  SilcRegexMatchStruct match[10];
-  int i, num_match = 10;
+  SilcRegexMatchStruct match[20];
+  int i, num_match = 20;
   char *regex, *string, *sub;
   SilcBufferStruct bmatch;
 
@@ -15,9 +15,261 @@ int main(int argc, char **argv)
     silc_log_debug(TRUE);
     silc_log_quick(TRUE);
     silc_log_debug_hexdump(TRUE);
-    silc_log_set_debug_string("*regex*,*errno*");
+    silc_log_set_debug_string("*regex*,*errno*,*buffmt*");
   }
 
+  string = silc_strdup("foobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/bar/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "barbar", 6))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar foobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/bar/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "barbar barbar", 13))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar foobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/bar/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "barbar foobar", 13))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar foobar\nfoobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "BARBARbar\nBARBARbar BARBARbar\nBARBARbar",
+                         39))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar foobar\nfoobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/BARBAR/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "BARBARbar\nBARBARbar foobar\nBARBARbar",
+                         36))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar foobar\nfoobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo//"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "bar\nbar foobar\nbar",
+                         18))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar foobar\nfoobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/B/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "Bbar\nBbar Bbar\nBbar",
+                         19))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar foobar\nfoobar");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/foo/B/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "Bbar\nBbar foobar\nBbar",
+                         21))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nBfoobar foobar\nBfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "/^B/s/foo/B/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch, "foobar\nBBbar Bbar\nBBbar\nfoo",
+                         27))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "/baz/s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nBARBARbar baz BARBARbar\nbazBARBARbar\nfoo",
+                         47))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "/baz/s/foo/BARBAR/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nBARBARbar baz foobar\nbazBARBARbar\nfoo",
+                         44))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "/baz/!s/foo/BARBAR/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "BARBARbar\nfoobar baz foobar\nbazfoobar\nBARBAR",
+                         44))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "2s/foo/BARBAR/"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nBARBARbar baz foobar\nbazfoobar\nfoo",
+                         41))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "2s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nBARBARbar baz BARBARbar\nbazfoobar\nfoo",
+                         44))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "200s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nfoobar baz foobar\nbazfoobar\nfoo",
+                         38))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "2!s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "BARBARbar\nfoobar baz foobar\nbazBARBARbar\nBARBAR",
+                         47))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "/xxx/s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nfoobar baz foobar\nbazfoobar\nfoo",
+                         38))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "!s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nfoobar baz foobar\nbazfoobar\nfoo",
+                         38))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "$s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+                         "foobar\nfoobar baz foobar\nbazfoobar\nBARBAR",
+                         41))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar baz foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "$!s/foo/BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+               "BARBARbar\nBARBARbar baz BARBARbar\nbazBARBARbar\nfoo",
+                         50))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar /baz/ foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/\\//BARBAR/g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+               "foobar\nfoobar BARBARbazBARBAR foobar\nbazfoobar\nfoo",
+                         50))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
+  string = silc_strdup("foobar\nfoobar /baz/ foobar\nbazfoobar\nfoo");
+  SILC_LOG_DEBUG(("Replace %s", string));
+  silc_buffer_set(&bmatch, string, strlen(string));
+  if (!silc_subst(&bmatch, "s/\\//\\/\\//g"))
+    goto err;
+  silc_buffer_printf(&bmatch, TRUE);
+  if (!silc_buffer_memcmp(&bmatch,
+               "foobar\nfoobar //baz// foobar\nbazfoobar\nfoo",
+                         42))
+    goto err;
+  silc_buffer_purge(&bmatch);
+
   regex = ".{5}";
   SILC_LOG_DEBUG(("Regex %s", regex));
   string = "abcdefghijklmn";
@@ -199,7 +451,6 @@ int main(int argc, char **argv)
   if (silc_regex(string, regex, &bmatch, NULL))
     goto err;
 
-
   regex = "(H..).(o..)";
   SILC_LOG_DEBUG(("Regex %s", regex));
   if (!silc_regex_compile(&reg, regex, 0))
@@ -213,7 +464,7 @@ int main(int argc, char **argv)
     if (match[i].start != -1) {
       SILC_LOG_DEBUG(("Match start %d, end %d", match[i].start,
                      match[i].end));
-      sub = silc_memdup(string + match[i].start, match[i].end - 
+      sub = silc_memdup(string + match[i].start, match[i].end -
                        match[i].start);
       SILC_LOG_DEBUG(("Match substring '%s'", sub));
       silc_free(sub);
@@ -255,7 +506,7 @@ int main(int argc, char **argv)
   for (i = 0; i < num_match; i++) {
     if (match[i].start != -1) {
       SILC_LOG_DEBUG(("Match start %d", match[i].start));
-      sub = silc_memdup(string + match[i].start, match[i].end - 
+      sub = silc_memdup(string + match[i].start, match[i].end -
                        match[i].start);
       SILC_LOG_DEBUG(("Match substring '%s'", sub));
       silc_free(sub);
@@ -277,7 +528,7 @@ int main(int argc, char **argv)
   for (i = 0; i < num_match; i++) {
     if (match[i].start != -1) {
       SILC_LOG_DEBUG(("Match start %d", match[i].start));
-      sub = silc_memdup(string + match[i].start, match[i].end - 
+      sub = silc_memdup(string + match[i].start, match[i].end -
                        match[i].start);
       SILC_LOG_DEBUG(("Match substring '%s'", sub));
       silc_free(sub);
@@ -292,7 +543,7 @@ int main(int argc, char **argv)
   for (i = 0; i < num_match; i++) {
     if (match[i].start != -1) {
       SILC_LOG_DEBUG(("Match start %d", match[i].start));
-      sub = silc_memdup(string + match[i].start, match[i].end - 
+      sub = silc_memdup(string + match[i].start, match[i].end -
                        match[i].start);
       SILC_LOG_DEBUG(("Match substring '%s'", sub));
       silc_free(sub);
@@ -314,7 +565,7 @@ int main(int argc, char **argv)
   for (i = 0; i < num_match; i++) {
     if (match[i].start != -1) {
       SILC_LOG_DEBUG(("Match start %d", match[i].start));
-      sub = silc_memdup(string + match[i].start, match[i].end - 
+      sub = silc_memdup(string + match[i].start, match[i].end -
                        match[i].start);
       SILC_LOG_DEBUG(("Match substring '%s'", sub));
       silc_free(sub);
@@ -363,4 +614,3 @@ int main(int argc, char **argv)
 
   return success;
 }
-