/*
 *	PROGRAM:	JRD Command Oriented Query Language
 *	MODULE:		parse.c
 *	DESCRIPTION:	Statement parser
 *
 * The contents of this file are subject to the Interbase Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy
 * of the License at http://www.Inprise.com/IPL.html
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code was created by Inprise Corporation
 * and its predecessors. Portions created by Inprise Corporation are
 * Copyright (C) Inprise Corporation.
 *
 * All Rights Reserved.
 * Contributor(s): ______________________________________.
 */

#include "firebird.h"
#include "../jrd/ib_stdio.h"
#include <stdlib.h>
#include <string.h>
#define PARSER_MAIN
#include "../qli/dtr.h"
#include "../qli/exe.h"			/* This is only included to suppress a compiler warning */
#include "../qli/parse.h"
#include "../qli/compile.h"
#include "../qli/report.h"
#include "../qli/all_proto.h"
#include "../qli/err_proto.h"
#include "../qli/hsh_proto.h"
#include "../qli/lex_proto.h"
#include "../qli/mov_proto.h"
#include "../qli/parse_proto.h"
#include "../qli/proc_proto.h"

#define SYNTAX_NODE(type,count)	syntax_node (type, count)
#define KEYWORD(kw)		(QLI_token->tok_keyword == kw)
#define INT_CAST		(SYN) (IPTR)

static void check_end(void);
static void command_end(void);
static DBB get_dbb(SYM);
static SYN make_list(LLS);
static NAM make_name(void);
static CON make_numeric_constant(TEXT *, USHORT);
static TEXT *make_string(TEXT *, USHORT);
static SYN negate(SYN);
static KWWORDS next_keyword(void);
static SYN parse_abort(void);
static SYN parse_accept(void);
static SYN parse_add(USHORT *, USHORT *);
static SYN parse_and(USHORT *);
static SYN parse_assignment(void);
static SYN parse_boolean(USHORT *);
static SYN parse_copy(void);
static DBB parse_database(void);
static SYN parse_declare(void);
static SYN parse_define(void);
static SYN parse_def_index(void);
static SYN parse_def_relation(void);
static SYN parse_delete(void);
static SYN parse_drop(void);
static int parse_dtype(USHORT *, USHORT *);
static int parse_dtype_subtype(void);
static SYN parse_edit(void);
static TEXT *parse_edit_string(void);
static SYN parse_erase(void);
static SYN parse_extract(void);
static QLI_FLD parse_field(int);
static SYN parse_field_name(SYN *);
static SYN parse_for(void);
#ifdef PYXIS
static SYN parse_form(void);
#endif
static SYN parse_from(USHORT *, USHORT *);
static SYN parse_function(void);
static TEXT *parse_header(void);
static SYN parse_help(void);
static SYN parse_if(void);
static SYN parse_in(SYN, NOD_T, USHORT);
static SYN parse_insert(void);
static NOD_T parse_join_type(void);
static SYN parse_list_fields(void);
static CON parse_literal(void);
static SYN parse_matches(void);
static void parse_matching_paren(void);
static SYN parse_menu(void);
static SYN parse_modify(void);
static SYN parse_modify_index(void);
static SYN parse_modify_relation(void);
static SYN parse_multiply(USHORT *, USHORT *);
static NAM parse_name(void);
static SYN parse_not(USHORT *);
static int parse_ordinal(void);
static SYN parse_output(void);
static SYN parse_primitive_value(USHORT *, USHORT *);
static SYN parse_print_list(void);
static SYN parse_print(void);
static SYN parse_prompt(void);
static QFL parse_qualified_filter(void);
static QFN parse_qualified_function(void);
static QPR parse_qualified_procedure(void);
static QLI_REL parse_qualified_relation(void);
static SYN parse_ready(NOD_T);
static SYN parse_relational(USHORT *);
static SYN parse_relation(void);
static SYN parse_rename(void);
static SYN parse_repeat(void);
static SYN parse_report(void);
static SYN parse_rse(void);
static SYN parse_select(void);
static SYN parse_set(void);
static SYN parse_shell(void);
static SYN parse_show(void);
static SYN parse_sort(void);
static SYN parse_sql_alter(void);
static SYN parse_sql_create(void);
static int parse_sql_dtype(USHORT *, USHORT *);
static QLI_FLD parse_sql_field(void);
static SYN parse_sql_grant_revoke(USHORT);
static SYN parse_sql_index_create(USHORT, USHORT);
static SYN parse_sql_joined_relation(SYN);
static SYN parse_sql_join_clause(SYN);
static SYN parse_sql_table_create(void);
#ifdef NOT_USED_OR_REPLACED
static SYN parse_sql_view_create(void);
#endif
static SYN parse_sql_relation(void);
static SYN parse_sql_rse(void);
static SYN parse_sql_singleton_select(void);
static SYN parse_sql_subquery(void);
static SYN parse_statement(void);
static SYN parse_statistical(void);
static SYN parse_store(void);
static TEXT *parse_string(void);
static SYM parse_symbol(void);
static void parse_terminating_parens(USHORT *, USHORT *);
static SYN parse_transaction(NOD_T);
static SYN parse_udf_or_field(void);
static SYN parse_update(void);
static SYN parse_value(USHORT *, USHORT *);
static int potential_rse(void);
static QLI_REL resolve_relation(SYM, SYM);
static SYN syntax_node(NOD_T, USHORT);
static int test_end(void);

typedef struct {
	SLONG gds_quad_high;
	ULONG gds_quad_low;
} gds_quad;

/* 
The following flags are: 

	sql_flag	indicates whether we are parsing in a SQL environment.  
			The flag is used to turn off automatic end-of-command
			recognition.

	else_count	indicates the depth of if/then/else's

	sw_report 	indicates whether we're in a report statement

	sw_statement	indicates that we're actively parsing a statement/command

	sw_sql_view	indicates parsing a SQL VIEW, so restrict the select.
*/

static USHORT sql_flag, else_count, sw_report, sw_statement, sw_sql_view;
static SSHORT function_count;	/* indicates the depth of UDF calls */

static struct nod_types {
	KWWORDS nod_t_keyword;
	NOD_T nod_t_node;
	NOD_T nod_t_rpt_node;
	NOD_T nod_t_sql_node;
} statisticals[] = {
	{ KW_MAX, nod_max, nod_rpt_max, nod_agg_max },
	{ KW_MIN, nod_min, nod_rpt_min, nod_agg_min },
	{ KW_COUNT, nod_count, nod_rpt_count, nod_agg_count },
	{ KW_AVERAGE, nod_average, nod_rpt_average, nod_agg_average },
	{ KW_TOTAL, nod_total, nod_rpt_total, nod_agg_total },
	{ KW_none, nod_any, nod_any, nod_any }
};

static NOD_T relationals[] = {
	nod_eql, nod_neq, nod_leq, nod_lss, nod_gtr, nod_geq, nod_containing,
	nod_like, nod_starts, nod_missing, nod_between, nod_sleuth,
	nod_matches, nod_and, nod_or, nod_not, nod_any, nod_unique, (NOD_T) 0
};


SYN PARQ_parse(void)
{
/**************************************
 *
 *	P A R Q _ p a r s e
 *
 **************************************
 *
 * Functional description
 *	Parse a single statement or command.
 *
 **************************************/
	LLS stack;
	SYN node;

	sql_flag = else_count = sw_report = 0;
	sw_statement = TRUE;

	switch (next_keyword()) {
	case KW_COMMIT:
		return parse_transaction(nod_commit);

	case KW_COPY:
		return parse_copy();

	case KW_EXIT:
		return SYNTAX_NODE(nod_exit, 0);

	case KW_EXTRACT:
		return parse_extract();

	case KW_QUIT:
		return SYNTAX_NODE(nod_quit, 0);

	case KW_DELETE:
	case KW_DROP:
		if (node = parse_drop())
			return node;
		node = parse_delete();
		check_end();
		if (!MATCH(KW_THEN))
			return node;
		stack = NULL;
		LLS_PUSH(node, &stack);
		LLS_PUSH(parse_statement(), &stack);
		return make_list(stack);

	case KW_DEFINE:
		return parse_define();

	case KW_CREATE:
		return parse_sql_create();

	case KW_ALTER:
		return parse_sql_alter();

	case KW_EDIT:
		return parse_edit();

	case KW_FINISH:
		return parse_transaction(nod_finish);

	case KW_GRANT:
		return parse_sql_grant_revoke(nod_sql_grant);

	case KW_HELP:
		return parse_help();

	case KW_PREPARE:
		return parse_transaction(nod_prepare);

	case KW_READY:
		return parse_ready(nod_ready);

	case KW_RENAME:
		return parse_rename();

	case KW_REVOKE:
		return parse_sql_grant_revoke(nod_sql_revoke);

	case KW_ROLLBACK:
		return parse_transaction(nod_rollback);

	case KW_SET:
		return parse_set();

	case KW_SHELL:
		return parse_shell();

	case KW_SHOW:
		return parse_show();
	}

	return parse_statement();
}


int PAR_match( KWWORDS keyword)
{
/**************************************
 *
 *	P A R _ m a t c h
 *
 **************************************
 *
 * Functional description
 *	Test the current token for a particular keyword.
 *	If so, advance the token stream.
 *
 **************************************/
	SYM symbol;

	if (KEYWORD(keyword)) {
		PAR_token();
		return TRUE;
	}

	for (symbol = QLI_token->tok_symbol; symbol; symbol = symbol->sym_homonym)
		if (symbol->sym_type == SYM_keyword &&
			symbol->sym_keyword == (int) keyword) {
			PAR_token();
			return TRUE;
		}

	return FALSE;
}


void PAR_real(void)
{
/**************************************
 *
 *	P A R _ r e a l
 *
 **************************************
 *
 * Functional description
 *	Get a real (not EOL) token.
 *
 *	If the token is an end of line, get the next token.
 *      If the next token is a colon, start reading the
 *      procedure.
 *
 **************************************/
	DBB database;

	while ((QLI_token->tok_type == tok_eol) || KEYWORD(KW_continuation))
		LEX_token();

	if (MATCH(KW_COLON)) {
		database = parse_database();
		PRO_invoke(database, QLI_token->tok_string);
	}
}


void PAR_real_token(void)
{
/**************************************
 *
 *	P A R _ r e a l _ t o k e n
 *
 **************************************
 *
 * Functional description
 *	Composition of PAR_token followed by PAR_real.
 *
 **************************************/

	PAR_token();
	PAR_real();
}


void PAR_token(void)
{
/**************************************
 *
 *	P A R _ t o k e n
 *
 **************************************
 *
 * Functional description
 *	Get the next token.  
 *	If it's a procedure invocation, handle it
 *      or complain and get rid of the evidence.
 *
 **************************************/
	DBB database;

	for (;;) {
		LEX_token();
		if (!(KEYWORD(KW_continuation)) &&
			!(sw_statement && QLI_semi && QLI_token->tok_type == tok_eol))
			break;
	}

	if (MATCH(KW_COLON)) {
		if (!QLI_databases) {
			ERRQ_error_format(159, NULL, NULL, NULL, NULL, NULL);	/* Msg159 no databases are ready */
			ERRQ_pending();
			LEX_token();
		}
		else {
			database = parse_database();
			PRO_invoke(database, QLI_token->tok_string);
		}
	}
}


static void check_end(void)
{
/**************************************
 *
 *	c h e c k _ e n d
 *
 **************************************
 *
 * Functional description
 *	Check for end of statement.  If it isn't complain bitterly.
 *
 **************************************/

	if (QLI_token->tok_type == tok_eol ||
		KEYWORD(KW_SEMI) ||
		KEYWORD(KW_THEN) || (else_count && KEYWORD(KW_ELSE))) {
		sw_statement = FALSE;
		return;
	}

	SYNTAX_ERROR(161);			/* Msg161 end of statement */
}


static void command_end(void)
{
/**************************************
 *
 *	c o m m a n d _ e n d
 *
 **************************************
 *
 * Functional description
 *	Check for end of command.  If it isn't complain bitterly.
 *
 **************************************/

	if (QLI_token->tok_type == tok_eol || KEYWORD(KW_SEMI)) {
		sw_statement = FALSE;
		return;
	}

	SYNTAX_ERROR(162);			/* Msg162 end of command */
}


static DBB get_dbb( SYM symbol)
{
/**************************************
 *
 *	g e t _ d b b
 *
 **************************************
 *
 * Functional description
 *	Find a database block from a symbol
 *	or its homonyms.
 *
 **************************************/

	for (; symbol; symbol = symbol->sym_homonym)
		if (symbol->sym_type == SYM_database)
			return (DBB) symbol->sym_object;

	return NULL;
}


static SYN make_list( LLS stack)
{
/**************************************
 *
 *	m a k e _ l i s t
 *
 **************************************
 *
 * Functional description
 *	Dump a stack of junk into a list node.  Best count
 *	them first.
 *
 **************************************/
	SYN node, *ptr;
	LLS temp;
	USHORT count;

	temp = stack;
	count = 0;

	while (temp) {
		count++;
		temp = temp->lls_next;
	}

	node = SYNTAX_NODE(nod_list, count);
	ptr = &node->syn_arg[count];

	while (stack)
		*--ptr = (SYN) LLS_POP(&stack);

	return node;
}


static NAM make_name(void)
{
/**************************************
 *
 *	m a k e _ n a m e
 *
 **************************************
 *
 * Functional description
 *	Generate a unique name for something
 *	(like a database) that needs one.
 *
 **************************************/
	NAM name;
	SSHORT l, i;
	TEXT c, *p, *q, string[32];

	for (i = 0; i < 1000; i++) {
		sprintf(string, "QLI_%d", i);
		if (i < 10)
			l = 5;
		else
			l = (i < 100) ? 6 : 7;
		if (!(HSH_lookup(string, l)))
			break;
	}

	name = (NAM) ALLOCDV(type_nam, l);
	name->nam_length = l;
	p = name->nam_string;
	q = string;

	if (l)
		do {
			c = *q++;
			*p++ = UPPER(c);
		} while (--l);

	return name;
}


static CON make_numeric_constant( TEXT * string, USHORT length)
{
/**************************************
 *
 *	m a k e _ n u m e r i c _ c o n s t a n t
 *
 **************************************
 *
 * Functional description
 *	Build a constant block for a numeric
 *	constant.  Numeric constants are normally
 *	stored as long words, but especially large
 *	ones become text.  They ought to become
 *      double precision, one would think, but they'd
 *      have to be VAX style double precision which is
 *      more pain than gain.
 *
 **************************************/
	CON constant;
	TEXT *p;
	USHORT l;

	p = string;
	l = length;

/* If there are a reasonable number of digits, convert to binary */

	if (length < 9) {
		constant = (CON) ALLOCDV(type_con, sizeof(SLONG));
		constant->con_desc.dsc_dtype = dtype_long;
		constant->con_desc.dsc_length = sizeof(SLONG);
		constant->con_desc.dsc_address = constant->con_data;
		constant->con_desc.dsc_scale =
			MOVQ_decompose(string, length, (SLONG*) constant->con_data);
	}
	else {
		++length;
		constant = (CON) ALLOCDV(type_con, length);
		constant->con_desc.dsc_dtype = dtype_text;
		constant->con_desc.dsc_length = length;
		constant->con_desc.dsc_address = constant->con_data;
		p = (TEXT *) constant->con_desc.dsc_address;
		*p++ = '0';
		while (--length)
			*p++ = *string++;
	}

	return constant;
}


static TEXT *make_string( TEXT * address, USHORT length)
{
/**************************************
 *
 *	m a k e _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Copy a string into a temporary string block.
 *
 **************************************/
	STR string;
	TEXT *p;

	string = (STR) ALLOCDV(type_str, length);
	p = string->str_data;
	if (length)
		do
			*p++ = *address++;
		while (--length);

	return string->str_data;
}


static SYN negate( SYN expr)
{
/**************************************
 *
 *	n e g a t e
 *
 **************************************
 *
 * Functional description
 *	Build negation of expression.
 *
 **************************************/
	SYN node;

	node = SYNTAX_NODE(nod_not, 1);
	node->syn_arg[0] = expr;

	return node;
}


static KWWORDS next_keyword(void)
{
/**************************************
 *
 *	n e x t _ k e y w o r d
 *
 **************************************
 *
 * Functional description
 *	Get a real token and return the keyword number.
 *
 **************************************/
	SYM symbol;

	PAR_real();

	for (symbol = QLI_token->tok_symbol; symbol; symbol = symbol->sym_homonym)
		if (symbol->sym_type == SYM_keyword)
			return (KWWORDS) symbol->sym_keyword;

	return KW_none;
}


static SYN parse_abort(void)
{
/**************************************
 *
 *	p a r s e _ a b o r t
 *
 **************************************
 *
 * Functional description
 *	Parse an ABORT statement.
 *
 **************************************/
	SYN node;

	PAR_token();
	node = SYNTAX_NODE(nod_abort, 1);

	if (KEYWORD(KW_SEMI))
		node->syn_count = 0;
	else
		node->syn_arg[0] = parse_value(0, 0);

	return node;
}


static SYN parse_accept(void)
{
/**************************************
 *
 *	p a r s e _ a c c e p t
 *
 **************************************
 *
 * Functional description
 *	Parse form update statement.
 *
 **************************************/
	IBERROR(484);				/* FORMs not supported */
	return 0;
}


static SYN parse_add( USHORT * paren_count, USHORT * bool_flag)
{
/**************************************
 *
 *	p a r s e _ a d d
 *
 **************************************
 *
 * Functional description
 *	Parse the lowest precedence operatrs, ADD and SUBTRACT.
 *
 **************************************/
	SYN node, arg;
	NOD_T operatr;

	node = parse_multiply(paren_count, bool_flag);

	while (TRUE) {
		if (MATCH(KW_PLUS))
			operatr = nod_add;
		else if (MATCH(KW_MINUS))
			operatr = nod_subtract;
		else
			return node;
		arg = node;
		node = SYNTAX_NODE(operatr, 2);
		node->syn_arg[0] = arg;
		node->syn_arg[1] = parse_multiply(paren_count, bool_flag);
	}
}


static SYN parse_and( USHORT * paren_count)
{
/**************************************
 *
 *	p a r s e _ a n d
 *
 **************************************
 *
 * Functional description
 *	Parse an AND expression.
 *
 **************************************/
	SYN expr, node;

	expr = parse_not(paren_count);

/*
while (*paren_count && KEYWORD (KW_RIGHT_PAREN))
    {
    parse_matching_paren();
    (*paren_count)--;
    }
*/

	if (!MATCH(KW_AND))
		return expr;

	node = SYNTAX_NODE(nod_and, 2);
	node->syn_arg[0] = expr;
	node->syn_arg[1] = parse_and(paren_count);

	return node;
}


static SYN parse_assignment(void)
{
/**************************************
 *
 *	p a r s e _ a s s i g n m e n t
 *
 **************************************
 *
 * Functional description
 *	Parse an assignment statement (or give an error).  The
 *	assignment statement can be either a simple assignment
 *	(field = value) or a restructure (relation = rse).  
 *	If the assignment operatr is missing, 
 *	generate an "expected statement" error.
 *
 **************************************/
	SYN node, field;
	QLI_REL relation;
	NAM name, name2;

	node = SYNTAX_NODE(nod_assign, s_asn_count);
	node->syn_arg[s_asn_to] = parse_field_name(&field);
	name = (NAM) field->syn_arg[0];

/* If the next token is an equals sign, the statement really is an
   assignment, and we're off the hook. */

	if (!MATCH(KW_EQUALS))
		ERRQ_print_error(156, name->nam_string, NULL, NULL, NULL, NULL);	/* Msg156 expected statement, encountered %s */

/* See if the "field name" is really a relation reference.  If so,
   turn the assignment into a restructure. */

	if (field->syn_count == 1)
		relation = resolve_relation(0, name->nam_symbol);
	else if (field->syn_count == 2 && name->nam_symbol) {
		name2 = (NAM) field->syn_arg[1];
		relation = resolve_relation(name->nam_symbol, name2->nam_symbol);
	}
	else
		relation = NULL;

	if (relation) {
		ALL_release((FRB) field);
		node->syn_type = nod_restructure;
		node->syn_arg[s_asn_to] = field =
			SYNTAX_NODE(nod_relation, s_rel_count);
		field->syn_arg[s_rel_relation] = (SYN) relation;
		node->syn_arg[s_asn_from] = parse_rse();
	}
	else
		node->syn_arg[s_asn_from] = parse_value(0, 0);

	return node;
}


static SYN parse_boolean( USHORT * paren_count)
{
/**************************************
 *
 *	p a r s e _ b o o l e a n
 *
 **************************************
 *
 * Functional description
 *	Parse a general boolean expression.  By precedence, handle an OR
 *	here.
 *
 **************************************/
	SYN expr, node;
	USHORT local_count;

	if (!paren_count) {
		local_count = 0;
		paren_count = &local_count;
	}

	expr = parse_and(paren_count);

/*
while (*paren_count && KEYWORD (KW_RIGHT_PAREN))
    {
    parse_matching_paren();
    (*paren_count)--;
    }
*/

	if (!MATCH(KW_OR)) {
		parse_terminating_parens(paren_count, &local_count);
		return expr;
	}

	node = SYNTAX_NODE(nod_or, 2);
	node->syn_arg[0] = expr;
	node->syn_arg[1] = parse_boolean(paren_count);
	parse_terminating_parens(paren_count, &local_count);

	return node;
}


static SYN parse_copy(void)
{
/**************************************
 *
 *	p a r s e _ c o p y
 *
 **************************************
 *
 * Functional description
 *	Parse a copy command, which copies
 *	one procedure to another.
 *
 **************************************/
	SYN node;

	PAR_real_token();

	if (MATCH(KW_PROCEDURE)) {
		node = SYNTAX_NODE(nod_copy_proc, 2);
		node->syn_arg[0] = (SYN) parse_qualified_procedure();
		MATCH(KW_TO);
		node->syn_arg[1] = (SYN) parse_qualified_procedure();
		return node;
	}

	ERRQ_print_error(157, QLI_token->tok_string, NULL, NULL, NULL, NULL);	/* Msg157 Expected PROCEDURE encountered %s */
	return NULL;
}


static DBB parse_database(void)
{
/**************************************
 *
 *	p a r s e _ d a t a b a s e
 *
 **************************************
 *
 * Functional description
 *	Pick up a database for a meta-data or
 *	procedure update.  Return NULL if the
 *	token is not a database name.
 *
 **************************************/
	DBB database;
	SYM db_symbol;


	if ((db_symbol = QLI_token->tok_symbol)
		&& db_symbol->sym_type == SYM_database) {
		database = (DBB) db_symbol->sym_object;
		PAR_real_token();
		if (!MATCH(KW_DOT))
			SYNTAX_ERROR(158);	/* Msg158 period in qualified name */
		PAR_real();
		return database;
	}

	if (!QLI_databases)
		IBERROR(159);			/* Msg159 no databases are ready */

	return NULL;
}


static SYN parse_declare(void)
{
/**************************************
 *
 *	p a r s e _ d e c l a r e
 *
 **************************************
 *
 * Functional description
 *	Parse a variable  declaration.
 *
 **************************************/
	SYN node, field_node;
	SYM name, query_name;
	QLI_FLD global_variable;
	USHORT dtype, length, scale;
	SSHORT sub_type, sub_type_missing;
	TEXT *edit_string, *query_header;
	QLI_REL relation;
	NAM db_name, rel_name;

	PAR_token();
	PAR_real();

	dtype = length = scale = 0;
	sub_type = 0;
	sub_type_missing = 1;
	field_node = NULL;
	relation = NULL;
	query_name = NULL;
	edit_string = query_header = NULL;
	name = parse_symbol();

/*if (global_flag) PAR_real();*/

	while (!KEYWORD(KW_SEMI) && !KEYWORD(KW_COMMA)) {
		PAR_real();
		switch (QLI_token->tok_keyword) {
		case KW_SHORT:
		case KW_LONG:
		case KW_FLOAT:
		case KW_DOUBLE:
		case KW_DATE:
		case KW_CHAR:
		case KW_VARYING:
			if (dtype)
				SYNTAX_ERROR(164);	/* Msg164 variable definition clause */
			dtype = parse_dtype(&length, &scale);
			break;

		case KW_BLOB:
			IBERROR(160);		/* Msg160 blob variables are not supported */
			break;

		case KW_SUB_TYPE:
			sub_type = parse_dtype_subtype();
			sub_type_missing = 0;
			break;

		case KW_EDIT_STRING:
			PAR_token();
			if (QLI_token->tok_type != tok_quoted)
				SYNTAX_ERROR(163);	/* Msg163 quoted edit string */
			edit_string =
				make_string(QLI_token->tok_string + 1,
							QLI_token->tok_length - 2);
			PAR_token();
			break;

		case KW_QUERY_NAME:
			PAR_token();
			MATCH(KW_IS);
			if (QLI_token->tok_type != tok_ident)
				SYNTAX_ERROR(199);	/* Msg199 identifier */
			query_name = parse_symbol();
			break;

		case KW_QUERY_HEADER:
			PAR_token();
			query_header = parse_header();
			break;

		case KW_BASED:
			PAR_token();
			MATCH(KW_ON);
			field_node = parse_field_name(0);
			break;

		default:
			SYNTAX_ERROR(164);	/* Msg164 variable definition clause */
			break;
		}
	}

	if (field_node && field_node->syn_count == 3) {
		db_name = (NAM) field_node->syn_arg[0];
		rel_name = (NAM) field_node->syn_arg[1];
		if (!db_name->nam_symbol)
			ERRQ_print_error(165, db_name->nam_string, NULL, NULL, NULL, NULL);	/* Msg165 %s is not a database */
		if (!
			(relation =
			 resolve_relation(db_name->nam_symbol,
							  rel_name->nam_symbol))) ERRQ_print_error(166,
																	   rel_name->
																	   nam_string,
																	   db_name->
																	   nam_string, NULL, NULL, NULL);	/* Msg166 %s is not a relation in database %s */
	}

	if (!dtype && !field_node)
		SYNTAX_ERROR(167);		/* Msg167 variable data type */
	if (field_node && (dtype || length || scale))
		IBERROR(168);			/* Msg168 no datatype may be specified for a variable based on a field */

	node = SYNTAX_NODE(nod_declare, 2);
	global_variable = (QLI_FLD) ALLOCDV(type_fld, length);
	node->syn_arg[0] = (SYN) global_variable;
	global_variable->fld_name = name;
	global_variable->fld_dtype = dtype;
	global_variable->fld_scale = scale;
	global_variable->fld_sub_type = sub_type;
	global_variable->fld_sub_type_missing = sub_type_missing;
	global_variable->fld_length = length;
	global_variable->fld_edit_string = edit_string;
	global_variable->fld_query_name = query_name;
	global_variable->fld_query_header = query_header;
	global_variable->fld_relation = relation;

	node->syn_arg[1] = field_node;

	check_end();

	return node;
}


static SYN parse_define(void)
{
/**************************************
 *
 *	p a r s e _ d e f i n e
 *
 **************************************
 *
 * Functional description
 *	Parse a DEFINE command.  
 *	There are, of course, a whole class of define commands.  
 *
 **************************************/
	SYN node;

	PAR_real_token();

	if (MATCH(KW_PROCEDURE)) {
		PAR_real();
		node = SYNTAX_NODE(nod_define, 1);
		node->syn_arg[0] = (SYN) parse_qualified_procedure();
		return node;
	}

	if (MATCH(KW_FIELD)) {
		PAR_real();
		node = SYNTAX_NODE(nod_def_field, 2);
		node->syn_arg[0] = (SYN) parse_database();
		node->syn_arg[1] = (SYN) parse_field(TRUE);
		return node;
	}

	if (MATCH(KW_RELATION))
		return parse_def_relation();

	if (KEYWORD(KW_DATABASE))
		return parse_ready(nod_def_database);

	if (MATCH(KW_INDEX))
		return parse_def_index();

	SYNTAX_ERROR(169);			/* Msg169 object type for DEFINE */
	return NULL;
}


static SYN parse_def_index(void)
{
/**************************************
 *
 *	p a r s e _ d e f _ i n d e x
 *
 **************************************
 *
 * Functional description
 *	Parse a DEFINE INDEX command.  
 *
 **************************************/
	LLS stack;
	SYN node;

	PAR_real();
	node = SYNTAX_NODE(nod_def_index, s_dfi_count);
	node->syn_arg[s_dfi_name] = (SYN) parse_symbol();
	PAR_real();
	MATCH(KW_FOR);

	if (!(node->syn_arg[s_dfi_relation] = (SYN) parse_qualified_relation()))
		SYNTAX_ERROR(170);		/* Msg170 relation name */

	PAR_real();

	while (TRUE) {
		PAR_real();
		if (MATCH(KW_UNIQUE))
			node->syn_flags |= s_dfi_flag_unique;
		else if (MATCH(KW_DUPLICATE))
			node->syn_flags &= ~s_dfi_flag_unique;
		else if (MATCH(KW_ACTIVE))
			node->syn_flags &= ~s_dfi_flag_inactive;
		else if (MATCH(KW_INACTIVE))
			node->syn_flags |= s_dfi_flag_inactive;
		else if (MATCH(KW_DESCENDING))
			node->syn_flags |= s_dfi_flag_descending;
		else if (MATCH(KW_ASCENDING))
			node->syn_flags &= ~s_dfi_flag_descending;
		else
			break;
	}

	stack = NULL;
	for (;;) {
		LLS_PUSH(parse_name(), &stack);
		if (!MATCH(KW_COMMA))
			break;
	}
	node->syn_arg[s_dfi_fields] = make_list(stack);

	command_end();
	return node;
}


static SYN parse_def_relation(void)
{
/**************************************
 *
 *	p a r s e _ d e f _ r e l a t i o n 
 *
 **************************************
 *
 * Functional description 
 *	Parse a DEFINE RELATION command,
 *	which include the field definitions
 *	for a primitive relation definition
 *	or it may just reference another relatio
 *	whose field definitions we will copy.  
 *
 **************************************/
	QLI_REL relation;
	QLI_FLD field, *ptr;
	SYN node;

	PAR_real();
	node = SYNTAX_NODE(nod_def_relation, 2);
	relation = (QLI_REL) ALLOCD(type_rel);
	node->syn_arg[0] = (SYN) relation;
	relation->rel_database = parse_database();
	relation->rel_symbol = parse_symbol();
	PAR_real();

	if (MATCH(KW_BASED)) {
		PAR_real();
		MATCH(KW_ON);
		PAR_real();
		MATCH(KW_RELATION);
		PAR_real();
		relation = (QLI_REL) ALLOCD(type_rel);
		node->syn_arg[1] = (SYN) relation;
		relation->rel_database = parse_database();
		relation->rel_symbol = parse_symbol();
	}
	else {
		node->syn_arg[1] = NULL;
		ptr = &relation->rel_fields;
		for (;;) {
			MATCH(KW_ADD);
			PAR_real();
			MATCH(KW_FIELD);
			*ptr = field = parse_field(FALSE);
			ptr = &field->fld_next;
			if (KEYWORD(KW_SEMI))
				break;
			if (!MATCH(KW_COMMA))
				SYNTAX_ERROR(171);	/* Msg171 comma between field definitions */
		}
	}

	command_end();
	return node;
}


static SYN parse_delete(void)
{
/**************************************
 *
 *	p a r s e _ d e l e t e
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL DELETE statement. 
 *	(DELETE PROCEDURE is parsed in parse_drop)
 *
 **************************************/
	SYN node, rse;

	++sql_flag;

	if (!MATCH(KW_FROM))
		SYNTAX_ERROR(172);		/* Msg172 FROM */

	node = SYNTAX_NODE(nod_erase, s_era_count);
	node->syn_arg[s_era_rse] = rse =
		SYNTAX_NODE(nod_rse, (int) s_rse_count + 2);
	rse->syn_count = 1;
	rse->syn_arg[s_rse_count] = parse_sql_relation();

/* Pick up boolean, if present */

	if (MATCH(KW_WITH))
		rse->syn_arg[s_rse_boolean] = parse_boolean(0);

	--sql_flag;

	return node;
}


static SYN parse_drop(void)
{
/**************************************
 *
 *	p a r s e _ d r o p
 *
 **************************************
 *
 * Functional description
 *	Parse a DDL DROP/DELETE command.  It it isn't one,
 *	just return NULL.
 *
 **************************************/
	SYN node;
	NOD_T type;
	DBB database;
	SSHORT l;
	TEXT *p, *q;

	PAR_real_token();

	if (MATCH(KW_RELATION) || MATCH(KW_VIEW) || MATCH(KW_TABLE)) {
		node = SYNTAX_NODE(nod_del_relation, 1);
		if (!(node->syn_arg[0] = (SYN) parse_qualified_relation()))
			SYNTAX_ERROR(173);	/* Msg173 relation or view name */
		return node;
	}

	switch (QLI_token->tok_keyword) {
	case KW_PROCEDURE:
		type = nod_delete_proc;
		break;

	case KW_INDEX:
		type = nod_del_index;
		break;

	case KW_FIELD:
		type = nod_del_field;
		break;

	case KW_DATABASE:
		LEX_filename();
		if (!(l = QLI_token->tok_length))
			ERRQ_error(429, NULL, NULL, NULL, NULL, NULL);	/* Msg429 database file name required on DROP DATABASE */
		q = QLI_token->tok_string;
		if (QLI_token->tok_type == tok_quoted) {
			l -= 2;
			q++;
		}
		database = (DBB) ALLOCDV(type_dbb, l);
		database->dbb_filename_length = l;
		p = database->dbb_filename;
		do
			*p++ = *q++;
		while (--l);
		PAR_token();

		/* parse an optional user name and password if given */

		for (;;) {
			if (MATCH(KW_USER))
				database->dbb_user = parse_literal();
			else if (MATCH(KW_PASSWORD))
				database->dbb_password = parse_literal();
			else
				break;
		}

		command_end();
		node = SYNTAX_NODE(nod_del_database, 1);
		node->syn_arg[0] = (SYN) database;
		return node;

	default:
		return NULL;
	}

	PAR_real_token();
	node = SYNTAX_NODE(type, 2);

	if (type == nod_delete_proc)
		node->syn_arg[0] = (SYN) parse_qualified_procedure();
	else {
		node->syn_arg[0] = (SYN) parse_database();
		node->syn_arg[1] = (SYN) parse_name();
	}

	return node;
}


static int parse_dtype( USHORT * length, USHORT * scale)
{
/**************************************
 *
 *	p a r s e _ d t y p e
 *
 **************************************
 *
 * Functional description
 *	Parse a datatype clause.
 *
 **************************************/
	KWWORDS keyword;
	USHORT dtype, l;

	keyword = QLI_token->tok_keyword;
	PAR_token();
	*scale = 0;

	switch (keyword) {
	case KW_SHORT:
		*length = sizeof(SSHORT);
		dtype = dtype_short;
		break;

	case KW_LONG:
		*length = sizeof(SLONG);
		dtype = dtype_long;
		break;

	case KW_FLOAT:
		*length = sizeof(float);
		return dtype_real;

	case KW_DOUBLE:
		*length = sizeof(double);
		return dtype_double;

	case KW_DATE:
		*length = sizeof(gds_quad);
		return dtype_timestamp;

	case KW_CHAR:
		dtype = dtype_text;
		break;

	case KW_VARYING:
		dtype = dtype_varying;
		break;

	case KW_BLOB:
		*length = sizeof(gds_quad);
		return dtype_blob;
	}

	if (dtype == dtype_short || dtype == dtype_long) {
		if (MATCH(KW_SCALE)) {
			l = (MATCH(KW_MINUS)) ? TRUE : FALSE;
			*scale = parse_ordinal();
			if (l)
				*scale = -(*scale);
		}
	}
	else if (dtype == dtype_text || dtype == dtype_varying) {
		if (!MATCH(KW_L_BRCKET) && !MATCH(KW_LT))
			SYNTAX_ERROR(174);	/* Msg174 "[" */

		l = parse_ordinal();
		if (dtype == dtype_varying)
			l += sizeof(SSHORT);
		*length = l;

		if (!MATCH(KW_R_BRCKET) && !MATCH(KW_GT))
			SYNTAX_ERROR(175);	/* Msg175 "]" */

	}

	return dtype;
}


static int parse_dtype_subtype(void)
{
/**************************************
 *
 *	p a r s e _ d t y p e _ s u b t y p e
 *
 **************************************
 *
 * Functional description
 *	Parse a sub-type definition, which can be any of
 *	SUB_TYPE {IS} [TEXT | FIXED | <n>]
 *
 *	Returns the numeric subtype value,
 *
 **************************************/
	int sign;

/* grab KW_SUB_TYPE */

	PAR_token();
	MATCH(KW_IS);
	if (MATCH(KW_TEXT) || MATCH(KW_FIXED))
		return 1;

	sign = (MATCH(KW_MINUS)) ? -1 : 1;

	return (sign * parse_ordinal());
}


static SYN parse_edit(void)
{
/**************************************
 *
 *	p a r s e _ e d i t 
 *
 **************************************
 *
 * Functional description
 *	Parse an edit statement which can
 *	be any of  EDIT <procedure_name>
 *	           EDIT <n> 
 *		   EDIT <*>
 *		   EDIT 
 *
 *
 **************************************/
	SYN node;
	NOD_T type;
	int l;
	LLS start, stop, statement_list;

	LEX_token();

/*
 * edit previous statements.  The top of the statment list
 * is this edit command, which we conveniently ignore.
 */

	if (KEYWORD(KW_SEMI) ||
		(QLI_token->tok_type == tok_number) || (KEYWORD(KW_ASTERISK))) {
		if (!(statement_list = LEX_statement_list()))
			IBERROR(176);		/* Msg176 No statements issued yet */

		if (MATCH(KW_ASTERISK))
			LEX_edit((SLONG) 0, (SLONG) statement_list->lls_object);
		else {
			if (KEYWORD(KW_SEMI))
				l = 1;
			else if (QLI_token->tok_type == tok_number)
				l = parse_ordinal();

			for (start = stop = statement_list;
				 l && start->lls_next; l--, start = start->lls_next);
			command_end();
			LEX_edit((SLONG) start->lls_object, (SLONG) stop->lls_object);
		}
	}
#ifdef PYXIS
	else if (MATCH(KW_FORM))
		IBERROR(484);			/* FORMs not supported */
#endif
	else {
		type = nod_edit_proc;
		node = SYNTAX_NODE(type, 2);
		node->syn_arg[0] = (SYN) parse_qualified_procedure();
		command_end();
		return node;
	}

	return NULL;
}


static TEXT *parse_edit_string(void)
{
/**************************************
 *
 *	p a r s e _ e d i t _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Look for and parse a clause of the form:
 *	    USING <edit_string>
 *
 **************************************/

	if (!KEYWORD(KW_USING))
		return NULL;

	LEX_edit_string();

	return parse_string();
}


static SYN parse_erase(void)
{
/**************************************
 *
 *	p a r s e _ e r a s e
 *
 **************************************
 *
 * Functional description
 *	Parse an ERASE statement.  Erase can be any of the
 *	following:
 *
 *	ERASE [ALL] [OF <rse>]
 *
 **************************************/
	SYN node;

	PAR_token();
	MATCH(KW_ALL);
	MATCH(KW_OF);
	node = SYNTAX_NODE(nod_erase, s_era_count);

	if (MATCH(KW_ALL) || potential_rse()) {
		MATCH(KW_OF);
		node->syn_arg[s_era_rse] = parse_rse();
	}

	return node;
}


static SYN parse_extract(void)
{
/**************************************
 *
 *	p a r s e _ e x t r a c t
 *
 **************************************
 *
 * Functional description
 *	Parse a procedure extract statement.  Syntax is:
 *
 *	EXTRACT [ON <file>] proc [, ...] [ON <file> ]
 *
 **************************************/
	LLS stack;
	SYN node;

	PAR_real_token();
	node = SYNTAX_NODE(nod_extract, 2);
	node->syn_arg[1] = parse_output();

	if (!MATCH(KW_ALL)) {
		stack = NULL;
		for (;;) {
			LLS_PUSH(parse_qualified_procedure(), &stack);
			if (!MATCH(KW_COMMA))
				break;
		}
		node->syn_arg[0] = make_list(stack);
	}

	if (!node->syn_arg[1] && !(node->syn_arg[1] = parse_output()))
		SYNTAX_ERROR(177);		/* Msg177 "ON or TO" */

	return node;
}


static QLI_FLD parse_field( int global_flag)
{
/**************************************
 *
 *	p a r s e _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Parse a field description.
 *
 **************************************/
	QLI_FLD field;
	SYM name, query_name, based_on;
	USHORT dtype, length, scale;
	SSHORT sub_type, sub_type_missing;
	TEXT *edit_string, *query_header;

	PAR_real();

	dtype = length = scale = 0;
	sub_type = 0;
	sub_type_missing = 1;
	query_name = based_on = NULL;
	edit_string = query_header = NULL;
	name = parse_symbol();

	if (global_flag)
		PAR_real();

	while (!KEYWORD(KW_SEMI) && !KEYWORD(KW_COMMA)) {
		PAR_real();
		switch (QLI_token->tok_keyword) {
		case KW_SHORT:
		case KW_LONG:
		case KW_FLOAT:
		case KW_DOUBLE:
		case KW_DATE:
		case KW_CHAR:
		case KW_VARYING:
		case KW_BLOB:
			if (dtype)
				SYNTAX_ERROR(179);	/* Msg179 field definition clause */
			dtype = parse_dtype(&length, &scale);
			break;

		case KW_SUB_TYPE:
			sub_type = parse_dtype_subtype();
			sub_type_missing = 0;
			break;

		case KW_EDIT_STRING:
			PAR_token();
			if (QLI_token->tok_type != tok_quoted)
				SYNTAX_ERROR(178);	/* Msg178 quoted edit string */
			edit_string =
				make_string(QLI_token->tok_string + 1,
							QLI_token->tok_length - 2);
			PAR_token();
			break;

		case KW_QUERY_NAME:
			PAR_token();
			MATCH(KW_IS);
			if (QLI_token->tok_type != tok_ident)
				SYNTAX_ERROR(199);	/* Msg199 identifier */
			query_name = parse_symbol();
			break;

		case KW_BASED:
			PAR_token();
			MATCH(KW_ON);
			based_on = parse_symbol();
			break;

		default:
			SYNTAX_ERROR(179);	/* Msg179 field definition clause */
			break;
		}
	}

	field = (QLI_FLD) ALLOCDV(type_fld, length);
	field->fld_name = name;
	field->fld_dtype = dtype;
	field->fld_scale = scale;
	field->fld_sub_type = sub_type;
	field->fld_sub_type_missing = sub_type_missing;
	field->fld_length = length;
	field->fld_edit_string = edit_string;
	field->fld_query_name = query_name;
	field->fld_query_header = query_header;
	if (!global_flag)
		field->fld_based = based_on;
	else if (based_on)
		IBERROR(180);			/* Msg180 global fields may not be based on other fields */

	return field;
}


static SYN parse_field_name( SYN * fld_ptr)
{
/**************************************
 *
 *	p a r s e _ f i e l d _ n a m e
 *
 **************************************
 *
 * Functional description
 *	Parse a qualified field name, or
 *	qualified * expression.
 *
 **************************************/
	SYN field, node;
	LLS stack;

	stack = NULL;

	while (TRUE) {
		if (MATCH(KW_ASTERISK)) {
			if (!stack)
				SYNTAX_ERROR(181);	/* Msg181 field name or asterisk expression */
			field = make_list(stack);
			field->syn_type = nod_star;
			return field;
		}
		LLS_PUSH(parse_name(), &stack);
		if (!MATCH(KW_DOT))
			break;
	}

	field = make_list(stack);
	field->syn_type = nod_field;
	if (fld_ptr)
		*fld_ptr = field;
	if (!(MATCH(KW_L_BRCKET)))
		return field;

/* Parse an array reference */

	stack = NULL;
	for (;;) {
		LLS_PUSH(parse_value(0, 0), &stack);
		if (MATCH(KW_R_BRCKET))
			break;
		if (!MATCH(KW_COMMA))
			SYNTAX_ERROR(183);	/* Msg183 comma */
	}

	node = SYNTAX_NODE(nod_index, s_idx_count);
	node->syn_arg[s_idx_field] = field;
	node->syn_arg[s_idx_subs] = make_list(stack);

	return node;
}


static SYN parse_for(void)
{
/**************************************
 *
 *	p a r s e _ f o r
 *
 **************************************
 *
 * Functional description
 *	Parse a FOR statement.
 *
 **************************************/
	SYN node;

	PAR_token();
#ifdef PYXIS
	if (MATCH(KW_FORM))
		IBERROR(484);			/* FORMs not supported */
#endif
	node = SYNTAX_NODE(nod_for, s_for_count);
	node->syn_arg[s_for_rse] = parse_rse();
	node->syn_arg[s_for_statement] = parse_statement();

	return node;
}

#ifdef PYXIS
static SYN parse_form(void)
{
/**************************************
 *
 *	p a r s e _ f o r m
 *
 **************************************
 *
 * Functional description
 *	Parse qualified form name, returning a form block.
 *
 **************************************/
	IBERROR(484);				/* FORMs not supported */
}
#endif

static SYN parse_from( USHORT * paren_count, USHORT * bool_flag)
{
/**************************************
 *
 *	p a r s e _ f r o m
 *
 **************************************
 *
 * Functional description
 *	Parse either an explicit or implicit FIRST ... FROM statement.
 *
 **************************************/
	SYN node, value;

	PAR_real();

	if (MATCH(KW_FIRST)) {
		value = parse_primitive_value(0, 0);
		PAR_real();
		if (!MATCH(KW_FROM))
			SYNTAX_ERROR(182);	/* Msg182 FROM rse clause */
	}
	else {
		value = parse_primitive_value(paren_count, bool_flag);
		if (sql_flag || !MATCH(KW_FROM))
			return value;
	}

	node = SYNTAX_NODE(nod_from, s_stt_count);
	node->syn_arg[s_stt_value] = value;
	node->syn_arg[s_stt_rse] = parse_rse();

	if (MATCH(KW_ELSE))
		node->syn_arg[s_stt_default] = parse_value(0, 0);

	return node;
}


static SYN parse_function(void)
{
/**************************************
 *
 *	p a r s e _ f u n c t i o n
 *
 **************************************
 *
 * Functional description
 *	Parse a function reference.
 *
 **************************************/
	SYN node;
	LLS stack;

	function_count++;
	node = SYNTAX_NODE(nod_function, s_fun_count);
	node->syn_arg[s_fun_function] = (SYN) QLI_token->tok_symbol;
	node->syn_count = 1;
	PAR_token();
	stack = NULL;

	if (MATCH(KW_LEFT_PAREN))
		for (;;) {
			LLS_PUSH(parse_value(0, 0), &stack);
			if (MATCH(KW_RIGHT_PAREN))
				break;
			if (!MATCH(KW_COMMA))
				SYNTAX_ERROR(183);	/* Msg183 comma */
		}

	node->syn_arg[s_fun_args] = make_list(stack);
	function_count--;

	return node;
}


static TEXT *parse_header(void)
{
/**************************************
 *
 *	p a r s e _ h e a d e r
 *
 **************************************
 *
 * Functional description
 *	Parse and store headers of the form:
 *		"quoted_string" [/ "more_string"]...
 *		or even the non-header -
 *
 **************************************/
	TEXT *p, *q, header[1024];

	p = header;

	while (TRUE) {
		PAR_real();
		if ((QLI_token->tok_keyword != KW_MINUS) &&
			(QLI_token->tok_type != tok_quoted)) SYNTAX_ERROR(184);	/* Msg184 quoted header segment */
		q = QLI_token->tok_string;
		while (*q)
			*p++ = *q++;
		PAR_real_token();
		if (!MATCH(KW_SLASH))
			break;
	}

	return make_string(header, p - header);
}


static SYN parse_help(void)
{
/**************************************
 *
 *	p a r s e _ h e l p
 *
 **************************************
 *
 * Functional description
 *	Parse HELP statement.  Unreasonable, but the masses
 *	must be appeased.  Bread, circuses, help.
 *
 **************************************/
	SYN node;
	LLS stack;

	stack = NULL;
	PAR_token();

	while (!KEYWORD(KW_SEMI)) {
		LLS_PUSH(parse_name(), &stack);
		MATCH(KW_COMMA);
	}

	node = make_list(stack);
	node->syn_type = nod_help;
	command_end();

	return node;
}


static SYN parse_if(void)
{
/**************************************
 *
 *	p a r s e _ i f
 *
 **************************************
 *
 * Functional description
 *	Parse an IF THEN ELSE statement.
 *
 **************************************/
	SYN node;

	PAR_token();
	node = SYNTAX_NODE(nod_if, s_if_count);
	node->syn_arg[s_if_boolean] = parse_boolean(0);
	PAR_real();
	MATCH(KW_THEN);
	++else_count;
	node->syn_arg[s_if_true] = parse_statement();
	--else_count;

	if (MATCH(KW_ELSE))
		node->syn_arg[s_if_false] = parse_statement();

	return node;
}


static SYN parse_in( SYN value, NOD_T operatr, USHORT all_flag)
{
/**************************************
 *
 *	p a r s e _ i n
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL "IN" clause.  This can have two forms:
 *
 *		value IN (exp1, exp2...)
 *
 *		value IN (column <select expression>)
 *
 **************************************/
	SYN node, node2, value2, rse;

	PAR_real();

	if (!MATCH(KW_LEFT_PAREN))
		SYNTAX_ERROR(185);		/* Msg185 left parenthesis */

/* Time to chose between two forms of the expression */

	if (!MATCH(KW_SELECT)) {
		node = SYNTAX_NODE(operatr, 2);
		node->syn_arg[0] = value;
		node->syn_arg[1] = parse_primitive_value(0, 0);
		while (MATCH(KW_COMMA)) {
			node2 = node;
			node = SYNTAX_NODE(nod_or, 2);
			node->syn_arg[0] = node2;
			node->syn_arg[1] = node2 = SYNTAX_NODE(nod_eql, 2);
			node2->syn_arg[0] = value;
			node2->syn_arg[1] = parse_value(0, 0);
		}
		parse_matching_paren();
		return node;
	}

	value2 = parse_value(0, 0);

/* We have the "hard" -- an implicit ANY */

	rse = parse_sql_rse();
	parse_matching_paren();

	rse->syn_arg[s_rse_outer] = value;
	rse->syn_arg[s_rse_inner] = value2;
	rse->syn_arg[s_rse_op] = INT_CAST operatr;
	rse->syn_arg[s_rse_all_flag] = INT_CAST all_flag;

/* Finally, construct an ANY node */

	node = SYNTAX_NODE(nod_any, 1);
	node->syn_arg[0] = rse;

	return (all_flag) ? negate(node) : node;
}


static SYN parse_insert(void)
{
/**************************************
 *
 *	p a r s e _ i n s e r t
 *
 **************************************
 *
 * Functional description
 *	Parse a STORE statement.
 *
 **************************************/
	SYN node;
	USHORT select_flag;
	LLS fields, values, distinct;

	++sql_flag;
	PAR_real_token();
	MATCH(KW_INTO);
	node = SYNTAX_NODE(nod_store, s_sto_count);

/* Pick up relation name for insert */

	node->syn_arg[s_sto_relation] = parse_sql_relation();

/* Pick up field list, provided one is present */

	distinct = fields = values = NULL;
	PAR_real();

	if (MATCH(KW_LEFT_PAREN))
		while (TRUE) {
			LLS_PUSH(parse_field_name(0), &fields);
			if (MATCH(KW_RIGHT_PAREN))
				break;
			if (!MATCH(KW_COMMA))
				SYNTAX_ERROR(186);	/* Msg186 comma or terminating right parenthesis */
		}

/* Pick up value list or SELECT statement */

	PAR_real();

	if (MATCH(KW_VALUES)) {
		select_flag = FALSE;
		if (!MATCH(KW_LEFT_PAREN))
			SYNTAX_ERROR(187);	/* Msg187 left parenthesis */
	}
	else if (MATCH(KW_SELECT))
		select_flag = TRUE;
	else
		SYNTAX_ERROR(188);		/* Msg188 VALUES list or SELECT clause */


	while (TRUE) {
		if (distinct || MATCH(KW_DISTINCT)) {
			LLS_PUSH(parse_value(0, 0), &distinct);
			LLS_PUSH(distinct->lls_object, &values);
			LLS_PUSH(0, &distinct);
		}
		else
			LLS_PUSH(parse_value(0, 0), &values);
		if (!MATCH(KW_COMMA))
			break;
	}

	if (select_flag)
		node->syn_arg[s_sto_rse] = parse_sql_rse();
	else
		parse_matching_paren();

	if (distinct)
		node->syn_arg[s_sto_rse]->syn_arg[s_rse_reduced] =
			make_list(distinct);

	node->syn_arg[s_sto_fields] = (SYN) fields;
	node->syn_arg[s_sto_values] = (SYN) values;

	--sql_flag;

	return node;
}


static NOD_T parse_join_type(void)
{
/**************************************
 *
 *	p a r s e _ j o i n _ t y p e
 *
 **************************************
 *
 * Functional description
 *	Parse a join type.
 *
 **************************************/
	NOD_T operatr;

	if (MATCH(KW_INNER))
		operatr = nod_join_inner;
	else if (MATCH(KW_LEFT))
		operatr = nod_join_left;
	else if (MATCH(KW_RIGHT))
		operatr = nod_join_right;
	else if (MATCH(KW_FULL))
		operatr = nod_join_full;
	else if (MATCH(KW_JOIN))
		return nod_join_inner;
	else
		return (NOD_T) 0;

	if (operatr != nod_join_inner)
		MATCH(KW_OUTER);

	if (!MATCH(KW_JOIN))
		SYNTAX_ERROR(489);		/* Msg489 JOIN */

	return operatr;
}


static SYN parse_list_fields(void)
{
/**************************************
 *
 *	p a r s e _ l i s t _ f i e l d s
 *
 **************************************
 *
 * Functional description
 *	Parse a LIST statement.  LIST is like PRINT, but does vertical
 *	formatting.
 *
 **************************************/
	SYN node, item;
	LLS stack;

	node = SYNTAX_NODE(nod_list_fields, s_prt_count);
	PAR_token();

	if (!MATCH(KW_ALL) && MATCH(KW_DISTINCT))
		node->syn_arg[s_prt_distinct] = INT_CAST TRUE;

	if (node->syn_arg[s_prt_output] = parse_output())
		return node;

	if (test_end())
		return node;

/* If there is a potential record selection expression, there obviously
   can't be a print list.  Get the rse.  Otherwise, pick up the print
   list. */

	if (potential_rse())
		node->syn_arg[s_prt_rse] = parse_rse();
	else {
		if (!test_end()) {
			stack = NULL;
			for (;;) {
				item = SYNTAX_NODE(nod_print_item, s_itm_count);
				item->syn_arg[s_itm_value] = parse_value(0, 0);
				item->syn_arg[s_itm_edit_string] = (SYN) parse_edit_string();
				LLS_PUSH(item, &stack);
				if (!MATCH(KW_COMMA) && !MATCH(KW_AND))
					break;
				PAR_real();
				if (MATCH(KW_AND))
					PAR_real();
			}
			node->syn_arg[s_prt_list] = make_list(stack);
		}
		if (MATCH(KW_OF))
			node->syn_arg[s_prt_rse] = parse_rse();
	}

	node->syn_arg[s_prt_output] = parse_output();

	return node;
}


static CON parse_literal(void)
{
/**************************************
 *
 *	p a r s e _ l i t e r a l
 *
 **************************************
 *
 * Functional description
 *	Parse a literal expression.
 *
 **************************************/
	CON constant;
	USHORT l;
	UCHAR *p, *q;

	PAR_real();
	q = (UCHAR *) QLI_token->tok_string;
	l = QLI_token->tok_length;

	if (QLI_token->tok_type == tok_quoted) {
		q++;
		l -= 2;
		constant = (CON) ALLOCDV(type_con, l);
		constant->con_desc.dsc_dtype = dtype_text;
		p = constant->con_desc.dsc_address = constant->con_data;
		if (constant->con_desc.dsc_length = l)
			do
				*p++ = *q++;
			while (--l);
	}
	else if (QLI_token->tok_type == tok_number)
		constant = make_numeric_constant(QLI_token->tok_string,
										 QLI_token->tok_length);
	else
		SYNTAX_ERROR(190);		/* Msg190 value expression */

	PAR_token();

	return constant;
}


static SYN parse_matches(void)
{
/**************************************
 *
 *	p a r s e _ m a t c h e s
 *
 **************************************
 *
 * Functional description
 *	Parse a matching expressing, including
 *	the preset matching language template.
 *
 **************************************/
	SYN node, language;

	PAR_token();
	node = SYNTAX_NODE(nod_sleuth, 3);
	node->syn_arg[1] = parse_value(0, 0);
	if (MATCH(KW_USING))
		node->syn_arg[2] = parse_value(0, 0);
	else if (QLI_matching_language) {
		node->syn_arg[2] = language = SYNTAX_NODE(nod_constant, 1);
		language->syn_arg[0] = (SYN) QLI_matching_language;
	}
	else {
		node->syn_type = nod_matches;
		node->syn_count = 2;
	}

	return node;
}


static void parse_matching_paren(void)
{
/**************************************
 *
 *	p a r s e _ m a t c h i n g _ p a r e n
 *
 **************************************
 *
 * Functional description
 *	Check for a trailing (right) parenthesis.  Complain if
 *	its not there.
 *
 **************************************/

	PAR_real();

	if (MATCH(KW_RIGHT_PAREN))
		return;

	SYNTAX_ERROR(191);			/* Msg191 right parenthesis */
}


static SYN parse_menu(void)
{
/**************************************
 *
 *	p a r s e _ m e n u
 *
 **************************************
 *
 * Functional description
 *	Parse a MENU statement.
 *
 **************************************/
	LLS labels, statements;
	SYN node;

	PAR_real_token();

	if (QLI_token->tok_type != tok_quoted)
		SYNTAX_ERROR(192);		/* Msg192 quoted string */

	node = SYNTAX_NODE(nod_menu, s_men_count);
	node->syn_arg[s_men_string] = parse_primitive_value(0, 0);
	labels = statements = NULL;
	PAR_real();

	while (!MATCH(KW_END)) {
		if (!MATCH(KW_ENTREE))
			SYNTAX_ERROR(193);	/* Msg193 ENTREE or END */
		if (QLI_token->tok_type != tok_quoted)
			SYNTAX_ERROR(194);	/* Msg194 quoted string */
		LLS_PUSH(parse_primitive_value(0, 0), &labels);
		MATCH(KW_COLON);
		LLS_PUSH(parse_statement(), &statements);
		PAR_real();
	}

	node->syn_arg[s_men_statements] = make_list(statements);
	node->syn_arg[s_men_labels] = make_list(labels);

	return node;
}


static SYN parse_modify(void)
{
/**************************************
 *
 *	p a r s e _ m o d i f y
 *
 **************************************
 *
 * Functional description
 *	Parse a MODIFY statement.  Modify can be any of the
 *	following:
 *
 *	MODIFY [ALL] [<field> [, <field>]...] [OF <rse> ]
 *	MODIFY [ALL] USING <statement> [OF <rse>]
 *
 **************************************/
	SYN node;
	LLS stack;

	PAR_token();

/* If this is a meta-data change, handle it elsewhere */

	if (MATCH(KW_INDEX))
		return parse_modify_index();

	if (MATCH(KW_FIELD)) {
		node = SYNTAX_NODE(nod_mod_field, 1);
		node->syn_arg[0] = (SYN) parse_database();
		node->syn_arg[1] = (SYN) parse_field(TRUE);
		return node;
	}

	if (MATCH(KW_RELATION))
		return parse_modify_relation();

/* Not a meta-data modification, just a simple data modify */

	MATCH(KW_ALL);
	node = SYNTAX_NODE(nod_modify, s_mod_count);

	if (MATCH(KW_USING))
#if PYXIS
		if (MATCH(KW_FORM))
			IBERROR(484);		/* FORMs not supported */
		else
#endif
			node->syn_arg[s_mod_statement] = parse_statement();
	else if (!KEYWORD(KW_SEMI)) {
		stack = NULL;
		while (TRUE) {
			LLS_PUSH(parse_field_name(0), &stack);
			if (!MATCH(KW_COMMA))
				break;
		}
		node->syn_arg[s_mod_list] = make_list(stack);
	}

	if (MATCH(KW_OF))
		node->syn_arg[s_mod_rse] = parse_rse();

	return node;
}


static SYN parse_modify_index(void)
{
/**************************************
 *
 *	p a r s e _ m o d i f y _ i n d e x
 *
 **************************************
 *
 * Functional description
 *	Parse a MODIFY INDEX statement. 
 *
 **************************************/
	SYN node;

	node = SYNTAX_NODE(nod_mod_index, s_mfi_count);
	node->syn_arg[s_mfi_database] = (SYN) parse_database();
	node->syn_arg[s_mfi_name] = (SYN) parse_name();
	PAR_real();

	while (TRUE) {
		if (MATCH(KW_UNIQUE))
			node->syn_flags |= (s_dfi_flag_selectivity | s_dfi_flag_unique);
		else if (MATCH(KW_DUPLICATE)) {
			node->syn_flags |= s_dfi_flag_selectivity;
			node->syn_flags &= ~s_dfi_flag_unique;
		}
		else if (MATCH(KW_INACTIVE))
			node->syn_flags |= (s_dfi_flag_activity | s_dfi_flag_inactive);
		else if (MATCH(KW_ACTIVE)) {
			node->syn_flags |= s_dfi_flag_activity;
			node->syn_flags &= ~s_dfi_flag_inactive;
		}
		else if (MATCH(KW_DESCENDING))
			node->syn_flags |= (s_dfi_flag_order | s_dfi_flag_descending);
		else if (MATCH(KW_ASCENDING)) {
			node->syn_flags |= s_dfi_flag_order;
			node->syn_flags &= ~s_dfi_flag_inactive;
		}
		else if (MATCH(KW_STATISTICS))
			node->syn_flags |= s_dfi_flag_statistics;
		else
			break;
	}

	if (!node->syn_flags)
		SYNTAX_ERROR(195);		/* Msg195 index state option */

	command_end();

	return node;
}


static SYN parse_modify_relation(void)
{
/**************************************
 *
 *	p a r s e _ m o d i f y _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Parse a MODIFY RELATION statement. 
 *
 **************************************/
	SYN node;
	QLI_REL relation;
	QLI_FLD field;

	node = SYNTAX_NODE(nod_mod_relation, 2);
	relation = parse_qualified_relation();
	node->syn_arg[0] = (SYN) relation;

	if (!relation)
		SYNTAX_ERROR(196);		/* Msg196 relation name */

	for (;;) {
		PAR_real();
		if (MATCH(KW_ADD)) {
			PAR_real();
			MATCH(KW_FIELD);
			field = parse_field(FALSE);
		}
		else if (MATCH(KW_MODIFY)) {
			PAR_real();
			MATCH(KW_FIELD);
			field = parse_field(FALSE);
			field->fld_flags |= FLD_modify;
		}
		else if (MATCH(KW_DROP)) {
			PAR_real();
			MATCH(KW_FIELD);
			field = parse_field(FALSE);
			field->fld_flags |= FLD_drop;
		}
		else
			SYNTAX_ERROR(197);	/* Msg197 ADD, MODIFY, or DROP */
		field->fld_next = (QLI_FLD) node->syn_arg[1];
		node->syn_arg[1] = (SYN) field;
		if (KEYWORD(KW_SEMI))
			break;
		if (!MATCH(KW_COMMA))
			SYNTAX_ERROR(198);	/* Msg198 comma between field definitions */
	}

	command_end();

	return node;
}


static SYN parse_multiply( USHORT * paren_count, USHORT * bool_flag)
{
/**************************************
 *
 *	p a r s e _ m u l t i p l y
 *
 **************************************
 *
 * Functional description
 *	Parse the operatrs * and /.
 *
 **************************************/
	SYN node, arg;
	NOD_T operatr;

	node = parse_from(paren_count, bool_flag);

	while (TRUE) {
		if (MATCH(KW_ASTERISK))
			operatr = nod_multiply;
		else if (MATCH(KW_SLASH))
			operatr = nod_divide;
		else
			return node;
		arg = node;
		node = SYNTAX_NODE(operatr, 2);
		node->syn_arg[0] = arg;
		node->syn_arg[1] = parse_from(paren_count, bool_flag);
	}
}


static NAM parse_name(void)
{
/**************************************
 *
 *	p a r s e _ n a m e
 *
 **************************************
 *
 * Functional description
 *	Turn current token into a name and get the next token.
 *
 **************************************/
	NAM name;
	SSHORT l;
	TEXT c, *p, *q;

	PAR_real();

	if (QLI_token->tok_type != tok_ident)
		SYNTAX_ERROR(199);		/* Msg199 identifier */

	l = QLI_token->tok_length;
	name = (NAM) ALLOCDV(type_nam, l);
	name->nam_length = l;
	name->nam_symbol = QLI_token->tok_symbol;
	q = QLI_token->tok_string;
	p = name->nam_string;

	if (l)
		do {
			c = *q++;
			*p++ = UPPER(c);

		} while (--l);

	PAR_token();

	return name;
}


static SYN parse_not( USHORT * paren_count)
{
/**************************************
 *
 *	p a r s e _ n o t
 *
 **************************************
 *
 * Functional description
 *	Parse a prefix NOT expression.
 *
 **************************************/

	PAR_real();

	if (!MATCH(KW_NOT))
		return parse_relational(paren_count);

	return negate(parse_not(paren_count));
}


static int parse_ordinal(void)
{
/**************************************
 *
 *	p a r s e _ o r d i n a l
 *
 **************************************
 *
 * Functional description
 *	Pick up a simple number as a number.  This is 
 *	used for SKIP [n], SPACE [n], COL n, and SQL 
 *	positions.
 *
 **************************************/
	int n;

	PAR_real();

	if (QLI_token->tok_type != tok_number)
		SYNTAX_ERROR(200);		/* Msg200 positive number */

	n = atoi(QLI_token->tok_string);
	if (n < 0)
		SYNTAX_ERROR(200);		/* Msg200 positive number */
	PAR_token();

	return n;
}


static SYN parse_output(void)
{
/**************************************
 *
 *	p a r s e _ o u t p u t
 *
 **************************************
 *
 * Functional description
 *	Parse an output clause the the absence thereof.
 *
 **************************************/
	SYN node;
	USHORT flag;

	if (MATCH(KW_ON))
		flag = FALSE;
	else if (MATCH(KW_TO))
		flag = TRUE;
	else
		return NULL;

	node = SYNTAX_NODE(nod_output, s_out_count);
	node->syn_arg[s_out_file] = parse_value(0, 0);
	node->syn_arg[s_out_pipe] = INT_CAST flag;

	return node;
}


static SYN parse_primitive_value( USHORT * paren_count, USHORT * bool_flag)
{
/**************************************
 *
 *	p a r s e _ p r i m i t i v e _ v a l u e
 *
 **************************************
 *
 * Functional description
 *	Pick up a value expression.  This may be either a field reference
 *	or a constant value.
 *
 **************************************/
	SYM symbol;
	SYN node, sub;
	CON constant;
	UCHAR *p;
	USHORT local_count;

	if (!paren_count) {
		local_count = 0;
		paren_count = &local_count;
	}

	PAR_real();

	switch (next_keyword()) {
	case KW_LEFT_PAREN:
		PAR_token();
		(*paren_count)++;
		if (bool_flag && *bool_flag)
			node = parse_boolean(paren_count);
		else
			node = parse_value(paren_count, bool_flag);
/*
	if (*paren_count && KEYWORD (KW_RIGHT_PAREN))
*/
		{
			parse_matching_paren();
			(*paren_count)--;
		}
		break;

	case KW_PLUS:
		PAR_token();
		return parse_primitive_value(paren_count, 0);

	case KW_MINUS:
		PAR_token();
		sub = parse_primitive_value(paren_count, 0);
		if (sub->syn_type == nod_constant) {
			constant = (CON) sub->syn_arg[0];
			p = constant->con_desc.dsc_address;
			switch (constant->con_desc.dsc_dtype) {
			case dtype_long:
				*(SLONG *) p = -*(SLONG *) p;
				return sub;
			case dtype_text:
				*p = '-';
				return sub;
			}
		}
		node = SYNTAX_NODE(nod_negate, 1);
		node->syn_arg[0] = sub;
		break;

	case KW_ASTERISK:
		node = parse_prompt();
		break;

	case KW_EDIT:
		PAR_token();
		node = SYNTAX_NODE(nod_edit_blob, 1);
		if (!KEYWORD(KW_SEMI))
			node->syn_arg[0] = parse_value(0, 0);
		break;

	case KW_FORMAT:
		PAR_token();
		node = SYNTAX_NODE(nod_format, s_fmt_count);
		node->syn_arg[s_fmt_value] = parse_value(0, 0);
		node->syn_arg[s_fmt_edit] = (SYN) parse_edit_string();
		break;

	case KW_NULL:
		PAR_token();
		node = SYNTAX_NODE(nod_null, 0);
		break;

	case KW_USER_NAME:
		PAR_token();
		node = SYNTAX_NODE(nod_user_name, 0);
		break;

	case KW_COUNT:
	case KW_MAX:
	case KW_MIN:
	case KW_AVERAGE:
	case KW_TOTAL:
		node = parse_statistical();
		break;

	case KW_RUNNING:
		if (function_count > 0)
			IBERROR(487);		/* Msg487 Invalid argument for UDF */
		PAR_real_token();
		node = SYNTAX_NODE(nod_running_total, s_stt_count);
		if (MATCH(KW_COUNT))
			node->syn_type = nod_running_count;
		else {
			MATCH(KW_TOTAL);
			node->syn_arg[s_stt_value] = parse_value(0, 0);
		}
		break;

	case KW_SELECT:
		node = parse_sql_subquery();
		break;

	default:
		if ((symbol = QLI_token->tok_symbol)
			&& symbol->sym_type == SYM_function) {
			node = parse_function();
			break;
		}
		if (QLI_token->tok_type == tok_ident) {
			node = parse_field_name(0);
			break;
		}
		node = SYNTAX_NODE(nod_constant, 1);
		node->syn_arg[0] = (SYN) parse_literal();
	}

	return node;
}


static SYN parse_print_list(void)
{
/**************************************
 *
 *	p a r s e _ p r i n t _ l i s t
 *
 **************************************
 *
 * Functional description
 *	Pick up a print item.  The syntax of a print item is:
 *
 *	<value> [ '[ <query_header> '] ] [USING <edit_string> ]
 *
 **************************************/
	NOD_T op;
	SYN node;
	LLS stack;

	stack = NULL;

	while (TRUE) {
		if (MATCH(KW_SKIP))
			op = nod_skip;
		else if (MATCH(KW_SPACE))
			op = nod_space;
		else if (MATCH(KW_TAB))
			op = nod_tab;
		else if (MATCH(KW_COLUMN))
			op = nod_column;
		else if (MATCH(KW_NEW_PAGE))
			op = nod_new_page;
		else if (MATCH(KW_REPORT_HEADER))
			op = nod_report_header;
		else if (MATCH(KW_COLUMN_HEADER))
			op = nod_column_header;
		else {
			op = nod_print_item;
			node = SYNTAX_NODE(nod_print_item, s_itm_count);
			node->syn_arg[s_itm_value] = parse_value(0, 0);
			if (MATCH(KW_LEFT_PAREN)) {
				if (MATCH(KW_MINUS))
					node->syn_arg[s_itm_header] = (SYN) "-";
				else
					node->syn_arg[s_itm_header] = (SYN) parse_header();
				parse_matching_paren();
			}
			node->syn_arg[s_itm_edit_string] = (SYN) parse_edit_string();
		}
		if (op != nod_print_item) {
			node = SYNTAX_NODE(op, 1);
			node->syn_count = 0;
			node->syn_arg[0] = INT_CAST 1;
			if (op == nod_column || QLI_token->tok_type == tok_number)
				node->syn_arg[0] = INT_CAST parse_ordinal();
			if ((op == nod_skip) && ((int) node->syn_arg[0] < 1))
				SYNTAX_ERROR(478);	/* Msg478 number > 0 */
		}
		LLS_PUSH(node, &stack);
		if (!MATCH(KW_COMMA) && !MATCH(KW_AND))
			break;
		PAR_real();
		if (MATCH(KW_AND))
			PAR_real();
	}

	node = make_list(stack);

	return node;
}


static SYN parse_print(void)
{
/**************************************
 *
 *	p a r s e _ p r i n t
 *
 **************************************
 *
 * Functional description
 *	Parse the PRINT statement.  This is the richest and most general
 *	Datatrieve statement.  Hence this may get a bit long.  The syntax is:
 *
 *		[<item> [, <item>]] OF <rse>
 *	PRINT [ 			     ] [ON <file>]
 *		<rse>
 *
 **************************************/
	SYN node;

	node = SYNTAX_NODE(nod_print, s_prt_count);
	PAR_token();

	if (!MATCH(KW_ALL) && MATCH(KW_DISTINCT))
		node->syn_arg[s_prt_distinct] = INT_CAST TRUE;

	if (node->syn_arg[s_prt_output] = parse_output())
		return node;

	if (test_end())
		return node;

/* If there is a potential record selection expression, there obviously
   can't be a print list.  Get the rse.  Otherwise, pick up the print
   list. */

	if (potential_rse())
		node->syn_arg[s_prt_rse] = parse_rse();
	else if (!KEYWORD(KW_USING)) {
		if (!KEYWORD(KW_THEN) && !KEYWORD(KW_OF) && !KEYWORD(KW_ON))
			node->syn_arg[s_prt_list] = parse_print_list();
		if (MATCH(KW_OF))
			node->syn_arg[s_prt_rse] = parse_rse();
	}

	if (!node->syn_arg[s_prt_list] && MATCH(KW_USING)) {
		IBERROR(484);			/* FORMs not supported */
	}
	else
		node->syn_arg[s_prt_output] = parse_output();

	return node;
}


static SYN parse_prompt(void)
{
/**************************************
 *
 *	p a r s e _ p r o m p t
 *
 **************************************
 *
 * Functional description
 *	Parse a prompt expression.
 *
 **************************************/
	SYN node;
	USHORT l;
	TEXT *q;

	PAR_token();
	node = SYNTAX_NODE(nod_prompt, 1);

/* If there is a period, get the prompt string and make a string node */

	if (MATCH(KW_DOT)) {
		PAR_real();
		l = QLI_token->tok_length;
		q = QLI_token->tok_string;
		if (QLI_token->tok_type == tok_quoted) {
			q++;
			l -= 2;
		}
		node->syn_arg[0] = (SYN) make_string(q, l);
		PAR_token();
	}

	return node;
}


static QFL parse_qualified_filter(void)
{
/**************************************
 *
 *	p a r s e _ q u a l i f i e d _ f i l t e r
 *
 **************************************
 *
 * Functional description
 *	This token ought to be a filter, possibly qualified.
 *	Return a qualified filter block, containing the 
 *	filter name in a NAM block and the database in a
 *	DBB block if a database was specified.  Somebody
 *	else will decide what to do if the database was not
 *	specified.
 *
 **************************************/
	QFL filter;

	filter = (QFL) ALLOCD(type_qfl);
	filter->qfl_database = parse_database();
	filter->qfl_name = parse_name();
	return filter;
}



static QFN parse_qualified_function(void)
{
/**************************************
 *
 *	p a r s e _ q u a l i f i e d _ f u n c t i o n
 *
 **************************************
 *
 * Functional description
 *	This token ought to be a function, possibly qualified.
 *	Return a qualified function block, containing the 
 *	function name in a NAM block and the database in a
 *	DBB block if a database was specified.  Somebody
 *	else will decide what to do if the database was not
 *	specified.
 *
 **************************************/
	QFN func;

	func = (QFN) ALLOCD(type_qfn);
	func->qfn_database = parse_database();
	func->qfn_name = parse_name();
	return func;
}


static QPR parse_qualified_procedure(void)
{
/**************************************
 *
 *	p a r s e _ q u a l i f i e d _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *	This token ought to be a procedure, possibly qualified.
 *	Return a qualified procedure block, containing the 
 *	procedure name in a NAM block and the database in a
 *	DBB block if a database was specified.  Somebody
 *	else will decide what to do if the database was not
 *	specified.
 *
 **************************************/
	QPR proc;

	proc = (QPR) ALLOCD(type_qpr);
	proc->qpr_database = parse_database();
	proc->qpr_name = parse_name();
	return proc;
}


static QLI_REL parse_qualified_relation(void)
{
/**************************************
 *
 *	p a r s e _ q u a l i f i e d _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Check for a relation name, possible qualified.  If there
 *	is a relation name, parse it and return the relation.  If
 *	not, return NULL.  Produce a syntax error only if there is
 *	a partially qualified name.
 *
 **************************************/
	SYM db_symbol;
	QLI_REL relation;

	PAR_real();

/* If the next token is a database symbol, take it as a qualifier */

	if ((db_symbol = QLI_token->tok_symbol)
		&& db_symbol->sym_type == SYM_database) {
		PAR_real_token();
		if (!MATCH(KW_DOT))
			SYNTAX_ERROR(202);	/* Msg202 period in qualified relation name */
		PAR_real();
		if (relation = resolve_relation(db_symbol, QLI_token->tok_symbol)) {
			PAR_token();
			return relation;
		}
		ERRQ_print_error(203, QLI_token->tok_string, db_symbol->sym_string,
						 NULL, NULL, NULL);	/* Msg203 %s is not a relation in database %s */
	}

	if (relation = resolve_relation(0, QLI_token->tok_symbol))
		PAR_token();

	return relation;
}


static SYN parse_ready( NOD_T node_type)
{
/**************************************
 *
 *	p a r s e _ r e a d y
 *
 **************************************
 *
 * Functional description
 *	Parse a READY statement: 
 *
 *	READY <filename> [AS <symbol>] [,...];
 *
 **************************************/
	SYN node;
	DBB database;
	NAM name;
	LLS stack;
	SSHORT l;
	TEXT *p, *q;

	stack = NULL;

	while (TRUE) {
		LEX_filename();
		if (!(l = QLI_token->tok_length))
			ERRQ_error(204, NULL, NULL, NULL, NULL, NULL);	/* Msg204 database file name required on READY */
		q = QLI_token->tok_string;
		if (QLI_token->tok_type == tok_quoted) {
			l -= 2;
			q++;
		}
		database = (DBB) ALLOCDV(type_dbb, l);
		database->dbb_filename_length = l;
		p = database->dbb_filename;
		do
			*p++ = *q++;
		while (--l);
		PAR_token();

		if (node_type == nod_def_database || node_type == nod_ready) {
			if (MATCH(KW_AS)) {
				name = parse_name();
				database->dbb_symbol = (SYM) name;
				if (HSH_lookup(name->nam_string, name->nam_length))
					ERRQ_error(408, name->nam_string, NULL, NULL, NULL, NULL);	/* Database handle is not unique */
			}
			else
				database->dbb_symbol = (SYM) make_name();
		}
		else if (node_type == nod_sql_database) {
			if (MATCH(KW_PAGESIZE)) {
				if (database->dbb_pagesize)
					SYNTAX_ERROR(390);	/* Msg390 Multiple page size specifications */
				if (!MATCH(KW_EQUALS))
					SYNTAX_ERROR(396);	/* Msg396 = (equals) */
				database->dbb_pagesize = parse_ordinal();
			}
			database->dbb_symbol = (SYM) make_name();
		}

		for (;;) {
			if (MATCH(KW_USER))
				database->dbb_user = parse_literal();
			else if (MATCH(KW_PASSWORD))
				database->dbb_password = parse_literal();
			else
				break;
		}

		LLS_PUSH(database, &stack);
		if (!KEYWORD(KW_COMMA) || (node_type == nod_sql_database))
			break;
	}

	command_end();
	node = make_list(stack);
	node->syn_type = node_type;

	return node;
}


static SYN parse_relational( USHORT * paren_count)
{
/**************************************
 *
 *	p a r s e _ r e l a t i o n a l
 *
 **************************************
 *
 * Functional description
 *	Parse a relational expression.
 *
 **************************************/
	SYN node, expr1, or_node;
	USHORT negation;
	NOD_T operatr, *rel_ops;
	USHORT local_flag;

	local_flag = TRUE;

	if (MATCH(KW_ANY)) {
		node = SYNTAX_NODE(nod_any, 1);
		node->syn_arg[0] = parse_rse();
		return node;
	}

	operatr = (NOD_T) 0;
	if (MATCH(KW_EXISTS))
		operatr = nod_any;
	else if (MATCH(KW_SINGULAR))
		operatr = nod_unique;

	if (operatr != (NOD_T) 0) {
		PAR_real();
		if (MATCH(KW_LEFT_PAREN)) {
			PAR_real();
			if (MATCH(KW_SELECT)) {
				PAR_real();
				node = SYNTAX_NODE(operatr, 2);
				if (!MATCH(KW_ASTERISK))
					node->syn_arg[1] = parse_value(0, 0);
				node->syn_arg[0] = parse_sql_rse();
				parse_matching_paren();
				return node;
			}
		}
		if (operatr == nod_any)
			SYNTAX_ERROR(205);	/* Msg205 EXISTS (SELECT * <sql rse>) */
		else
			SYNTAX_ERROR(488);	/* Msg488 SINGULAR (SELECT * <sql rse>) */
	}

	if (MATCH(KW_UNIQUE)) {
		node = SYNTAX_NODE(nod_unique, 1);
		node->syn_arg[0] = parse_rse();
		return node;
	}

	expr1 = parse_value(paren_count, &local_flag);
	if (KEYWORD(KW_RIGHT_PAREN))
		return expr1;
	if (KEYWORD(KW_SEMI))
		for (rel_ops = relationals; *rel_ops != (NOD_T) 0; rel_ops++)
			if (expr1->syn_type == *rel_ops)
				return expr1;

	negation = FALSE;
	node = NULL;
	MATCH(KW_IS);
	PAR_real();

	if (MATCH(KW_NOT)) {
		negation = TRUE;
		PAR_real();
	}

	switch (next_keyword()) {
	case KW_IN:
		PAR_token();
		node = parse_in(expr1, nod_eql, FALSE);
		break;

	case KW_EQUALS:
	case KW_EQ:
		operatr = (negation) ? nod_neq : nod_eql;
		negation = FALSE;
		break;

	case KW_NE:
		operatr = (negation) ? nod_eql : nod_neq;
		negation = FALSE;
		break;

	case KW_GT:
		operatr = (negation) ? nod_leq : nod_gtr;
		negation = FALSE;
		break;

	case KW_GE:
		operatr = (negation) ? nod_lss : nod_geq;
		negation = FALSE;
		break;

	case KW_LE:
		operatr = (negation) ? nod_gtr : nod_leq;
		negation = FALSE;
		break;

	case KW_LT:
		operatr = (negation) ? nod_geq : nod_lss;
		negation = FALSE;
		break;

	case KW_CONTAINING:
		operatr = nod_containing;
		break;

	case KW_MATCHES:
		node = parse_matches();
		node->syn_arg[0] = expr1;
		operatr = node->syn_type;
		break;

	case KW_LIKE:
		PAR_token();
		node = SYNTAX_NODE(nod_like, 3);
		node->syn_arg[0] = expr1;
		node->syn_arg[1] = parse_value(0, 0);
		if (MATCH(KW_ESCAPE))
			node->syn_arg[2] = parse_value(0, 0);
		else
			node->syn_count = 2;
		break;


	case KW_STARTS:
		PAR_token();
		MATCH(KW_WITH);
		node = SYNTAX_NODE(nod_starts, 2);
		node->syn_arg[0] = expr1;
		node->syn_arg[1] = parse_value(0, 0);
		break;

	case KW_NULL:
	case KW_MISSING:
		PAR_token();
		node = SYNTAX_NODE(nod_missing, 1);
		node->syn_arg[0] = expr1;
		break;

	case KW_BETWEEN:
		PAR_token();
		node = SYNTAX_NODE(nod_between, 3);
		node->syn_arg[0] = expr1;
		node->syn_arg[1] = parse_value(0, 0);
		MATCH(KW_AND);
		node->syn_arg[2] = parse_value(0, 0);
		break;

	default:
		for (rel_ops = relationals; *rel_ops != (NOD_T) 0; rel_ops++)
			if (expr1->syn_type == *rel_ops)
				return expr1;
		SYNTAX_ERROR(206);		/* Msg206 relational operatr */
	}

/* If we haven't already built a node, it must be an ordinary binary operatr.
   Build it. */

	if (!node) {
		PAR_token();
		if (MATCH(KW_ANY))
			return parse_in(expr1, operatr, FALSE);
		if (MATCH(KW_ALL))
			return parse_in(expr1, operatr, TRUE);
		node = SYNTAX_NODE(operatr, 2);
		node->syn_arg[0] = expr1;
		node->syn_arg[1] = parse_value(paren_count, &local_flag);
	}

/* If a negation remains to be handled, zap the node under a NOT. */

	if (negation)
		node = negate(node);

/*  If the node isn't an equality, we've done.  Since equalities can be
    structured as implicit ORs, build them here. */

	if (operatr != nod_eql)
		return node;

/* We have an equality operation, which can take a number of values.  Generate
   implicit ORs */

	while (MATCH(KW_COMMA)) {
		PAR_real();
		MATCH(KW_OR);
		or_node = SYNTAX_NODE(nod_or, 2);
		or_node->syn_arg[0] = node;
		or_node->syn_arg[1] = node = SYNTAX_NODE(nod_eql, 2);
		node->syn_arg[0] = expr1;
		node->syn_arg[1] = parse_value(paren_count, &local_flag);
		node = or_node;
	}

	return node;
}


static SYN parse_relation(void)
{
/**************************************
 *
 *	p a r s e _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Parse a relation expression.  Syntax is:
 *
 *	[ <context_variable> IN ] <relation>
 *
 **************************************/
	SYN node;
	SYM context;

	node = SYNTAX_NODE(nod_relation, s_rel_count);

/* Token wasn't a relation name, maybe it's a context variable. */

	if (!(node->syn_arg[s_rel_relation] = (SYN) parse_qualified_relation())) {
		context = parse_symbol();
		node->syn_arg[s_rel_context] = (SYN) context;
		if (sql_flag || !MATCH(KW_IN)) {
			if (!QLI_databases)
				IBERROR(207);	/* Msg207 a database has not been readied */
			ERRQ_print_error(208, context->sym_string, NULL, NULL, NULL, NULL);	/* Msg208 expected \"relation_name\", encountered \"%s\ */
		}
		if (!
			(node->syn_arg[s_rel_relation] =
			 (SYN) parse_qualified_relation()))SYNTAX_ERROR(209);	/* Msg209 relation name */
	}

	return node;
}


static SYN parse_rename(void)
{
/**************************************
 *
 *	p a r s e _ r e n a m e
 *
 **************************************
 *
 * Functional description
 *	Parse a PROCEDURE rename statement.
 *
 **************************************/
	SYN node;

	PAR_real_token();

	if (!MATCH(KW_PROCEDURE))
		SYNTAX_ERROR(210);		/* Msg210 PROCEDURE */

	node = SYNTAX_NODE(nod_rename_proc, 2);
	node->syn_arg[0] = (SYN) parse_qualified_procedure();
	MATCH(KW_TO);
	node->syn_arg[1] = (SYN) parse_qualified_procedure();

	return node;
}


static SYN parse_repeat(void)
{
/**************************************
 *
 *	p a r s e _ r e p e a t
 *
 **************************************
 *
 * Functional description
 *	Parse REPEAT statement.
 *
 **************************************/
	SYN node;

	PAR_token();
	node = SYNTAX_NODE(nod_repeat, s_rpt_count);
	node->syn_arg[s_rpt_value] = parse_value(0, 0);
	node->syn_arg[s_rpt_statement] = parse_statement();

	return node;
}


static SYN parse_report(void)
{
/**************************************
 *
 *	p a r s e _ r e p o r t
 *
 **************************************
 *
 * Functional description
 *	Parse a report specification.
 *
 **************************************/
	SYN node, rse, flds, qli_fld, rse_fld;
	RPT report;
	BRK control, *ptr, tmpptr, tmpptr1;
	USHORT top, i, srt_syn, ctl_syn, syn_count;
	NAM name1, name2;

	++sw_report;
	PAR_token();
	report = (RPT) ALLOCD(type_rpt);
	node = SYNTAX_NODE(nod_report, s_prt_count);
	node->syn_arg[s_prt_list] = (SYN) report;

/* Pick up record select expression */

	rse = node->syn_arg[s_prt_rse] = parse_rse();
	node->syn_arg[s_prt_output] = parse_output();

/* Pick up report clauses */

	for (;;) {
		PAR_real();
		if (MATCH(KW_END_REPORT))
			break;
		switch (next_keyword()) {
		case KW_PRINT:
			PAR_token();
			report->rpt_detail_line = (QLI_NOD) parse_print_list();
			break;

		case KW_AT:
			PAR_token();
			if (MATCH(KW_TOP))
				top = TRUE;
			else if (MATCH(KW_BOTTOM))
				top = FALSE;
			else
				SYNTAX_ERROR(382);	/* Msg382 TOP or BOTTOM */
			MATCH(KW_OF);
			if (MATCH(KW_REPORT)) {
				control = (BRK) ALLOCD(type_brk);
				ptr = (top) ? &report->rpt_top_rpt : &report->rpt_bottom_rpt;
				control->brk_next = *ptr;
				*ptr = control;
				MATCH(KW_PRINT);
				control->brk_line = parse_print_list();
			}
			else if (MATCH(KW_PAGE)) {
				control = (BRK) ALLOCD(type_brk);
				ptr =
					(top) ? &report->rpt_top_page : &report->rpt_bottom_page;
				control->brk_next = *ptr;
				*ptr = control;
				MATCH(KW_PRINT);
				control->brk_line = parse_print_list();
			}
			else {
				ptr = (top) ? &report->rpt_top_breaks :
					&report->rpt_bottom_breaks;
				if (!*ptr) {
					/* control breaks should only be on sorted fields, set up list
					   of control breaks based on sorted fields and then add action (print)
					   items to that list. */
					if (!(flds = rse->syn_arg[s_rse_sort]))
						SYNTAX_ERROR(383);	/* Msg383 sort field */
					tmpptr = *ptr;
					for (i = 0; i < flds->syn_count; i += 2) {
						control = (BRK) ALLOCD(type_brk);
						control->brk_field = flds->syn_arg[i];
						control->brk_line = NULL;
						control->brk_statisticals = NULL;
						control->brk_next = tmpptr;
						tmpptr = control;
					}
					if (!top) {
						/* reverse the 'at bottom' control break list as the
						   lower control breaks should be performed prior to the higher ones. */
						for (tmpptr1 = tmpptr->brk_next, control = 0; tmpptr;) {
							tmpptr->brk_next = control;
							control = tmpptr;
							if (tmpptr = tmpptr1)
								tmpptr1 = tmpptr->brk_next;
						}
						tmpptr = control;
					}
					*ptr = tmpptr;
				}
				qli_fld = parse_field_name(0);
				for (control = *ptr; control; control = control->brk_next) {
					rse_fld = (SYN) control->brk_field;
					if (rse_fld->syn_type != qli_fld->syn_type)
						continue;
					/* if number of field qualifiers on sort field and control field
					   are not equal test match of rightmost set */
					syn_count = MIN(rse_fld->syn_count, qli_fld->syn_count);
					srt_syn = ctl_syn = 0;
					if (syn_count != rse_fld->syn_count)
						srt_syn = rse_fld->syn_count - syn_count;
					if (syn_count != qli_fld->syn_count)
						ctl_syn = qli_fld->syn_count - syn_count;
					for (i = 0; i < syn_count; i++) {
						name1 = (NAM) rse_fld->syn_arg[i + srt_syn];
						name2 = (NAM) qli_fld->syn_arg[i + ctl_syn];
						if (strcmp(name1->nam_string, name2->nam_string))
							break;
					}
					if (i == qli_fld->syn_count)
						break;
				}
				if (!control)
					SYNTAX_ERROR(383);	/* Msg383 sort field */
				MATCH(KW_PRINT);
				control->brk_field = qli_fld;
				control->brk_line = parse_print_list();
			}
			break;

		case KW_SET:
			PAR_token();
			if (MATCH(KW_COLUMNS)) {
				MATCH(KW_EQUALS);
				report->rpt_columns = parse_ordinal();
			}
			else if (MATCH(KW_LINES)) {
				MATCH(KW_EQUALS);
				report->rpt_lines = parse_ordinal();
			}
			else if (MATCH(KW_REPORT_NAME)) {
				MATCH(KW_EQUALS);
				report->rpt_name = (TEXT *) parse_header();
			}
			else
				SYNTAX_ERROR(212);	/* Msg212 report writer SET option */
			break;

		default:
			SYNTAX_ERROR(213);	/* Msg213 report item */
		}
		MATCH(KW_SEMI);
	}

	if (!node->syn_arg[s_prt_output])
		node->syn_arg[s_prt_output] = parse_output();

	check_end();
	--sw_report;

	return node;
}


static SYN parse_rse(void)
{
/**************************************
 *
 *	p a r s e _ r s e
 *
 **************************************
 *
 * Functional description
 *	Parse a record selection expression.
 *
 **************************************/
	SYN node, first, over, *ptr;
	LLS stack, field_stack;
	USHORT count, sw_with;

	count = sw_with = 0;
	first = NULL;
	stack = NULL;
	PAR_real();

	if (MATCH(KW_ALL))
		PAR_real();

	if (MATCH(KW_FIRST))
		first = parse_value(0, 0);

	while (TRUE) {
		count++;
		LLS_PUSH(parse_relation(), &stack);
		over = NULL;
		if (MATCH(KW_OVER)) {
			field_stack = NULL;
			while (TRUE) {
				LLS_PUSH(parse_field_name(0), &field_stack);
				if (!MATCH(KW_COMMA))
					break;
			}
			over = make_list(field_stack);
		}
		LLS_PUSH(over, &stack);
		if (!MATCH(KW_CROSS))
			break;
	}

	node = SYNTAX_NODE(nod_rse, (int) s_rse_count + 2 * count);
	node->syn_count = count;
	node->syn_arg[s_rse_first] = first;
	ptr = &node->syn_arg[(int) s_rse_count + 2 * count];

	while (stack)
		*--ptr = (SYN) LLS_POP(&stack);

/* Pick up various other clauses */

	while (TRUE) {
		if (MATCH(KW_WITH)) {
			if (!sw_with) {
				sw_with++;
				node->syn_arg[s_rse_boolean] = parse_boolean(0);
			}
			else
				IBERROR(384);	/* Msg384 Too many WITHs */
		}

		else if (MATCH(KW_SORTED)) {
			PAR_real();
			MATCH(KW_BY);
			node->syn_arg[s_rse_sort] = parse_sort();
		}

#ifdef PC_ENGINE
		else if (MATCH(KW_USING)) {
			PAR_real();
			MATCH(KW_INDEX);
			node->syn_arg[s_rse_index] = (SYN) parse_name();
		}
#endif

		else if (MATCH(KW_REDUCED)) {
			PAR_real();
			MATCH(KW_TO);
			node->syn_arg[s_rse_reduced] = parse_sort();
		}

		else if (MATCH(KW_GROUP)) {
			PAR_real();
			MATCH(KW_BY);
			stack = NULL;
			while (TRUE) {
				LLS_PUSH(parse_udf_or_field(), &stack);
				if (!MATCH(KW_COMMA))
					break;
			}
			node->syn_arg[s_rse_group_by] = make_list(stack);
		}
		else
			break;
	}

	return node;
}


static SYN parse_select(void)
{
/**************************************
 *
 *	p a r s e _ s e l e c t
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL select statement.
 *
 **************************************/
	SYN node, item, rse;
	LLS stack;

	++sql_flag;
	PAR_token();
	node = SYNTAX_NODE(nod_print, s_prt_count);

	if (!MATCH(KW_ALL) && MATCH(KW_DISTINCT))
		node->syn_arg[s_prt_distinct] = INT_CAST TRUE;

/* Get list of items */

	if (!MATCH(KW_ASTERISK)) {
		stack = NULL;
		while (TRUE) {
			item = SYNTAX_NODE(nod_print_item, s_itm_count);
			item->syn_arg[s_itm_value] = parse_value(0, 0);
			LLS_PUSH(item, &stack);
			if (!MATCH(KW_COMMA))
				break;
		}
		node->syn_arg[s_prt_list] = make_list(stack);
	}

	node->syn_arg[s_prt_rse] = rse = parse_sql_rse();
	rse->syn_arg[s_rse_list] = node->syn_arg[s_prt_list];

	if (MATCH(KW_ORDER)) {
		PAR_real();
		MATCH(KW_BY);
		node->syn_arg[s_prt_order] = parse_sort();
	}

	--sql_flag;

	return node;
}


static SYN parse_set(void)
{
/**************************************
 *
 *	p a r s e _ s e t
 *
 **************************************
 *
 * Functional description
 *	Parse a SET statement.
 *
 **************************************/
	SYN node;
	LLS stack;
	ENUM set_t sw;
	USHORT count;
	U_IPTR value;
	PAR_token();
	stack = NULL;
	count = 0;

	while (TRUE) {
		PAR_real();
		value = TRUE;
		if (MATCH(KW_NO)) {
			value = FALSE;
			PAR_real();
		}
		switch (QLI_token->tok_keyword) {
		case KW_BLR:
			sw = set_blr;
			PAR_token();
			break;

		case KW_STATISTICS:
			sw = set_statistics;
			PAR_token();
			break;

		case KW_COLUMNS:
			sw = set_columns;
			PAR_token();
			MATCH(KW_EQUALS);
			value = parse_ordinal();
			break;

		case KW_LINES:
			sw = set_lines;
			PAR_token();
			MATCH(KW_EQUALS);
			value = parse_ordinal();
			break;

		case KW_SEMICOLON:
			sw = set_semi;
			PAR_token();
			break;

		case KW_ECHO:
			sw = set_echo;
			PAR_token();
			break;
#ifdef PYXIS
		case KW_FORM:
		case KW_FORMS:
			IBERROR(484);		/* FORMs not supported */
			break;
#endif
		case KW_MATCHING_LANGUAGE:
			sw = set_matching_language;
			PAR_token();
			MATCH(KW_EQUALS);
			if (value)
				value = (U_IPTR) parse_literal();
			break;

		case KW_PASSWORD:
			sw = set_password;
			PAR_token();
			value = (U_IPTR) parse_literal();
			break;

		case KW_PROMPT:
			sw = set_prompt;
			PAR_token();
			value = (U_IPTR) parse_literal();
			break;

		case KW_CONT_PROMPT:
			sw = set_continuation;
			PAR_token();
			value = (U_IPTR) parse_literal();
			break;

		case KW_USER:
			sw = set_user;
			PAR_token();
			value = (U_IPTR) parse_literal();
			break;

		case KW_COUNT:
			sw = set_count;
			PAR_token();
			break;

		case KW_CHAR:
			sw = set_charset;
			PAR_token();
			MATCH(KW_SET);
			if (value) {		/* allow for NO */
				MATCH(KW_EQUALS);
				value = (U_IPTR) parse_name();
			}
			break;

		case KW_NAMES:
			sw = set_charset;
			PAR_token();
			if (value) {
				value = (U_IPTR) parse_name();
			}
			break;

#ifdef DEV_BUILD
		case KW_EXPLAIN:
			sw = set_explain;
			PAR_token();
			break;

		case KW_HEXOUT:
			sw = set_hex_output;
			PAR_token();
			break;
#endif

		default:
			SYNTAX_ERROR(214);	/* Msg214 set option */
		}
		LLS_PUSH(sw, &stack);
		LLS_PUSH(value, &stack);
		count++;
		if (!MATCH(KW_COMMA))
			break;
	}

	command_end();
	node = make_list(stack);
	node->syn_count = count;
	node->syn_type = nod_set;

	return node;
}


static SYN parse_shell(void)
{
/**************************************
 *
 *	p a r s e _ s h e l l
 *
 **************************************
 *
 * Functional description
 *	Parse SHELL command.
 *
 **************************************/
	SYN node;

	PAR_token();
	node = SYNTAX_NODE(nod_shell, 1);

	if (!KEYWORD(KW_SEMI))
		node->syn_arg[0] = (SYN) parse_literal();

	command_end();

	return node;
}


static SYN parse_show(void)
{
/**************************************
 *
 *	p a r s e _ s h o w
 *
 **************************************
 *
 * Functional description
 *	Parse a SHOW statement.  The first
 *	group are individual things (one 
 *	named relation, field, form, ...)
 *	
 *	the second group are sets of things
 *	and can be qualified with a FOR 
 *	[DATABASE] <handle>
 *
 **************************************/
	SYN node;
	QLI_REL relation;
	LLS stack;
	SYM symbol;
	ENUM show_t sw;
	BLK value;
	USHORT count;
	QPR proc;

	PAR_token();
	stack = NULL;
	count = 0;

	while (TRUE) {
		PAR_real();
		value = NULL;
		symbol = QLI_token->tok_symbol;
		if (MATCH(KW_ALL))
			sw = show_all;
		else if (MATCH(KW_MATCHING_LANGUAGE))
			sw = show_matching_language;
		else if (MATCH(KW_VERSION))
			sw = show_version;
		else if (MATCH(KW_RELATION)) {
			if (!(value = (BLK) parse_qualified_relation()))
				SYNTAX_ERROR(216);	/* Msg216 relation name */
			else
				sw = show_relation;
		}
		else if (MATCH(KW_FILTER)) {
			sw = show_filter;
			value = (BLK) parse_qualified_filter();
		}
		else if (MATCH(KW_FUNCTION)) {
			sw = show_function;
			value = (BLK) parse_qualified_function();
		}
		else if ((MATCH(KW_DATABASES)) || (MATCH(KW_READY)))
			sw = show_databases;
		else if (MATCH(KW_DATABASE)) {
			sw = show_database;
			if (value = (BLK) get_dbb(QLI_token->tok_symbol))
				PAR_token();
		}
		else if (MATCH(KW_FIELD)) {
			sw = show_field;
			value = (BLK) parse_field_name(0);
		}
		else if (MATCH(KW_PROCEDURE)) {
			sw = show_procedure;
			value = (BLK) parse_qualified_procedure();
		}
		else if (MATCH(KW_VARIABLE)) {
			sw = show_variable;
			value = (BLK) parse_name();
		}
		else if (MATCH(KW_VARIABLES))
			sw = show_variables;
		else if (MATCH(KW_FIELDS)) {
			if (MATCH(KW_FOR)) {
				if (MATCH(KW_DATABASE)) {
					if (value = (BLK) get_dbb(QLI_token->tok_symbol))
						PAR_token();
					else
						SYNTAX_ERROR(221);	/* Msg221 database name */
					sw = show_db_fields;
				}
				else {
					MATCH(KW_RELATION);
					if (!(value = (BLK) parse_qualified_relation()))
						SYNTAX_ERROR(218);	/* Msg218 relation name */
					else
						sw = show_relation;
				}
			}
			else
				sw = show_all;
		}
		else if (MATCH(KW_INDICES)) {
			sw = show_indices;
			if (MATCH(KW_FOR))
				if (MATCH(KW_DATABASE)) {
					if (value = (BLK) get_dbb(QLI_token->tok_symbol))
						PAR_token();
					else
						SYNTAX_ERROR(221);	/* Msg221 database name */
					sw = show_db_indices;
				}
				else if (!(value = (BLK) parse_qualified_relation()))
					SYNTAX_ERROR(220);	/* Msg220 relation name */
		}
		else if (MATCH(KW_SECURITY_CLASS)) {
			sw = show_security_class;
			value = (BLK) parse_name();
		}
		else if (MATCH(KW_TRIGGERS)) {
			sw = show_triggers;
			if (MATCH(KW_FOR)) {
				if (MATCH(KW_DATABASE)) {
					if (value = (BLK) get_dbb(QLI_token->tok_symbol))
						PAR_token();
					else
						SYNTAX_ERROR(221);	/* Msg221 database name */
				}
				else {
					MATCH(KW_RELATION);
					if (!(value = (BLK) parse_qualified_relation()))
						SYNTAX_ERROR(222);	/* Msg222 relation_name */
					sw = show_trigger;
				}
			}
		}
		else if (MATCH(KW_RELATIONS))
			sw = show_relations;
		else if (MATCH(KW_VIEWS))
			sw = show_views;
		else if (MATCH(KW_SECURITY_CLASSES))
			sw = show_security_classes;
		else if (MATCH(KW_SYSTEM)) {
			if (MATCH(KW_TRIGGERS))
				sw = show_system_triggers;
			else if (MATCH(KW_RELATIONS) ||
					 QLI_token->tok_type == tok_eol ||
					 KEYWORD(KW_SEMI) || KEYWORD(KW_FOR))
				sw = show_system_relations;
			else
				SYNTAX_ERROR(215);	/* Msg215 RELATIONS or TRIGGERS */
		}
		else if (MATCH(KW_PROCEDURES))
			sw = show_procedures;
		else if (MATCH(KW_FILTERS))
			sw = show_filters;
		else if (MATCH(KW_FUNCTIONS))
			sw = show_functions;
#ifdef PYXIS
		else if (MATCH(KW_FORMS)) {
			sw = show_forms;
		}
#endif
		else if (MATCH(KW_GLOBAL)) {
			PAR_real();
			if (MATCH(KW_FIELD)) {
				sw = show_global_field;
				value = (BLK) parse_field_name(0);
			}
			else if (MATCH(KW_FIELDS))
				sw = show_global_fields;
		}
		else if (symbol && symbol->sym_type == SYM_relation) {
			sw = show_relation;
			value = symbol->sym_object;
			PAR_token();
		}
		else if (value = (BLK) get_dbb(symbol)) {
			sw = show_database;
			PAR_token();
			if (MATCH(KW_DOT))
				if (MATCH(KW_RELATIONS))
					sw = show_relations;
				else if (MATCH(KW_FIELDS))
					sw = show_db_fields;
				else if (MATCH(KW_INDICES))
					sw = show_db_indices;
				else if (MATCH(KW_SECURITY_CLASS))
					sw = show_security_class;
				else if (MATCH(KW_TRIGGERS))
					sw = show_triggers;
				else if (MATCH(KW_VIEWS))
					sw = show_views;
				else if (MATCH(KW_SECURITY_CLASSES))
					sw = show_security_classes;
				else if (MATCH(KW_SYSTEM)) {
					if (MATCH(KW_TRIGGERS))
						sw = show_system_triggers;
					else if (MATCH(KW_RELATIONS) ||
							 QLI_token->tok_type == tok_eol ||
							 KEYWORD(KW_SEMI) || KEYWORD(KW_FOR))
						sw = show_system_relations;
					else
						SYNTAX_ERROR(215);	/* Msg215 RELATIONS or TRIGGERS */
				}
				else if (MATCH(KW_PROCEDURES))
					sw = show_procedures;
				else if (MATCH(KW_FILTERS))
					sw = show_filters;
				else if (MATCH(KW_FUNCTIONS))
					sw = show_functions;
#ifdef PYXIS
				else if (MATCH(KW_FORMS))
					sw = show_forms;
#endif
				else if (MATCH(KW_GLOBAL)) {
					PAR_real();
					if (MATCH(KW_FIELD)) {
						sw = show_global_field;
						value = (BLK) parse_field_name(0);
					}
					else if (MATCH(KW_FIELDS))
						sw = show_global_fields;
				}
				else if (relation =
						 resolve_relation(symbol, QLI_token->tok_symbol)) {
					sw = show_relation;
					value = relation->rel_symbol->sym_object;
					PAR_token();
				}
				else {
					sw = show_procedure;
					proc = (QPR) ALLOCD(type_qpr);
					proc->qpr_database = (DBB) value;
					proc->qpr_name = parse_name();
					value = (BLK) proc;
				}
		}
		else {
			sw = show_procedure;
			value = (BLK) parse_qualified_procedure();
		}

		LLS_PUSH(sw, &stack);
		if (!value && (sw == show_relations || sw == show_views ||
					   sw == show_security_classes
					   || sw == show_system_triggers
					   || sw == show_system_relations || sw == show_procedures
#ifdef PYXIS
					   || sw == show_forms 
#endif
					   || sw == show_filters
					   || sw == show_functions || sw == show_global_fields))
			if (MATCH(KW_FOR)) {
				MATCH(KW_DATABASE);
				if (value = (BLK) get_dbb(QLI_token->tok_symbol))
					PAR_token();
				else
					SYNTAX_ERROR(221);	/* Msg221 database name */
			}
		LLS_PUSH(value, &stack);
		count++;
		if (!MATCH(KW_COMMA))
			break;
	}

	command_end();
	node = make_list(stack);
	node->syn_count = count;
	node->syn_type = nod_show;

	return node;
}



static SYN parse_sort(void)
{
/**************************************
 *
 *	p a r s e _ s o r t
 *
 **************************************
 *
 * Functional description
 *	Parse a sort list.
 *
 **************************************/
	SYN node, upcase;
	LLS stack;
	USHORT direction, sensitive;

	direction = sensitive = 0;
	stack = NULL;

	while (TRUE) {
		PAR_real();
		if (!sql_flag) {
			if (MATCH(KW_ASCENDING)) {
				direction = 0;
				continue;
			}
			else if (MATCH(KW_DESCENDING)) {
				direction = 1;
				continue;
			}
			else if (MATCH(KW_EXACTCASE)) {
				sensitive = 0;
				continue;
			}
			else if (MATCH(KW_ANYCASE)) {
				sensitive = 1;
				continue;
			}
		}
		if (sql_flag && QLI_token->tok_type == tok_number) {
			node = SYNTAX_NODE(nod_position, 1);
			node->syn_arg[0] = INT_CAST parse_ordinal();
		}
		else
			node = parse_value(0, 0);
		if (sensitive) {
			upcase = SYNTAX_NODE(nod_upcase, 1);
			upcase->syn_arg[0] = node;
			LLS_PUSH(upcase, &stack);
		}
		else
			LLS_PUSH(node, &stack);
		if (sql_flag)
			if (MATCH(KW_ASCENDING))
				direction = 0;
			else if (MATCH(KW_DESCENDING))
				direction = 1;
		LLS_PUSH(direction, &stack);
		if (!MATCH(KW_COMMA))
			break;
	}

	return make_list(stack);
}


static SYN parse_sql_alter(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ a l t e r
 *
 **************************************
 *
 * Functional description
 *	Parse the leading clauses of a SQL ALTER statement.
 *
 **************************************/
	SYN node;
	QLI_REL relation;
	QLI_FLD field;

	PAR_real_token();

	if (!MATCH(KW_TABLE))
		SYNTAX_ERROR(407);		/* Msg407 TABLE */

	node = SYNTAX_NODE(nod_sql_al_table, 2);
	relation = parse_qualified_relation();
	node->syn_arg[0] = (SYN) relation;

	for (;;) {
		if (MATCH(KW_ADD)) {
			field = parse_sql_field();
			field->fld_flags |= FLD_add;
		}
		else if (MATCH(KW_DROP)) {
			field = parse_field(FALSE);
			field->fld_flags |= FLD_drop;
		}
		else
			SYNTAX_ERROR(405);	/* Msg405 ADD or DROP */

		field->fld_next = (QLI_FLD) node->syn_arg[1];
		node->syn_arg[1] = (SYN) field;

		if (!MATCH(KW_COMMA))
			break;
	}

	command_end();
	return node;

}



static SYN parse_sql_create(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ c r e a t e
 *
 **************************************
 *
 * Functional description
 *	Parse the leading clauses of a SQL CREATE statement.
 *
 **************************************/
	USHORT unique, descending;

	PAR_real_token();

	if (KEYWORD(KW_DATABASE))
		return parse_ready(nod_sql_database);

	if (KEYWORD(KW_UNIQUE) || KEYWORD(KW_ASCENDING)
		|| KEYWORD(KW_DESCENDING) || KEYWORD(KW_INDEX)) {
		unique = descending = FALSE;
		while (TRUE) {
			if (MATCH(KW_UNIQUE))
				unique = TRUE;
			else if (MATCH(KW_ASCENDING))
				descending = FALSE;
			else if (MATCH(KW_DESCENDING))
				descending = TRUE;
			else if (MATCH(KW_INDEX))
				return parse_sql_index_create(unique, descending);
			else
				SYNTAX_ERROR(389);	/* Msg389 INDEX */
		}
	}

	if (MATCH(KW_TABLE))
		return parse_sql_table_create();

#ifdef NOT_USED_OR_REPLACED
/***
if (MATCH (KW_VIEW))
    return parse_sql_view_create();
***/
#endif

	SYNTAX_ERROR(386);			/* Msg386 object type for CREATE */
	return NULL;
}


static int parse_sql_dtype( USHORT * length, USHORT * scale)
{
/**************************************
 *
 *	p a r s e _ s q l _ d t y p e
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL datatype clause.
 *
 **************************************/
	KWWORDS keyword;
	USHORT dtype, l;

	keyword = QLI_token->tok_keyword;
	PAR_token();
	*scale = 0;
	*length = 1;

	switch (keyword) {
	case KW_DATE:
		*length = 8;
		return dtype_timestamp;

	case KW_CHAR:
		dtype = dtype_text;
		break;

	case KW_VARCHAR:
		dtype = dtype_varying;
		break;

	case KW_SMALLINT:
		*length = sizeof(SSHORT);
		return dtype_short;

	case KW_INTEGER:
		*length = sizeof(SLONG);
		return dtype_long;

	case KW_REAL:
	case KW_FLOAT:
		*length = sizeof(float);
		dtype = dtype_real;
		break;

	case KW_LONG:
		if (!MATCH(KW_FLOAT))
			SYNTAX_ERROR(388);	/* Msg388 "FLOAT" */
	case KW_DOUBLE:
	case KW_PRECISION:
		*length = sizeof(double);
		dtype = dtype_double;
		break;

	case KW_DECIMAL:
		*length = sizeof(SLONG);
		dtype = dtype_long;
		break;
	}

	if (dtype == dtype_long || dtype == dtype_real || dtype == dtype_double) {
		if (MATCH(KW_LEFT_PAREN)) {
			l = (MATCH(KW_MINUS)) ? TRUE : FALSE;
			*scale = parse_ordinal();
			if (l)
				*scale = -(*scale);
			parse_matching_paren();
		}
	}
	else if (dtype == dtype_text || dtype == dtype_varying) {
		if (MATCH(KW_LEFT_PAREN)) {
			l = parse_ordinal();
			if (dtype == dtype_varying)
				l += sizeof(SSHORT);
			*length = l;
			parse_matching_paren();
		}
	}

	return dtype;
}


static QLI_FLD parse_sql_field(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Parse a field description.
 *
 **************************************/
	QLI_FLD field;
	SYM name;
	USHORT dtype, length, scale;

	PAR_real();

	dtype = length = scale = 0;
	name = parse_symbol();

	PAR_real();
	switch (QLI_token->tok_keyword) {
	case KW_DOUBLE:
		MATCH(KW_PRECISION);
	case KW_NUMERIC:
	case KW_REAL:
	case KW_DATE:
	case KW_CHAR:
	case KW_VARCHAR:
	case KW_SMALLINT:
	case KW_INTEGER:
	case KW_FLOAT:
	case KW_LONG:
	case KW_DECIMAL:
		dtype = parse_sql_dtype(&length, &scale);
		break;

	default:
		SYNTAX_ERROR(179);		/* Msg179 field definition clause */
		break;
	}

	field = (QLI_FLD) ALLOCDV(type_fld, length);
	field->fld_name = name;
	field->fld_dtype = dtype;
	field->fld_scale = scale;
	field->fld_length = length;

	if (MATCH(KW_NOT))
		if (MATCH(KW_NULL)) {
			field->fld_flags |= FLD_not_null;
		}
		else {
			SYNTAX_ERROR(393);	/* Msg393 NULL */
		}

	return field;
}


static SYN parse_sql_grant_revoke( USHORT type)
{
/**************************************
 *
 *	p a r s e _ s q l _ g r a n t _ r e v o k e
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL GRANT/REVOKE statement.
 *
 **************************************/
	SYN node;
	LLS stack;
	USHORT privileges;

	PAR_real_token();
	node = SYNTAX_NODE((NOD_T) type, s_grant_count);
	stack = NULL;
	privileges = 0;

	if (MATCH(KW_ALL)) {
		MATCH(KW_PRIVILEGES);
		privileges |= PRV_all;
	}
	else
		while (TRUE) {
			PAR_real();
			if (MATCH(KW_SELECT)) {
				privileges |= PRV_select;
				continue;
			}
			else if (MATCH(KW_INSERT)) {
				privileges |= PRV_insert;
				continue;
			}
			else if (MATCH(KW_DELETE)) {
				privileges |= PRV_delete;
				continue;
			}
			else if (MATCH(KW_UPDATE)) {
				privileges |= PRV_update;

				if (MATCH(KW_COMMA))
					continue;

				if (KEYWORD(KW_ON))
					break;

				if (!MATCH(KW_LEFT_PAREN))
					SYNTAX_ERROR(187);	/* Msg187 left parenthesis */

				do {
					if (KEYWORD(KW_SELECT) || KEYWORD(KW_INSERT)
						|| KEYWORD(KW_DELETE) || KEYWORD(KW_UPDATE))
						break;
					PAR_real();
					LLS_PUSH(parse_name(), &stack);

				} while (MATCH(KW_COMMA));

				if (!MATCH(KW_RIGHT_PAREN))
					SYNTAX_ERROR(191);	/* Msg191 right parenthesis */

				continue;
			}

			if (!MATCH(KW_COMMA))
				break;
		}

	node->syn_arg[s_grant_fields] = make_list(stack);

	PAR_real();
	if (!MATCH(KW_ON))
		SYNTAX_ERROR(397);		/* Msg397 ON */

	PAR_real();
	if (!(node->syn_arg[s_grant_relation] = (SYN) parse_qualified_relation()))
		SYNTAX_ERROR(170);		/* Msg170 relation name */

	if (type == (USHORT) nod_sql_grant) {
		if (!MATCH(KW_TO))
			SYNTAX_ERROR(404);	/* Msg404 TO */
	}
	else {
		if (!MATCH(KW_FROM))
			SYNTAX_ERROR(403);	/* Msg403 FROM */
	}

	stack = NULL;

	while (TRUE) {
		PAR_real();
		LLS_PUSH(parse_name(), &stack);
		if (!MATCH(KW_COMMA))
			break;
	}

	node->syn_arg[s_grant_users] = make_list(stack);

	if (type == (USHORT) nod_sql_grant)
		if (MATCH(KW_WITH)) {
			PAR_real();
			if (!MATCH(KW_GRANT))
				SYNTAX_ERROR(401);	/* Msg401 GRANT */
			MATCH(KW_OPTION);
			privileges |= PRV_grant_option;
		}

	node->syn_arg[s_grant_privileges] = INT_CAST privileges;

	return node;
}


static SYN parse_sql_index_create( USHORT unique, USHORT descending)
{
/**************************************
 *
 *	p a r s e _ s q l _ i n d e x _ c r e a t e
 *
 **************************************
 *
 * Functional description
 *	Parse the SQL CREATE INDEX statement.
 *
 **************************************/
	LLS stack;
	SYN node;

	PAR_real();
	node = SYNTAX_NODE(nod_def_index, s_dfi_count);

	if (unique)
		node->syn_flags |= s_dfi_flag_unique;
	if (descending)
		node->syn_flags |= s_dfi_flag_descending;

	node->syn_arg[s_dfi_name] = (SYN) parse_symbol();

	PAR_real();
	if (!MATCH(KW_ON))
		SYNTAX_ERROR(397);		/* Msg397 ON */

	if (!(node->syn_arg[s_dfi_relation] = (SYN) parse_qualified_relation()))
		SYNTAX_ERROR(170);		/* Msg170 relation name */

	PAR_real();

	if (!MATCH(KW_LEFT_PAREN))
		SYNTAX_ERROR(185);		/* Msg185 left parenthesis */

	stack = NULL;

	for (;;) {
		LLS_PUSH(parse_name(), &stack);
		if (MATCH(KW_RIGHT_PAREN))
			break;
		if (!MATCH(KW_COMMA))
			SYNTAX_ERROR(171);	/* Msg171 comma between field definitions */
	}

	node->syn_arg[s_dfi_fields] = make_list(stack);

	command_end();

	return node;
}


static SYN parse_sql_joined_relation( SYN prior_context)
{
/**************************************
 *
 *	p a r s e _ s q l _ j o i n e d _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Parse a join relation clause.
 *
 **************************************/
	SYN left;

	if (MATCH(KW_LEFT_PAREN)) {
		left = parse_sql_joined_relation(0);
		parse_matching_paren();
	}
	else if (!(left = parse_sql_relation()))
		return NULL;

	return parse_sql_join_clause(left);
}


static SYN parse_sql_join_clause( SYN left)
{
/**************************************
 *
 *	p a r s e _ s q l _ j o i n _ c l a u s e
 *
 **************************************
 *
 * Functional description
 *	Parse a join relation clause.
 *
 **************************************/
	SYN right, node;
	NOD_T join_type;

	join_type = parse_join_type();
	if (join_type == (NOD_T) 0)
		return left;

	if (!(right = parse_sql_joined_relation(left)))
		SYNTAX_ERROR(490);		/* Msg490 joined relation clause */

	if (!MATCH(KW_ON))
		SYNTAX_ERROR(492);		/* Msg492 ON */

	node = SYNTAX_NODE(nod_rse, (int) s_rse_count + 2 * 2);
	node->syn_count = 2;
	node->syn_arg[s_rse_count] = left;
	node->syn_arg[s_rse_count + 2] = right;
	node->syn_arg[s_rse_join_type] = (SYN) join_type;
	node->syn_arg[s_rse_boolean] = parse_boolean(0);

	return parse_sql_join_clause(node);
}


static SYN parse_sql_table_create(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ t a b l e _ c r e a t e
 *
 **************************************
 *
 * Functional description
 *	Parse the SQL CREATE TABLE statement.
 *
 **************************************/
	QLI_REL relation;
	QLI_FLD field, *ptr;
	SYN node;

	PAR_real();
	node = SYNTAX_NODE(nod_sql_cr_table, 1);
	relation = (QLI_REL) ALLOCD(type_rel);
	node->syn_arg[0] = (SYN) relation;
	relation->rel_database = parse_database();
	relation->rel_symbol = parse_symbol();

	ptr = &relation->rel_fields;

	if (!MATCH(KW_LEFT_PAREN))
		SYNTAX_ERROR(185);		/* Msg185 left parenthesis */

	PAR_real();

	for (;;) {
		*ptr = field = parse_sql_field();
		ptr = &field->fld_next;
		if (MATCH(KW_RIGHT_PAREN))
			break;
		if (!MATCH(KW_COMMA))
			SYNTAX_ERROR(171);	/* Msg171 comma between field definitions */
	}

	command_end();

	return node;
}

#ifdef NOT_USED_OR_REPLACED
static SYN parse_sql_view_create(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ v i e w _ c r e a t e
 *
 **************************************
 *
 * Functional description
 *	Parse the SQL CREATE VIEW statement.
 *
 **************************************/
	SYN node;
	LLS stack;
	QLI_REL relation;

	PAR_real();

	sw_sql_view = TRUE;
	node = SYNTAX_NODE(nod_sql_cr_view, s_crv_count);
	stack = NULL;

	relation = (QLI_REL) ALLOCD(type_rel);
	node->syn_arg[s_crv_name] = (SYN) relation;
	relation->rel_database = parse_database();
	relation->rel_symbol = parse_symbol();

/* if field list is present parse it and create corresponding field blocks */

	if (MATCH(KW_LEFT_PAREN)) {
		for (;;) {
			LLS_PUSH(parse_name(), &stack);
			if (MATCH(KW_RIGHT_PAREN))
				break;
			if (!MATCH(KW_COMMA))
				SYNTAX_ERROR(171);	/* Msg171 comma between field definitions */
		}
	}

/* node->syn_arg [s_crv_fields] = make_list (stack); */

	if (!MATCH(KW_AS))
		SYNTAX_ERROR(394);		/* Msg394 As */

	if (!KEYWORD(KW_SELECT))
		SYNTAX_ERROR(395);		/* Msg395 Select */

	node->syn_arg[s_crv_rse] = parse_select();

	sw_sql_view = FALSE;

	return node;
}
#endif

static SYN parse_sql_relation(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL relation clause.
 *
 **************************************/
	SYN node;

	node = SYNTAX_NODE(nod_relation, s_rel_count);

	if (!(node->syn_arg[s_rel_relation] = (SYN) parse_qualified_relation()))
		SYNTAX_ERROR(223);		/* Msg223 relation name */

	if (!QLI_token->tok_symbol)
		node->syn_arg[s_rel_context] = (SYN) parse_symbol();

	return node;
}


static SYN parse_sql_rse(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ r s e
 *
 **************************************
 *
 * Functional description
 *	Parse the trailing clauses of a SQL SELECT statement.
 *
 **************************************/
	SYN node, *ptr;
	LLS stack;
	USHORT count;

	stack = NULL;
	count = 0;
	PAR_real();

	if (!MATCH(KW_FROM))
		SYNTAX_ERROR(224);		/* Msg224 FROM clause */

/* Parse FROM list of relations */

	while (TRUE) {
		count++;
		LLS_PUSH(parse_sql_joined_relation(0), &stack);
		if (!MATCH(KW_COMMA))
			break;
	}

/* Build a syntax node.  Since SQL doesn't support OVER, only every
   other slot will be used in the RSE. */

	node = SYNTAX_NODE(nod_rse, (int) s_rse_count + 2 * count);
	node->syn_count = count;
	ptr = &node->syn_arg[(int) s_rse_count + 2 * count];

	while (stack) {
		--ptr;
		*--ptr = (SYN) LLS_POP(&stack);
	}

	if (MATCH(KW_WITH))
		node->syn_arg[s_rse_boolean] = parse_boolean(0);

	if (MATCH(KW_GROUP)) {
		if (sw_sql_view)
			SYNTAX_ERROR(391);	/* Msg391 No group by in view def */
		PAR_real();
		MATCH(KW_BY);
		stack = NULL;
		while (TRUE) {
			LLS_PUSH(parse_udf_or_field(), &stack);
			if (!MATCH(KW_COMMA))
				break;
		}
		node->syn_arg[s_rse_group_by] = make_list(stack);
		if (MATCH(KW_HAVING))
			node->syn_arg[s_rse_having] = parse_boolean(0);
	}

	return node;
}


static SYN parse_sql_singleton_select(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ s i n g l e t o n _ s e l e c t
 *
 **************************************
 *
 * Functional description
 *	Finish parsing an SQL singleton select and
 *	turn it into a FIRST ... FROM --- not exactly
 *	kosher, but a start.
 *
 **************************************/
	SYN node, value;

	value = parse_primitive_value(0, 0);
	PAR_real();

	node = SYNTAX_NODE(nod_from, s_stt_count);
	node->syn_arg[s_stt_value] = value;

	node->syn_arg[s_stt_rse] = parse_sql_rse();
	--sql_flag;

	return node;
}


static SYN parse_sql_subquery(void)
{
/**************************************
 *
 *	p a r s e _ s q l _ s u b q u e r y
 *
 **************************************
 *
 * Functional description
 *	Parse an sql subquery that should
 *	return a single value.
 *
 **************************************/
	struct nod_types *types;
	SYN node;
	KWWORDS keyword;

	if (sw_sql_view)
		SYNTAX_ERROR(392);		/* Msg392 No aggregates in view def */

	PAR_token();

	keyword = next_keyword();
	++sql_flag;

	for (types = statisticals;; types++)
		if (types->nod_t_keyword == KW_none)
			return parse_sql_singleton_select();
		else if (types->nod_t_keyword == keyword)
			break;

	PAR_token();
	node = SYNTAX_NODE(types->nod_t_node, s_stt_count);

	MATCH(KW_LEFT_PAREN);

	if (node->syn_type != nod_count || !MATCH(KW_ASTERISK)) {
		if (MATCH(KW_DISTINCT))
			node->syn_arg[s_prt_distinct] = INT_CAST TRUE;
		node->syn_arg[s_stt_value] = parse_value(0, 0);
	}

	parse_matching_paren();

	node->syn_arg[s_stt_rse] = parse_sql_rse();
	--sql_flag;

	return node;
}


static SYN parse_statement(void)
{
/**************************************
 *
 *	p a r s e _ s t a t e m e n t
 *
 **************************************
 *
 * Functional description
 *	Parse a statement.  (Set statement switch
 *	to true here as well as in PARQ_parse to
 *	avoid confusion with linked statements 
 *	e.g. THEN conjuncts )
 *
 **************************************/
	SYN node;
	LLS stack;

	PAR_real();
	sw_statement = TRUE;
	function_count = 0;

	switch (next_keyword()) {
	case KW_ABORT:
		node = parse_abort();
		break;

	case KW_ACCEPT:
		node = parse_accept();
		break;

	case KW_COMMIT:
		node = parse_transaction(nod_commit_retaining);
		break;

	case KW_DECLARE:
		node = parse_declare();
		break;

	case KW_DELETE:
		MATCH(KW_DELETE);
		node = parse_delete();
		break;

	case KW_ERASE:
		node = parse_erase();
		break;

	case KW_FOR:
		node = parse_for();
		break;

	case KW_IF:
		node = parse_if();
		break;

	case KW_INSERT:
		node = parse_insert();
		break;

	case KW_LIST:
		node = parse_list_fields();
		break;

	case KW_MENU:
		node = parse_menu();
		break;

	case KW_MODIFY:
		node = parse_modify();
		break;

	case KW_PRINT:
		node = parse_print();
		break;

	case KW_REPEAT:
		node = parse_repeat();
		break;

	case KW_REPORT:
		node = parse_report();
		break;

	case KW_SELECT:
		node = parse_select();
		break;

	case KW_STORE:
		node = parse_store();
		break;

	case KW_UPDATE:
		node = parse_update();
		break;

	case KW_BEGIN:
		stack = NULL;
		PAR_token();
		while (TRUE) {
			PAR_real();
			if (MATCH(KW_END))
				break;
			LLS_PUSH(parse_statement(), &stack);
			MATCH(KW_SEMI);
		}
		node = make_list(stack);
		break;

	default:
		node = parse_assignment();
	}

	check_end();

/* Check for the "THEN" connective.  If found, make a list of statements. */

	if (QLI_token->tok_type != tok_eol || (QLI_semi && !KEYWORD(KW_SEMI)))
		MATCH(KW_SEMI);

	if (!MATCH(KW_THEN))
		return node;

	stack = NULL;
	LLS_PUSH(node, &stack);
	LLS_PUSH(parse_statement(), &stack);

	return make_list(stack);
}


static SYN parse_statistical(void)
{
/**************************************
 *
 *	p a r s e _ s t a t i s t i c a l
 *
 **************************************
 *
 * Functional description
 *	Parse statistical expression.
 *
 **************************************/
	struct nod_types *types;
	SYN node;
	KWWORDS keyword;

	keyword = next_keyword();
	PAR_token();

	for (types = statisticals;; types++)
		if (types->nod_t_keyword == keyword)
			break;

/* Handle SQL statisticals a little differently */

	if (sql_flag) {
		node = SYNTAX_NODE(types->nod_t_sql_node, s_stt_count);
		if (!MATCH(KW_LEFT_PAREN))
			SYNTAX_ERROR(227);	/* Msg227 left parenthesis */
		if (node->syn_type != nod_agg_count || !MATCH(KW_ASTERISK)) {
			if (MATCH(KW_DISTINCT))
				node->syn_arg[s_prt_distinct] = INT_CAST TRUE;
			node->syn_arg[s_stt_value] = parse_value(0, 0);
		}
		parse_matching_paren();
		return node;
	}

/* Handle GDML statisticals */

	node = SYNTAX_NODE(types->nod_t_node, s_stt_count);

	if (node->syn_type != nod_count)
		node->syn_arg[s_stt_value] = parse_value(0, 0);

	if (!MATCH(KW_OF)) {
		if (sw_report) {
			if (function_count > 0)
				IBERROR(487);	/* Msg487 Invalid argument for UDF */
			node->syn_type = types->nod_t_rpt_node;
			return node;
		}
		PAR_real();
		if (!MATCH(KW_OF))
			SYNTAX_ERROR(228);	/* Msg 228 OF */
	}

	node->syn_arg[s_stt_rse] = parse_rse();

	return node;
}


static SYN parse_store(void)
{
/**************************************
 *
 *	p a r s e _ s t o r e
 *
 **************************************
 *
 * Functional description
 *	Parse a STORE statement.
 *
 **************************************/
	SYN node;

	PAR_token();
	node = SYNTAX_NODE(nod_store, s_sto_count);
	node->syn_arg[s_sto_relation] = parse_relation();

	if (test_end())
		return node;

	MATCH(KW_USING);

#ifdef PYXIS
	if (MATCH(KW_FORM))
		IBERROR(484);			/* FORMs not supported */
	else
#endif
		node->syn_arg[s_sto_statement] = parse_statement();

	return node;
}


static TEXT *parse_string(void)
{
/**************************************
 *
 *	p a r s e _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Save the current token as a string, advance to the next
 *	token, and return a pointer to the string.
 *
 **************************************/
	TEXT *string;

	string = make_string(QLI_token->tok_string, QLI_token->tok_length);
	PAR_token();

	return string;
}


static SYM parse_symbol(void)
{
/**************************************
 *
 *	p a r s e _ s y m b o l
 *
 **************************************
 *
 * Functional description
 *	Parse the next token as a context symbol.
 *
 **************************************/
	SYM context;
	USHORT l;
	TEXT c, *p, *q;

	l = QLI_token->tok_length;
	context = (SYM) ALLOCDV(type_sym, l);
	context->sym_type = SYM_context;
	context->sym_length = l;
	q = QLI_token->tok_string;
	p = context->sym_string = context->sym_name;

	if (l)
		do {
			c = *q++;
			*p++ = UPPER(c);
		} while (--l);

	PAR_token();

	return context;
}


static void parse_terminating_parens( USHORT * paren_count, USHORT * local_count)
{
/**************************************
 *
 *	p a r s e _ t e r m i n a t i n g _ p a r e n s
 *
 **************************************
 *
 * Functional description
 *	Check for balancing parenthesis.  If missing, barf.
 *
 **************************************/

	if (*paren_count && paren_count == local_count)
		do
			parse_matching_paren();
		while (--(*paren_count));
}


static SYN parse_transaction( NOD_T node_type)
{
/**************************************
 *
 *	p a r s e _ t r a n s a c t i o n
 *
 **************************************
 *
 * Functional description
 *	Parse the FINISH, COMMIT, ROLLBACK,
 *	and PREPARE commands and the COMMIT statement.
 *
 **************************************/
	SYN node;
	LLS stack;
	SYM symbol;

	stack = NULL;
	PAR_token();

	if (!KEYWORD(KW_SEMI))
		while (TRUE) {
			for (symbol = QLI_token->tok_symbol; symbol;
				 symbol = symbol->sym_homonym)
					if (symbol->sym_type == SYM_database) break;
			if (!symbol)
				SYNTAX_ERROR(229);	/* Msg229 database name */
			LLS_PUSH(symbol->sym_object, &stack);
			PAR_token();
			if (!MATCH(KW_COMMA))
				break;
		}

	command_end();
	node = make_list(stack);
	node->syn_type = node_type;

	return node;
}


static SYN parse_udf_or_field(void)
{
/**************************************
 *
 *	p a r s e _ u d f _ o r _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Parse a function or field reference.
 *
 **************************************/
	SYM symbol;

	if ((symbol = QLI_token->tok_symbol) && symbol->sym_type == SYM_function)
		return parse_function();

	return parse_field_name(0);
}


static SYN parse_update(void)
{
/**************************************
 *
 *	p a r s e _ u p d a t e
 *
 **************************************
 *
 * Functional description
 *	Parse a SQL UPDATE statement.
 *
 **************************************/
	SYN node, rse;
	LLS stack;

	++sql_flag;
	PAR_token();
	node = SYNTAX_NODE(nod_modify, s_mod_count);
	node->syn_arg[s_mod_rse] = rse =
		SYNTAX_NODE(nod_rse, (int) s_rse_count + 2);
	rse->syn_count = 1;
	rse->syn_arg[s_rse_count] = parse_sql_relation();

	if (!MATCH(KW_SET))
		SYNTAX_ERROR(230);		/* Msg230 SET */

/* Pick up assignments */

	stack = NULL;

	while (TRUE) {
		LLS_PUSH(parse_assignment(), &stack);
		if (!MATCH(KW_COMMA))
			break;
	}

/* Pick up boolean, if present */

	if (MATCH(KW_WITH))
		rse->syn_arg[s_rse_boolean] = parse_boolean(0);

	node->syn_arg[s_mod_statement] = make_list(stack);
	--sql_flag;

	return node;
}


static SYN parse_value( USHORT * paren_count, USHORT * bool_flag)
{
/**************************************
 *
 *	p a r s e _ v a l u e
 *
 **************************************
 *
 * Functional description
 *	Parse a general value expression.  In practice, this means parse the
 *	lowest precedence operatr CONCATENATE.
 *
 **************************************/
	SYN node, arg;
	USHORT local_count, local_flag;

	if (!paren_count) {
		local_count = 0;
		paren_count = &local_count;
	}
	if (!bool_flag) {
		local_flag = FALSE;
		bool_flag = &local_flag;
	}

	node = parse_add(paren_count, bool_flag);

	while (TRUE) {
		if (!MATCH(KW_BAR)) {
			parse_terminating_parens(paren_count, &local_count);
			return node;
		}
		arg = node;
		node = SYNTAX_NODE(nod_concatenate, 2);
		node->syn_arg[0] = arg;
		node->syn_arg[1] = parse_add(paren_count, bool_flag);
	}
}


static int potential_rse(void)
{
/**************************************
 *
 *	p o t e n t i a l _ r s e
 *
 **************************************
 *
 * Functional description
 *	Test to see if the current token is likely (sic!) to be part of
 *	a record selection expression.
 *
 **************************************/
	SYM symbol;

	for (symbol = QLI_token->tok_symbol; symbol; symbol = symbol->sym_homonym)
		if ((symbol->sym_type == SYM_keyword &&
			 symbol->sym_keyword == (int) KW_FIRST) ||
			symbol->sym_type == SYM_relation ||
			symbol->sym_type == SYM_database) return TRUE;

	return FALSE;
}


static QLI_REL resolve_relation( SYM db_symbol, SYM relation_symbol)
{
/**************************************
 *
 *	r e s o l v e _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Given symbols for a database and a relation (either may be null),
 *	resolve the relation.  If the relation can't be resolved, return
 *	NULL (don't error!).
 *
 **************************************/
	QLI_REL relation;
	SYM temp;
	DBB dbb;

/* If we don't recognize the relation, punt. */

	if (!relation_symbol)
		return NULL;

/* If a database symbol is present, resolve the relation against the
   the given database. */

	if (db_symbol) {			/* && db_symbol->sym_type == SYM_database ?  */
		for (; db_symbol; db_symbol = db_symbol->sym_homonym)
			for (temp = relation_symbol; temp; temp = temp->sym_homonym)
				if (temp->sym_type == SYM_relation) {
					relation = (QLI_REL) temp->sym_object;
					if (relation->rel_database == (DBB) db_symbol->sym_object)
						return relation;
				}
		return NULL;
	}

/* No database qualifier, so search all databases. */

	for (dbb = QLI_databases; dbb; dbb = dbb->dbb_next)
		for (temp = relation_symbol; temp; temp = temp->sym_homonym)
			if (temp->sym_type == SYM_relation) {
				relation = (QLI_REL) temp->sym_object;
				if (relation->rel_database == dbb)
					return relation;
			}

	return NULL;
}


static SYN syntax_node( NOD_T type, USHORT count)
{
/**************************************
 *
 *	s y n t a x _ n o d e
 *
 **************************************
 *
 * Functional description
 *	Allocate and initialize a syntax node of given type.
 *
 **************************************/
	SYN node;

	node = (SYN) ALLOCDV(type_syn, count);
	node->syn_type = type;
	node->syn_count = count;

	return node;
}


static int test_end(void)
{
/**************************************
 *
 *	t e s t _ e n d
 *
 **************************************
 *
 * Functional description
 *	Test for end of a statement.  In specific, test for one of
 *	THEN, ELSE, ON, or a semi-colon.
 *
 **************************************/

	if (KEYWORD(KW_THEN) ||
		KEYWORD(KW_ON) || KEYWORD(KW_ELSE) || KEYWORD(KW_SEMI)) return TRUE;

	return FALSE;
}
