Implement lexer and parser for cypher commands with MATCH prototype. (#880)

This commit introduces the cypher .l and cypher.y file contains n a lexer
and grammar for handling cypher commands, respectively. The new files also
include a prototype function for the MATCH command, which will be further
developed in subsequent commits.

flex -b -Cfe -p -p -o ‘cypher.c’ cypher.l
bison -d cypher.y
make
diff --git a/Makefile b/Makefile
index 2d14f69..0a1b239 100644
--- a/Makefile
+++ b/Makefile
@@ -43,6 +43,8 @@
 	mainloop.o \
 	prompt.o \
 	psqlscanslash.o \
+	cypher.o \
+	cypher.tab.o \
 	sql_help.o \
 	startup.o \
 	stringutils.o \
@@ -53,6 +55,8 @@
 all: psql
 
 psql: $(OBJS)
+	flex -b -Cfe -p -p -o'cypher.c' cypher.l
+	bison -d cypher.y
 	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o agesql
 
 
diff --git a/cypher.l b/cypher.l
new file mode 100644
index 0000000..42fe06c
--- /dev/null
+++ b/cypher.l
@@ -0,0 +1,44 @@
+%option noyywrap
+%{
+#include "postgres_fe.h"                                                        
+                                                                                
+#include "psqlscanslash.h"                                                      
+#include "common/logging.h"                                                     
+#include "fe_utils/conditional.h"                                               
+                                                                                
+#include "libpq-fe.h"  
+#include "cypherscan.h"
+#include "cypher.tab.h"
+%}
+
+%%
+
+"->" { return ARROW; }
+"[" { return LBRACKET; }
+"]" { return RBRACKET; }
+"(" { return LPAREN; }
+")" { return RPAREN; }
+":" { return COLON; }
+"|" { return PIPE; }
+"," { return COMMA; }
+";" { return SEMICOLON; }
+"{" { return LBRACE; }
+"}" { return RBRACE; }
+
+"MATCH" { return MATCH; }
+"WHERE" { return WHERE; }
+"WITH" { return WITH; }
+"ORDER" { return ORDER; }
+"BY" { return BY; }
+"SKIP" { return SKIP; }
+"LIMIT" { return LIMIT; }
+"RETURN" { return RETURN; }
+"AS" { return AS; }
+
+[0-9]+ { yylval.int_val = atoi(yytext); return INTEGER; }
+[a-zA-Z][a-zA-Z0-9_]* { yylval.str_val = strdup(yytext); return IDENTIFIER; }
+"([^\"]|\.)*" { yylval.str_val = strdup(yytext); return STRING; }
+. { return UNKNOWN; }
+
+%%
+
diff --git a/cypher.y b/cypher.y
new file mode 100644
index 0000000..ccfbcfb
--- /dev/null
+++ b/cypher.y
@@ -0,0 +1,193 @@
+%{
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "postgres_fe.h"                                                        
+                                                                                
+#include "psqlscanslash.h"                                                      
+#include "common/logging.h"                                                     
+#include "fe_utils/conditional.h"                                               
+                                                                                
+#include "libpq-fe.h"                                                           
+#include "cypherscan.h"                                                         
+#include "cypher.tab.h" 
+
+void yyerror(char const *s);
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+typedef struct {
+    char* str_val;
+    int int_val;
+} yyval;
+
+int yylex(void);
+
+int order_clause_direction = 1; // 1 for ascending, -1 for descending
+%}
+
+%union {
+    char* str_val;
+    int int_val;
+}
+
+%token ASC ARROW AS DESC LBRACKET RBRACKET LPAREN RPAREN COLON PIPE COMMA SEMICOLON LBRACE RBRACE MATCH WHERE WITH ORDER BY SKIP LIMIT RETURN
+%token <int_val> INTEGER
+%token <str_val> IDENTIFIER STRING
+%token UNKNOWN
+
+%left PIPE
+%left ARROW
+
+%start statement
+
+%%
+
+statement:
+    { printf("Query parsed successfully.\n"); }
+    ;
+
+query:
+    match_clause
+    //where_clause_opt
+    //with_clause_opt
+    //return_clause
+    ;
+
+match_clause:
+    MATCH path_pattern { printf("Match clause parsed successfully.\n"); }
+	;    
+
+path_pattern:
+    node_pattern
+    | node_pattern ARROW rel_pattern node_pattern { printf("Path pattern parsed successfully.\n"); }
+    ;
+
+node_pattern:
+    LPAREN node_labels_opt node_properties_opt RPAREN { printf("Node pattern parsed successfully.\n"); }
+    ;
+
+node_labels_opt:
+    /* empty */ { printf("No node labels.\n"); }
+    | COLON IDENTIFIER { printf("Node label parsed: %s.\n", $2); }
+    | node_labels_opt COLON IDENTIFIER { printf("Node label parsed: %s.\n", $3); }
+    ;
+
+node_properties_opt:
+    /* empty */ { printf("No node properties.\n"); }
+    | LBRACE map_literal RBRACE { printf("Node properties parsed successfully.\n"); }
+    ;
+
+rel_pattern:
+    rel_type rel_direction rel_type { printf("Rel pattern parsed successfully.\n"); }
+    ;
+
+rel_type:
+    { printf("Rel type parsed: \n"); }
+    ;
+
+//rel_type:
+//    COLON str_val { printf("Rel type parsed: %s.\n", $2); }
+//     | LBRACKET str_val RBRACKET { printf("Rel type parsed: %s.\n", $2); }
+//    ;
+
+
+rel_direction:
+    {printf("Rel direction parsed: ->.\n"); }
+    ;
+
+//rel_direction:
+//    ARROW { printf("Rel direction parsed: ->.\n"); }
+//    | ARROW rel_type_name ARROW { printf("Rel direction parsed: ->%s->.\n", $2); }
+//    ;
+
+map_literal:
+    /* empty */ { printf("Empty map literal.\n"); }
+    | nonempty_map_literal { printf("Nonempty map literal.\n"); }
+    ;
+
+nonempty_map_literal:
+    map_entry { printf("Map literal entry parsed successfully.\n"); }
+    | nonempty_map_literal COMMA map_entry { printf("Map literal entry parsed successfully.\n"); }
+    ;
+
+map_entry:
+    IDENTIFIER COLON expression { printf("Map entry parsed successfully.\n"); }
+    ;
+
+expression:
+    INTEGER { printf("Integer expression parsed: %d.\n", $1); }
+    | STRING { printf("String expression parsed: %s.\n", $1); }
+    | IDENTIFIER { printf("Identifier expression parsed: %s.\n", $1); }
+    ;
+
+where_clause_opt:
+    /* empty */ { printf("No WHERE clause.\n"); }
+    | WHERE expression { printf("WHERE clause parsed successfully.\n"); }
+    ;
+
+with_clause_opt:
+    /* empty */ { printf("No WITH clause.\n"); }
+    | WITH expression_list return_clause { printf("WITH clause parsed successfully.\n"); }
+
+expression_list:
+	expression { printf("Expression parsed successfully.\n"); }
+	| expression_list COMMA expression { printf("Expression parsed successfully.\n"); }
+	;
+
+return_clause:
+	RETURN return_item_list order_clause_opt skip_clause_opt limit_clause_opt { printf("Return clause parsed successfully.\n"); }
+;
+
+return_item_list:
+	return_item { printf("Return item parsed successfully.\n"); }
+	| return_item_list COMMA return_item { printf("Return item parsed successfully.\n"); }
+	;
+
+return_item:
+	expression { printf("Return item parsed successfully.\n"); }
+	| expression AS IDENTIFIER { printf("Return item with alias parsed successfully.\n"); }
+	;
+
+order_clause_opt:
+	/* empty */ { printf("No ORDER BY clause.\n"); }
+	| ORDER BY sort_item_list { printf("ORDER BY clause parsed successfully.\n"); }
+	;
+
+sort_item_list:
+sort_item { printf("Sort item parsed successfully.\n"); }
+| sort_item_list COMMA sort_item { printf("Sort item parsed successfully.\n"); }
+;
+
+sort_item:
+expression sort_direction_opt { printf("Sort item parsed successfully.\n"); }
+;
+
+sort_direction_opt:
+	/* empty */ { printf("Sort direction not specified; defaulting to ASC.\n"); order_clause_direction = 1; }
+	| ASC { printf("Sort direction specified: ASC.\n"); order_clause_direction = 1; }
+	| DESC { printf("Sort direction specified: DESC.\n"); order_clause_direction = -1; }
+	;
+
+skip_clause_opt:
+	/* empty */ { printf("No SKIP clause.\n"); }
+	| SKIP INTEGER { printf("SKIP clause parsed: %d.\n", $2); }
+	;
+
+limit_clause_opt:
+	/* empty */ { printf("No LIMIT clause.\n"); }
+	| LIMIT INTEGER { printf("LIMIT clause parsed: %d.\n", $2); }
+	;
+
+%%
+
+void yyerror(char const *s)
+{
+	printf("Parser error: %s\n", s);
+}
+
+void
+psql_scan_cypher_command(PsqlScanState state)
+{
+
+}
diff --git a/cypherscan.h b/cypherscan.h
new file mode 100644
index 0000000..80dcf05
--- /dev/null
+++ b/cypherscan.h
@@ -0,0 +1,14 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2022, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/cypherscan.h
+ */
+#ifndef CYPHERSACN_H
+#define CYPHERSCAN_H
+
+#include "fe_utils/psqlscan.h"
+void psql_scan_cypher_command(PsqlScanState state);
+
+#endif   /* CYPHERSCAN_H */
diff --git a/mainloop.c b/mainloop.c
index b0c4177..0e1ee2b 100644
--- a/mainloop.c
+++ b/mainloop.c
@@ -15,6 +15,7 @@
 #include "mb/pg_wchar.h"
 #include "prompt.h"
 #include "settings.h"
+#include "cypherscan.h"
 
 /* callback functions for our flex lexer */
 const PsqlScanCallbacks psqlscan_callbacks = {
@@ -491,6 +492,7 @@
 					pg_send_history(history_buf);
 					line_saved_in_history = true;
 				}
+				psql_scan_cypher_command(query_buf->data);
 
 				/* execute backslash command */
 				slashCmdStatus = HandleSlashCmds(scan_state,
@@ -498,6 +500,7 @@
 												 query_buf,
 												 previous_buf);
 
+
 				success = slashCmdStatus != PSQL_CMD_ERROR;
 
 				/*
diff --git a/psqlscanslash.c b/psqlscanslash.c
index aee12dd..515f215 100644
--- a/psqlscanslash.c
+++ b/psqlscanslash.c
@@ -8,7 +8,7 @@
  *
  * See fe_utils/psqlscan_int.h for additional commentary.
  *
- * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
@@ -1151,8 +1151,6 @@
 #line 29 "psqlscanslash.l"
 #include "fe_utils/psqlscan_int.h"
 
-#define PQmblenBounded(s, e)  strnlen(s, PQmblen(s, e))
-
 /*
  * We must have a typedef YYSTYPE for yylex's first argument, but this lexer
  * doesn't presently make use of that argument, so just declare it as int.
@@ -1195,7 +1193,7 @@
 
 /* LCOV_EXCL_START */
 
-#line 1199 "psqlscanslash.c"
+#line 1197 "psqlscanslash.c"
 /* Except for the prefix, these options should match psqlscan.l */
 #define YY_NO_INPUT 1
 /*
@@ -1211,7 +1209,7 @@
 /*
  * Assorted character class definitions that should match psqlscan.l.
  */
-#line 1215 "psqlscanslash.c"
+#line 1213 "psqlscanslash.c"
 
 #define INITIAL 0
 #define xslashcmd 1
@@ -1491,11 +1489,11 @@
 		}
 
 	{
-#line 121 "psqlscanslash.l"
+#line 119 "psqlscanslash.l"
 
 
 
-#line 125 "psqlscanslash.l"
+#line 123 "psqlscanslash.l"
 		/* Declare some local variables inside yylex(), for convenience */
 		PsqlScanState cur_state = yyextra;
 		PQExpBuffer output_buf = cur_state->output_buf;
@@ -1515,7 +1513,7 @@
 	 * lexer; but if we are, just spit data to the output_buf until EOF.
 	 */
 
-#line 1519 "psqlscanslash.c"
+#line 1517 "psqlscanslash.c"
 
 	while ( /*CONSTCOND*/1 )		/* loops until end-of-file is reached */
 		{
@@ -1548,7 +1546,7 @@
 case 1:
 /* rule 1 can match eol */
 YY_RULE_SETUP
-#line 144 "psqlscanslash.l"
+#line 142 "psqlscanslash.l"
 { ECHO; }
 	YY_BREAK
 /*
@@ -1559,7 +1557,7 @@
 case 2:
 /* rule 2 can match eol */
 YY_RULE_SETUP
-#line 153 "psqlscanslash.l"
+#line 151 "psqlscanslash.l"
 {
 					yyless(0);
 					cur_state->start_state = YY_START;
@@ -1568,7 +1566,7 @@
 	YY_BREAK
 case 3:
 YY_RULE_SETUP
-#line 159 "psqlscanslash.l"
+#line 157 "psqlscanslash.l"
 { ECHO; }
 	YY_BREAK
 
@@ -1581,12 +1579,12 @@
 case 4:
 /* rule 4 can match eol */
 YY_RULE_SETUP
-#line 170 "psqlscanslash.l"
+#line 168 "psqlscanslash.l"
 { }
 	YY_BREAK
 case 5:
 YY_RULE_SETUP
-#line 172 "psqlscanslash.l"
+#line 170 "psqlscanslash.l"
 {
 					if (option_type == OT_FILEPIPE)
 					{
@@ -1604,7 +1602,7 @@
 	YY_BREAK
 case 6:
 YY_RULE_SETUP
-#line 187 "psqlscanslash.l"
+#line 185 "psqlscanslash.l"
 {
 					yyless(0);
 					BEGIN(xslasharg);
@@ -1622,7 +1620,7 @@
 case 7:
 /* rule 7 can match eol */
 YY_RULE_SETUP
-#line 203 "psqlscanslash.l"
+#line 201 "psqlscanslash.l"
 {
 					/*
 					 * Unquoted space is end of arg; do not eat.  Likewise
@@ -1640,7 +1638,7 @@
 	YY_BREAK
 case 8:
 YY_RULE_SETUP
-#line 218 "psqlscanslash.l"
+#line 216 "psqlscanslash.l"
 {
 					*option_quote = '\'';
 					unquoted_option_chars = 0;
@@ -1649,7 +1647,7 @@
 	YY_BREAK
 case 9:
 YY_RULE_SETUP
-#line 224 "psqlscanslash.l"
+#line 222 "psqlscanslash.l"
 {
 					backtick_start_offset = output_buf->len;
 					*option_quote = '`';
@@ -1659,7 +1657,7 @@
 	YY_BREAK
 case 10:
 YY_RULE_SETUP
-#line 231 "psqlscanslash.l"
+#line 229 "psqlscanslash.l"
 {
 					ECHO;
 					*option_quote = '"';
@@ -1669,7 +1667,7 @@
 	YY_BREAK
 case 11:
 YY_RULE_SETUP
-#line 238 "psqlscanslash.l"
+#line 236 "psqlscanslash.l"
 {
 					/* Possible psql variable substitution */
 					if (cur_state->callbacks->get_variable == NULL)
@@ -1709,7 +1707,7 @@
 	YY_BREAK
 case 12:
 YY_RULE_SETUP
-#line 275 "psqlscanslash.l"
+#line 273 "psqlscanslash.l"
 {
 					psqlscan_escape_variable(cur_state, yytext, yyleng,
 											 PQUOTE_SQL_LITERAL);
@@ -1719,7 +1717,7 @@
 	YY_BREAK
 case 13:
 YY_RULE_SETUP
-#line 283 "psqlscanslash.l"
+#line 281 "psqlscanslash.l"
 {
 					psqlscan_escape_variable(cur_state, yytext, yyleng,
 											 PQUOTE_SQL_IDENT);
@@ -1729,14 +1727,14 @@
 	YY_BREAK
 case 14:
 YY_RULE_SETUP
-#line 290 "psqlscanslash.l"
+#line 288 "psqlscanslash.l"
 {
 					psqlscan_test_variable(cur_state, yytext, yyleng);
 				}
 	YY_BREAK
 case 15:
 YY_RULE_SETUP
-#line 294 "psqlscanslash.l"
+#line 292 "psqlscanslash.l"
 {
 					/* Throw back everything but the colon */
 					yyless(1);
@@ -1746,7 +1744,7 @@
 	YY_BREAK
 case 16:
 YY_RULE_SETUP
-#line 301 "psqlscanslash.l"
+#line 299 "psqlscanslash.l"
 {
 					/* Throw back everything but the colon */
 					yyless(1);
@@ -1756,7 +1754,7 @@
 	YY_BREAK
 case 17:
 YY_RULE_SETUP
-#line 308 "psqlscanslash.l"
+#line 306 "psqlscanslash.l"
 {
 					/* Throw back everything but the colon */
 					yyless(1);
@@ -1766,7 +1764,7 @@
 	YY_BREAK
 case 18:
 YY_RULE_SETUP
-#line 315 "psqlscanslash.l"
+#line 313 "psqlscanslash.l"
 {
 					/* Throw back everything but the colon */
 					yyless(1);
@@ -1776,7 +1774,7 @@
 	YY_BREAK
 case 19:
 YY_RULE_SETUP
-#line 322 "psqlscanslash.l"
+#line 320 "psqlscanslash.l"
 {
 					unquoted_option_chars++;
 					ECHO;
@@ -1790,42 +1788,42 @@
 	 */
 case 20:
 YY_RULE_SETUP
-#line 335 "psqlscanslash.l"
+#line 333 "psqlscanslash.l"
 { BEGIN(xslasharg); }
 	YY_BREAK
 case 21:
 YY_RULE_SETUP
-#line 337 "psqlscanslash.l"
+#line 335 "psqlscanslash.l"
 { appendPQExpBufferChar(output_buf, '\''); }
 	YY_BREAK
 case 22:
 YY_RULE_SETUP
-#line 339 "psqlscanslash.l"
+#line 337 "psqlscanslash.l"
 { appendPQExpBufferChar(output_buf, '\n'); }
 	YY_BREAK
 case 23:
 YY_RULE_SETUP
-#line 340 "psqlscanslash.l"
+#line 338 "psqlscanslash.l"
 { appendPQExpBufferChar(output_buf, '\t'); }
 	YY_BREAK
 case 24:
 YY_RULE_SETUP
-#line 341 "psqlscanslash.l"
+#line 339 "psqlscanslash.l"
 { appendPQExpBufferChar(output_buf, '\b'); }
 	YY_BREAK
 case 25:
 YY_RULE_SETUP
-#line 342 "psqlscanslash.l"
+#line 340 "psqlscanslash.l"
 { appendPQExpBufferChar(output_buf, '\r'); }
 	YY_BREAK
 case 26:
 YY_RULE_SETUP
-#line 343 "psqlscanslash.l"
+#line 341 "psqlscanslash.l"
 { appendPQExpBufferChar(output_buf, '\f'); }
 	YY_BREAK
 case 27:
 YY_RULE_SETUP
-#line 345 "psqlscanslash.l"
+#line 343 "psqlscanslash.l"
 {
 					/* octal case */
 					appendPQExpBufferChar(output_buf,
@@ -1834,7 +1832,7 @@
 	YY_BREAK
 case 28:
 YY_RULE_SETUP
-#line 351 "psqlscanslash.l"
+#line 349 "psqlscanslash.l"
 {
 					/* hex case */
 					appendPQExpBufferChar(output_buf,
@@ -1843,13 +1841,13 @@
 	YY_BREAK
 case 29:
 YY_RULE_SETUP
-#line 357 "psqlscanslash.l"
+#line 355 "psqlscanslash.l"
 { psqlscan_emit(cur_state, yytext + 1, 1); }
 	YY_BREAK
 case 30:
 /* rule 30 can match eol */
 YY_RULE_SETUP
-#line 359 "psqlscanslash.l"
+#line 357 "psqlscanslash.l"
 { ECHO; }
 	YY_BREAK
 
@@ -1860,7 +1858,7 @@
 	 */
 case 31:
 YY_RULE_SETUP
-#line 369 "psqlscanslash.l"
+#line 367 "psqlscanslash.l"
 {
 					/* In an inactive \if branch, don't evaluate the command */
 					if (cur_state->cb_passthrough == NULL ||
@@ -1871,7 +1869,7 @@
 	YY_BREAK
 case 32:
 YY_RULE_SETUP
-#line 377 "psqlscanslash.l"
+#line 375 "psqlscanslash.l"
 {
 					/* Possible psql variable substitution */
 					if (cur_state->callbacks->get_variable == NULL)
@@ -1901,7 +1899,7 @@
 	YY_BREAK
 case 33:
 YY_RULE_SETUP
-#line 404 "psqlscanslash.l"
+#line 402 "psqlscanslash.l"
 {
 					psqlscan_escape_variable(cur_state, yytext, yyleng,
 											 PQUOTE_SHELL_ARG);
@@ -1909,7 +1907,7 @@
 	YY_BREAK
 case 34:
 YY_RULE_SETUP
-#line 409 "psqlscanslash.l"
+#line 407 "psqlscanslash.l"
 {
 					/* Throw back everything but the colon */
 					yyless(1);
@@ -1919,7 +1917,7 @@
 case 35:
 /* rule 35 can match eol */
 YY_RULE_SETUP
-#line 415 "psqlscanslash.l"
+#line 413 "psqlscanslash.l"
 { ECHO; }
 	YY_BREAK
 
@@ -1927,7 +1925,7 @@
 /* double-quoted text: copy verbatim, including the double quotes */
 case 36:
 YY_RULE_SETUP
-#line 422 "psqlscanslash.l"
+#line 420 "psqlscanslash.l"
 {
 					ECHO;
 					BEGIN(xslasharg);
@@ -1936,7 +1934,7 @@
 case 37:
 /* rule 37 can match eol */
 YY_RULE_SETUP
-#line 427 "psqlscanslash.l"
+#line 425 "psqlscanslash.l"
 { ECHO; }
 	YY_BREAK
 
@@ -1946,7 +1944,7 @@
 case 38:
 /* rule 38 can match eol */
 YY_RULE_SETUP
-#line 435 "psqlscanslash.l"
+#line 433 "psqlscanslash.l"
 {
 					if (output_buf->len > 0)
 						ECHO;
@@ -1954,7 +1952,7 @@
 	YY_BREAK
 case 39:
 YY_RULE_SETUP
-#line 440 "psqlscanslash.l"
+#line 438 "psqlscanslash.l"
 { ECHO; }
 	YY_BREAK
 
@@ -1962,7 +1960,7 @@
 /* at end of command, eat a double backslash, but not anything else */
 case 40:
 YY_RULE_SETUP
-#line 447 "psqlscanslash.l"
+#line 445 "psqlscanslash.l"
 {
 					cur_state->start_state = YY_START;
 					return LEXRES_OK;
@@ -1971,7 +1969,7 @@
 case 41:
 /* rule 41 can match eol */
 YY_RULE_SETUP
-#line 452 "psqlscanslash.l"
+#line 450 "psqlscanslash.l"
 {
 					yyless(0);
 					cur_state->start_state = YY_START;
@@ -1988,7 +1986,7 @@
 case YY_STATE_EOF(xslashdquote):
 case YY_STATE_EOF(xslashwholeline):
 case YY_STATE_EOF(xslashend):
-#line 460 "psqlscanslash.l"
+#line 458 "psqlscanslash.l"
 {
 					if (cur_state->buffer_stack == NULL)
 					{
@@ -2006,10 +2004,10 @@
 	YY_BREAK
 case 42:
 YY_RULE_SETUP
-#line 475 "psqlscanslash.l"
+#line 473 "psqlscanslash.l"
 YY_FATAL_ERROR( "flex scanner jammed" );
 	YY_BREAK
-#line 2013 "psqlscanslash.c"
+#line 2011 "psqlscanslash.c"
 
 	case YY_END_OF_BUFFER:
 		{
@@ -3125,7 +3123,7 @@
 
 #define YYTABLES_NAME "yytables"
 
-#line 475 "psqlscanslash.l"
+#line 473 "psqlscanslash.l"
 
 
 /* LCOV_EXCL_STOP */