Hello, I tried to implement the mini-language, which is a
simplified reglar expression for this specific use.

As a ultra-POC, the attached patch has very ad-hoc preprocess
function and does on-the-fly preprocessing, compilation then
execution of regular expression. And it is applied to only the
first ten or so matchings in psql_completion().

The first attachment is the same with that of previous patchset.

Every matching line looks like the following,

> else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](.."))
>       COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));

As a temporary desin, "{}" means grouping, "|" means alternatives,
"[]" means capture and '#id' means any identifier.

The largest problem of this would be its computation cost:( This
in turn might be too slow if about three hundred matches run...

I see no straight-forward way to preprocess these strings.. A
possible solution would be extracting these strings then
auto-generate a c-srouce to preprocess them. And when running,
RM() retrieves the compiled regular expression using the string
as the key.


At Tue, 17 Nov 2015 15:35:43 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI 
<horiguchi.kyot...@lab.ntt.co.jp> wrote in 
<20151117.153543.183036803.horiguchi.kyot...@lab.ntt.co.jp>
> At Mon, 16 Nov 2015 12:16:07 -0300, Alvaro Herrera <alvhe...@2ndquadrant.com> 
> wrote in <20151116151606.GW614468@alvherre.pgsql>
> > I don't think this is an improvement.  It seems to me that a lot more
> > work is required to maintain these regular expressions, which while
> > straightforward are not entirely trivial (harder to read).
> > 
> > If we could come up with a reasonable format that is pre-processed to a
> > regexp, that might be better.  I think Thomas' proposed patch is closer
> > to that than Horiguchi-san's.
> 
> I aimed that matching mechanism can handle optional elements in
> syntexes more robustly. But as all you are saying, bare regular
> expression is too complex here.

At Tue, 17 Nov 2015 16:09:25 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI 
<horiguchi.kyot...@lab.ntt.co.jp> wrote in 
<20151117.160925.45883793.horiguchi.kyot...@lab.ntt.co.jp>
> if (Match("^,ALTER,TABLE,\id,$") ||
>     Match("^,ALTER,TABLE,IF,EXISTS,\id,$"))...
> 
> Interpreting this kind of mini-language into regular expressions
> could be doable..

regards,

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
>From cdc0b9cce43e38463af0b2b7ad4a0181f41995a2 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:03:18 +0900
Subject: [PATCH 1/2] Allow regex module to be used outside server.

To make regular expression available on frontend, enable pg_regex
module and the files included from it to be detached from the features
not available when used outside backend.
---
 src/backend/regex/regc_pg_locale.c |  7 +++++++
 src/backend/regex/regcomp.c        | 12 ++++++++++++
 src/include/regex/regcustom.h      |  6 ++++++
 src/include/utils/pg_locale.h      |  7 +++++--
 4 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/src/backend/regex/regc_pg_locale.c b/src/backend/regex/regc_pg_locale.c
index b707b06..a631ac2 100644
--- a/src/backend/regex/regc_pg_locale.c
+++ b/src/backend/regex/regc_pg_locale.c
@@ -220,6 +220,13 @@ static const unsigned char pg_char_properties[128] = {
 };
 
 
+/* Get rid of using server-side feature elsewhere of server. */
+#ifdef FRONTEND
+#define lc_ctype_is_c(col) (true)
+#define GetDatabaseEncoding() PG_UTF8
+#define ereport(x,...) exit(1)
+#endif
+
 /*
  * pg_set_regex_collation: set collation for these functions to obey
  *
diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c
index a165b3b..b35ccb4 100644
--- a/src/backend/regex/regcomp.c
+++ b/src/backend/regex/regcomp.c
@@ -2040,11 +2040,17 @@ rfree(regex_t *re)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rcancelrequested(void)
 {
+#ifndef FRONTEND
 	return InterruptPending && (QueryCancelPending || ProcDiePending);
+#else
+	return 0;
+#endif
 }
 
 /*
@@ -2056,11 +2062,17 @@ rcancelrequested(void)
  * The current implementation is Postgres-specific.  If we ever get around
  * to splitting the regex code out as a standalone library, there will need
  * to be some API to let applications define a callback function for this.
+ *
+ * This check is available only on server side.
  */
 static int
 rstacktoodeep(void)
 {
+#ifndef FRONTEND
 	return stack_is_too_deep();
+#else
+	return 0;
+#endif
 }
 
 #ifdef REG_DEBUG
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index dbb461a..ffc4031 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -29,7 +29,11 @@
  */
 
 /* headers if any */
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#else
 #include "postgres.h"
+#endif
 
 #include <ctype.h>
 #include <limits.h>
@@ -53,7 +57,9 @@
 #define MALLOC(n)		malloc(n)
 #define FREE(p)			free(VS(p))
 #define REALLOC(p,n)	realloc(VS(p),n)
+#ifndef assert
 #define assert(x)		Assert(x)
+#endif
 
 /* internal character type and related */
 typedef pg_wchar chr;			/* the type itself */
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 8e91033..d71ec07 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -17,14 +17,16 @@
 #include <xlocale.h>
 #endif
 
+/* Don't look GUCs elsewhere of server */
+#ifndef FRONTEND
 #include "utils/guc.h"
 
-
 /* GUC settings */
 extern char *locale_messages;
 extern char *locale_monetary;
 extern char *locale_numeric;
 extern char *locale_time;
+#endif
 
 /* lc_time localization cache */
 extern char *localized_abbrev_days[];
@@ -32,7 +34,7 @@ extern char *localized_full_days[];
 extern char *localized_abbrev_months[];
 extern char *localized_full_months[];
 
-
+#ifndef FRONTEND
 extern bool check_locale_messages(char **newval, void **extra, GucSource source);
 extern void assign_locale_messages(const char *newval, void *extra);
 extern bool check_locale_monetary(char **newval, void **extra, GucSource source);
@@ -41,6 +43,7 @@ extern bool check_locale_numeric(char **newval, void **extra, GucSource source);
 extern void assign_locale_numeric(const char *newval, void *extra);
 extern bool check_locale_time(char **newval, void **extra, GucSource source);
 extern void assign_locale_time(const char *newval, void *extra);
+#endif
 
 extern bool check_locale(int category, const char *locale, char **canonname);
 extern char *pg_perm_setlocale(int category, const char *locale);
-- 
1.8.3.1

>From 08965765409f0a339058e12b3e7f55a7c8792e6c Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp>
Date: Fri, 30 Oct 2015 18:18:18 +0900
Subject: [PATCH 2/2] Replace previous matching rule with regexps take 2.

This patch simply replaces previous matching rule with regular
expressions. As a POC, this patch repalces only first several entries.
---
 src/bin/psql/Makefile       |  15 ++-
 src/bin/psql/tab-complete.c | 275 ++++++++++++++++++++++++++++++++++----------
 2 files changed, 226 insertions(+), 64 deletions(-)

diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f1336d5..bd0b5cc 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -23,12 +23,23 @@ override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/p
 OBJS=	command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
 	startup.o prompt.o variables.o large_obj.o print.o describe.o \
 	tab-complete.o mbprint.o dumputils.o keywords.o kwlookup.o \
-	sql_help.o \
+	sql_help.o regcomp.o regexec.o regfree.o wstrncmp.o \
 	$(WIN32RES)
 
-
+CFLAGS+= -DFRONTEND
 all: psql
 
+
+regc_color.c regc_cvec.c regc_lex.c regc_locale.c regc_nfa.c regcomp.c regc_pg_locale.c rege_dfa.c regexec.c regfree.c: % :$(top_srcdir)/src/backend/regex/%
+	rm -f $@ && $(LN_S) $< .
+
+wstrncmp.c: % : $(top_srcdir)/src/backend/utils/mb/%
+	rm -f $@ && $(LN_S) $< .
+
+regcomp.o regexec.o regfree.o:regc_color.c regc_cvec.c regc_lex.c regc_locale.c regc_nfa.c regcomp.c regc_pg_locale.c rege_dfa.c regexec.c regfree.c wstrncmp.c
+
+tab-complete.o: tab-complete.c
+
 psql: $(OBJS) | submake-libpq submake-libpgport
 	$(CC) $(CFLAGS) $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b58ec14..7941020 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -54,6 +54,8 @@
 #include "common.h"
 #include "settings.h"
 #include "stringutils.h"
+#include "regex/regex.h"
+#include "catalog/pg_collation.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -792,6 +794,18 @@ typedef struct
 #define THING_NO_DROP		(1 << 1)	/* should not show up after DROP */
 #define THING_NO_SHOW		(THING_NO_CREATE | THING_NO_DROP)
 
+#define CAPBUFLEN 128
+#define MATCHNUM 5
+
+#define RM(pat) (rematch(linebufw, wstrlen, pat, rmatches))
+#define MATCHBEG(n) (rmatches[n].rm_so)
+#define MATCHEND(n) (rmatches[n].rm_eo)
+#define MATCHLEN(n) (MATCHEND(n) - MATCHBEG(n))
+#define CAPCPYLEN(n)(MATCHLEN(n)<CAPBUFLEN ? MATCHLEN(n):CAPBUFLEN - 1)
+#define CAPTURE0(n) (strncpy(capbuf[n], linebuf + MATCHBEG(n), CAPCPYLEN(n)),capbuf[n][CAPCPYLEN(n)] = 0)
+#define CAPTURE(n)  (CAPTURE0(n), capbuf[n])
+#define CAPBUF(n)   (capbuf[n])
+
 static const pgsql_thing_t words_after_create[] = {
 	{"AGGREGATE", NULL, &Query_for_list_of_aggregates},
 	{"CAST", NULL, NULL},		/* Casts have complex structures for names, so
@@ -842,7 +856,6 @@ static const pgsql_thing_t words_after_create[] = {
 	{NULL}						/* end of list */
 };
 
-
 /* Forward declaration of functions */
 static char **psql_completion(const char *text, int start, int end);
 static char *create_command_generator(const char *text, int state);
@@ -868,11 +881,28 @@ static void get_previous_words(int point, char **previous_words, int nwords);
 
 static char *get_guctype(const char *varname);
 
+static bool rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches);
+
+
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
 #endif
 
+static int
+pg_ascii2wchar_with_len(const char *from, pg_wchar *to, int len)
+{
+	int			cnt = 0;
+
+	while (len > 0 && *from)
+	{
+		*to++ = *from++;
+		len--;
+		cnt++;
+	}
+	*to = 0;
+	return cnt;
+}
 
 /*
  * Initialize the readline library for our purposes.
@@ -893,6 +923,31 @@ initialize_readline(void)
 	 */
 }
 
+static pg_wchar *
+expand_wchar_buffer(pg_wchar *p, int *buflen, int newlen, int limit)
+{
+	pg_wchar *ret = p;
+	int       len1 = newlen - 1;
+
+	Assert(newlen > 0);
+	if (newlen >= *buflen)
+	{
+		int mask;
+
+		/* Allocate in size of 2^n. Minimum 256 wchars */
+		for (mask = 255 ; mask < limit && (len1 & mask) != len1 ;
+			 mask = (mask << 1) | 1);
+		
+		/* mask is (<new buffer length> - 1) here */
+		if (mask >= limit) return NULL; /* Exceeds limit */
+		
+		*buflen = mask + 1;
+		
+		ret = pg_realloc(p, (*buflen) * sizeof(pg_wchar));
+	}		
+
+	return ret;
+}
 
 /*
  * The completion function.
@@ -905,6 +960,13 @@ initialize_readline(void)
 static char **
 psql_completion(const char *text, int start, int end)
 {
+	const char *linebuf = rl_line_buffer;
+	static pg_wchar	 *linebufw = NULL;
+	static int	     linebufwlen = 0;
+	int len, wstrlen;
+	regmatch_t rmatches[MATCHNUM];
+	char capbuf[MATCHNUM][CAPBUFLEN];
+
 	/* This is the variable we'll return. */
 	char	  **matches = NULL;
 
@@ -967,6 +1029,10 @@ psql_completion(const char *text, int start, int end)
 	 * probably intending to type.
 	 */
 	get_previous_words(start, previous_words, lengthof(previous_words));
+	len = strlen(linebuf);
+	/* Expand string buffer if needed */
+	linebufw = expand_wchar_buffer(linebufw, &linebufwlen, len + 1, 2048); 
+	wstrlen = pg_ascii2wchar_with_len(linebuf, linebufw, strlen(linebuf));
 
 	/* If a backslash command was started, continue */
 	if (text[0] == '\\')
@@ -984,36 +1050,31 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* If no previous word, suggest one of the basic sql commands */
-	else if (prev_wd[0] == '\0')
+	else if (RM("^"))
 		COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
 	/* complete with something you can create */
-	else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
+	else if (RM("^CREATE "))
 		matches = completion_matches(text, create_command_generator);
 
 /* DROP, but not DROP embedded in other commands */
 	/* complete with something you can drop */
-	else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-			 prev2_wd[0] == '\0')
+	else if (RM("^DROP "))
 		matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
 
 	/* ALTER TABLE */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "TABLE") == 0)
-	{
+	else if (RM("^ALTER TABLE "))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
 								   "UNION SELECT 'ALL IN TABLESPACE'");
-	}
 
 	/*
 	 * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
 	 * in ALTER TABLE sth ALTER
 	 */
-	else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TABLE") != 0)
+	else if (RM("^ALTER "))
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
@@ -1026,9 +1087,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTER);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx */
-	else if (pg_strcasecmp(prev4_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev3_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
+	else if (RM("ALL IN TABLESPACE #id "))
 	{
 		static const char *const list_ALTERALLINTSPC[] =
 		{"SET TABLESPACE", "OWNED BY", NULL};
@@ -1036,38 +1095,24 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERALLINTSPC);
 	}
 	/* ALTER TABLE,INDEX,MATERIALIZED VIEW xxx ALL IN TABLESPACE xxx OWNED BY */
-	else if (pg_strcasecmp(prev6_wd, "ALL") == 0 &&
-			 pg_strcasecmp(prev5_wd, "IN") == 0 &&
-			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
-			 pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
-			 pg_strcasecmp(prev4_wd, "BY") == 0)
-	{
+	else if (RM("ALL IN TABLESPACE #id OWNED BY "))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-	}
 	/* ALTER AGGREGATE,FUNCTION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+	else if (RM("^ALTER {AGGREGATE|FUNCTION} #id "))
 		COMPLETE_WITH_CONST("(");
 	/* ALTER AGGREGATE,FUNCTION <name> (...) */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
-			  pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
+	else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](.."))
+		COMPLETE_WITH_FUNCTION_ARG(CAPTURE(1));
+	else if (RM("ALTER {AGGREGATE|FUNCTION} [#id](..)"))
 	{
-		if (prev_wd[strlen(prev_wd) - 1] == ')')
-		{
-			static const char *const list_ALTERAGG[] =
+		static const char *const list_ALTERAGG[] =
 			{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-			COMPLETE_WITH_LIST(list_ALTERAGG);
-		}
-		else
-			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
+		COMPLETE_WITH_LIST(list_ALTERAGG);
 	}
 
 	/* ALTER SCHEMA <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
+	else if (RM("ALTER SCHEMA #id "))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", NULL};
@@ -1076,8 +1121,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER COLLATION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "COLLATION") == 0)
+	else if (RM("ALTER COLLATION #id "))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1086,8 +1130,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER CONVERSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
+	else if (RM("ALTER CONVERSION #id "))
 	{
 		static const char *const list_ALTERGEN[] =
 		{"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
@@ -1096,8 +1139,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER DATABASE <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+	else if (RM("ALTER DATABASE #id "))
 	{
 		static const char *const list_ALTERDATABASE[] =
 		{"RESET", "SET", "OWNER TO", "RENAME TO", "IS_TEMPLATE",
@@ -1107,17 +1149,13 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev_wd, "TRIGGER") == 0)
+	else if (RM("ALTER EVENT TRIGGER"))
 	{
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	}
 
 	/* ALTER EVENT TRIGGER <name> */
-	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev3_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+	else if (RM("ALTER EVENT TRIGGER #id "))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER[] =
 		{"DISABLE", "ENABLE", "OWNER TO", "RENAME TO", NULL};
@@ -1126,10 +1164,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EVENT TRIGGER <name> ENABLE */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "EVENT") == 0 &&
-			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
-			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
+	else if (RM("ALTER EVENT TRIGGER #id ENABLE "))
 	{
 		static const char *const list_ALTER_EVENT_TRIGGER_ENABLE[] =
 		{"REPLICA", "ALWAYS", NULL};
@@ -1138,8 +1173,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER EXTENSION <name> */
-	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+	else if (RM("ALTER EXTENSION #id "))
 	{
 		static const char *const list_ALTEREXTENSION[] =
 		{"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
@@ -1148,8 +1182,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN */
-	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+	else if (RM("ALTER FOREIGN "))
 	{
 		static const char *const list_ALTER_FOREIGN[] =
 		{"DATA WRAPPER", "TABLE", NULL};
@@ -1158,10 +1191,7 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* ALTER FOREIGN DATA WRAPPER <name> */
-	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
-			 pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
-			 pg_strcasecmp(prev3_wd, "DATA") == 0 &&
-			 pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+	else if (RM("ALTER FOREIGN DATA WRAPPER #id "))
 	{
 		static const char *const list_ALTER_FDW[] =
 		{"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
@@ -4185,12 +4215,10 @@ psql_completion(const char *text, int start, int end)
 		for (i = 0; i < lengthof(previous_words); i++)
 			free(previous_words[i]);
 	}
-
 	/* Return our Grand List O' Matches */
 	return matches;
 }
 
-
 /*
  * GENERATOR FUNCTIONS
  *
@@ -4923,4 +4951,127 @@ dequote_file_name(char *text, char quote_char)
 }
 #endif   /* NOT_USED */
 
+//#define WB  "[\\t\\n@$><=;|&{\\(\\) ]"				/* WORD BREAKS */
+//#define NWB "[^\\t\\n@$><=;|&{\\(\\) ]"				/* ^WORD BREAKS */
+#define WB  "[\\t\\n@$><=;|&{ ]"				/* WORD BREAKS */
+#define NWB "[^\\t\\n@$><=;|&{ ]"				/* ^WORD BREAKS */
+#define PB  "^(?:.*;)?\\s*"			/* bind to the beginning of the line */
+#define PM  "^(?:.*"NWB WB")?\\s*"					/* floating head */
+#define ID  NWB"+"									/* identifier */
+#define KWD "\\w+"									/* any keywords */
+
+bool
+rematch(pg_wchar *line, int linelen, char *pat, regmatch_t *rmatches)
+{
+	pg_wchar rewstr[4096];
+	char restr[4096];
+	char *p, *rp;
+	int relen;
+	int ret;
+	regex_t re;
+	bool prev_is_ws = false;
+	char *NORMAL_TAIL = NWB"*$";
+	char *INPAREN_TAIL = "[^\\)]*$";
+	char *tail;
+
+	restr[0] = 0;
+	if (*pat != '^')
+		strcpy(restr, PM);
+	rp = restr + strlen(restr);
+	for (p = pat ; *p ; p++)
+	{
+		tail = NORMAL_TAIL;
+		switch (*p)
+		{
+		case '.':
+			if (strncmp(p+1, ".", 1) == 0)
+			{
+				p++;
+				strcpy(rp, "[^\\)]*");
+			}
+			else
+				exit(1);
+			break;
+		case '#':
+			if (strncmp(p+1, "id", 2) == 0)
+			{
+				p += 2;
+				if (!prev_is_ws)
+					strcpy(rp, WB"+");
+				strcpy(rp, ID NWB "+");
+				prev_is_ws = false;
+			}
+			else
+				exit(1);
+			break;
+		case '^':
+			strcpy(rp, "^(?:.*;)?\\s*");
+			prev_is_ws = true;
+			break;
+		case '$':
+			strcpy(rp, NWB"*$");
+			prev_is_ws = false;
+			break;
+		case '?':
+			strcpy(rp,"?");
+			prev_is_ws = false;
+			break;
+		case ' ':
+			if (!prev_is_ws)
+				strcpy(rp, WB"+");
+			prev_is_ws = true;
+			break;
+		case '(':
+			strcpy(rp, WB"*\\([\\)]*");
+			prev_is_ws = true;
+			tail = INPAREN_TAIL;
+			break;
+		case ')':
+			strcpy(rp, WB"*\\)"WB"*");
+			prev_is_ws = true;
+			break;
+		case '[':
+			strcpy(rp, "(");
+			break;
+		case ']':
+			strcpy(rp, ")");
+			break;
+		case '{':
+			strcpy(rp, "(?:");
+			break;
+		case '}':
+			strcpy(rp, ")");
+			break;
+		case '|':
+			strcpy(rp, "|");
+			break;
+		default:
+			if (*p)
+			{
+				if (!prev_is_ws)
+					strcpy(rp, WB"+");
+				while(isalnum(*p))
+					*rp++ = *p++;
+				*rp = 0;
+				p--;
+				prev_is_ws = false;
+			}
+		}
+		while (*rp) rp++;
+		/* No overrun check! */
+	}
+	strcpy(rp, tail);
+
+	relen = pg_ascii2wchar_with_len(restr, rewstr, strlen(restr));
+	ret = pg_regcomp(&re, rewstr, relen, REG_ADVANCED|REG_ICASE,
+					 C_COLLATION_OID);
+	if (ret != 0)
+		exit(1);
+
+	ret = pg_regexec(&re, line, linelen, 0, NULL, MATCHNUM, rmatches, 0) == 0;
+	pg_regfree(&re);
+
+	return ret;
+}
+
 #endif   /* USE_READLINE */
-- 
1.8.3.1

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to