Logo Search packages:      
Sourcecode: firebird3.0 version File versions  Download package

expand.cpp

/*
 *    PROGRAM:    JRD Command Oriented Query Language
 *    MODULE:           expand.cpp
 *    DESCRIPTION:      Expand syntax tree -- first phase of compiler
 *
 * 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 <string.h>
#include "../qli/dtr.h"
#include "../qli/parse.h"
#include "../qli/compile.h"
#include "../qli/exe.h"
#include "../qli/report.h"
#include "../qli/all_proto.h"
#include "../qli/comma_proto.h"
#include "../qli/compi_proto.h"
#include "../qli/err_proto.h"
#include "../qli/expan_proto.h"
#include "../qli/help_proto.h"
#include "../qli/meta_proto.h"
#include "../qli/show_proto.h"

using MsgFormat::SafeArg;


static bool compare_names(const qli_name*, const qli_symbol*);
static bool compare_symbols(const qli_symbol*, const qli_symbol*);
static qli_symbol* copy_symbol(const qli_symbol*);
static void declare_global(qli_fld*, qli_syntax*);
static qli_syntax* decompile_field(qli_fld*, qli_ctx*);
static qli_name* decompile_symbol(qli_symbol*);
static qli_nod* expand_assignment(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_any(qli_syntax*, qli_lls*);
static qli_nod* expand_boolean(qli_syntax*, qli_lls*);
static void expand_control_break(qli_brk**, qli_lls*);
static void expand_distinct(qli_nod*, qli_nod*);
static void expand_edit_string(qli_nod*, qli_print_item*);
static qli_nod* expand_erase(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_expression(qli_syntax*, qli_lls*);
static qli_nod* expand_field(qli_syntax*, qli_lls*, qli_syntax*);
static qli_nod* expand_for(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_function(qli_syntax*, qli_lls*);
static qli_nod* expand_group_by(qli_syntax*, qli_lls*, qli_ctx*);
static qli_nod* expand_modify(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_output(qli_syntax*, qli_lls*, qli_prt**);
static qli_nod* expand_print(qli_syntax*, qli_lls*, qli_lls*);
static qli_print_item* expand_print_item(qli_syntax*, qli_lls*);
static qli_nod* expand_print_list(qli_syntax*, qli_lls*);
static qli_nod* expand_report(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_restructure(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_rse(qli_syntax*, qli_lls**);
static qli_nod* expand_sort(qli_syntax*, qli_lls*, qli_nod*);
static qli_nod* expand_statement(qli_syntax*, qli_lls*, qli_lls*);
static qli_nod* expand_store(qli_syntax*, qli_lls*, qli_lls*);
static void expand_values(qli_syntax*, qli_lls*);
static qli_ctx* find_context(const qli_name*, qli_lls*);
static int generate_fields(qli_ctx*, qli_lls*, qli_syntax*);
static int generate_items(const qli_syntax*, qli_lls*, qli_lls*, qli_nod*);
static bool global_agg(const qli_syntax*, const qli_syntax*);
static bool invalid_nod_field(const qli_nod*, const qli_nod*);
static bool invalid_syn_field(const qli_syntax*, const qli_syntax*);
static qli_nod* make_and(qli_nod*, qli_nod*);
static qli_nod* make_assignment(qli_nod*, qli_nod*, qli_lls*);
static qli_nod* make_field(qli_fld*, qli_ctx*);
static qli_nod* make_list(qli_lls*);
static qli_nod* make_node(nod_t, USHORT);
static qli_nod* negate(qli_nod*);
static qli_nod* possible_literal(qli_syntax*, qli_lls*, bool);
static qli_nod* post_map(qli_nod*, qli_ctx*);
static qli_fld* resolve(qli_syntax*, qli_lls*, qli_ctx**);
static void resolve_really(qli_fld*, const qli_syntax*);

static qli_lls* global_output_stack;


qli_nod* EXP_expand( qli_syntax* node)
{
/**************************************
 *
 *    E X P _ e x p a n d
 *
 **************************************
 *
 * Functional description
 *    Expand a syntax tree into something richer and more complete.
 *
 **************************************/
      switch (node->syn_type)
      {
      case nod_commit:
      case nod_prepare:
      case nod_rollback:
            CMD_transaction(node);
            return NULL;

      case nod_copy_proc:
            CMD_copy_procedure(node);
            return NULL;

      case nod_declare:
            declare_global((qli_fld*) node->syn_arg[0], node->syn_arg[1]);
            return NULL;

      case nod_define:
            CMD_define_procedure(node);
            return NULL;

      case nod_delete_proc:
            CMD_delete_proc(node);
            return NULL;

      case nod_def_database:
      case nod_sql_database:
            MET_ready(node, true);
            return NULL;

      case nod_def_field:
            MET_define_field((qli_dbb*) node->syn_arg[0], (qli_fld*) node->syn_arg[1]);
            return NULL;

      case nod_def_index:
            MET_define_index(node);
            return NULL;

      case nod_def_relation:
            MET_define_relation((qli_rel*)node->syn_arg[0], (qli_rel*) node->syn_arg[1]);
            return NULL;

      case nod_del_relation:
            MET_delete_relation((qli_rel*)node->syn_arg[0]);
            return NULL;

      case nod_del_field:
            MET_delete_field((qli_dbb*)node->syn_arg[0], (qli_name*) node->syn_arg[1]);
            return NULL;

      case nod_del_index:
            MET_delete_index((qli_dbb*)node->syn_arg[0], (qli_name*) node->syn_arg[1]);
            return NULL;

      case nod_del_database:
            MET_delete_database((qli_dbb*)node->syn_arg[0]);
            return NULL;

      case nod_edit_proc:
            CMD_edit_proc(node);
            return NULL;
      case nod_extract:
            node->syn_arg[1] = (qli_syntax*) expand_output(node->syn_arg[1], 0, 0);
            CMD_extract(node);
            return NULL;

      case nod_finish:
            CMD_finish(node);
            return NULL;

      case nod_help:
            HELP_help(node);
            return NULL;

      case nod_mod_field:
            MET_modify_field((qli_dbb*)node->syn_arg[0], (qli_fld*) node->syn_arg[1]);
            return NULL;

      case nod_mod_relation:
            MET_modify_relation((qli_rel*) node->syn_arg[0], (qli_fld*) node->syn_arg[1]);
            return NULL;

      case nod_mod_index:
            MET_modify_index(node);
            return NULL;

      case nod_ready:
            MET_ready(node, false);
            return NULL;

      case nod_rename_proc:
            CMD_rename_proc(node);
            return NULL;

      case nod_set:
            CMD_set(node);
            return NULL;

      case nod_show:
            SHOW_stuff(node);
            return NULL;

      case nod_shell:
            CMD_shell(node);
            return NULL;

      case nod_sql_grant:
            MET_sql_grant(node);
            return NULL;

      case nod_sql_revoke:
            MET_sql_revoke(node);
            return NULL;

      case nod_sql_cr_table:
            MET_define_sql_relation((qli_rel*) node->syn_arg[0]);
            return NULL;

    //case nod_sql_cr_view:
      //    MET_sql_cr_view (node);
      //    GEN_release();
      //    return NULL;

      case nod_sql_al_table:
            MET_sql_alter_table((qli_rel*) node->syn_arg[0], (qli_fld*) node->syn_arg[1]);
            return NULL;
      } // end switch, no default case for error

      // If there are any variables, make up a context now

      global_output_stack = NULL;
      qli_lls* right = NULL;
      qli_lls* left = NULL;

      if (QLI_variables)
      {
            qli_ctx* context = (qli_ctx*) ALLOCD(type_ctx);
            context->ctx_type = CTX_VARIABLE;
            context->ctx_variable = QLI_variables;
            ALLQ_push((blk*) context, &right);
            ALLQ_push((blk*) context, &left);
      }

      qli_nod* expanded = expand_statement(node, right, left);
      if (!expanded)
            return NULL;

      while (global_output_stack)
      {
            qli_nod* output = (qli_nod*) ALLQ_pop(&global_output_stack);
            output->nod_arg[e_out_statement] = expanded;
            expanded = output;
      }

      return expanded;
}


static bool compare_names( const qli_name* name, const qli_symbol* symbol)
{
/**************************************
 *
 *    c o m p a r e _ n a m e s
 *
 **************************************
 *
 * Functional description
 *    Compare a name node to a symbol.  If they are equal, return true.
 *
 **************************************/
      if (!symbol)
            return false;

      const int l = name->nam_length;
      if (l != symbol->sym_length)
            return false;

      if (l)
            return memcmp(symbol->sym_string, name->nam_string, l) == 0;

      return true;
}


static bool compare_symbols( const qli_symbol* symbol1, const qli_symbol* symbol2)
{
/**************************************
 *
 *    c o m p a r e _ s y m b o l s
 *
 **************************************
 *
 * Functional description
 *    Compare two symbols (either may be 0).
 *
 **************************************/
      if (!symbol1 || !symbol2)
            return false;

      const int l = symbol1->sym_length;
      if (l != symbol2->sym_length)
            return false;

      if (l)
            return memcmp(symbol1->sym_string, symbol2->sym_string, l) == 0;

      return true;
}


static qli_symbol* copy_symbol( const qli_symbol* old)
{
/**************************************
 *
 *    c o p y _ s y m b o l
 *
 **************************************
 *
 * Functional description
 *    Copy a symbol into the permanent pool.
 *
 **************************************/
      qli_symbol* new_sym = (qli_symbol*) ALLOCPV(type_sym, old->sym_length);
      new_sym->sym_length = old->sym_length;
      new_sym->sym_type = old->sym_type;
      new_sym->sym_string = new_sym->sym_name;
      strcpy(new_sym->sym_name, old->sym_name);

      return new_sym;
}


static void declare_global( qli_fld* variable, qli_syntax* field_node)
{
/**************************************
 *
 *    d e c l a r e _ g l o b a l
 *
 **************************************
 *
 * Functional description
 *    Copy a variable block into the permanent pool as a field.
 *    Resolve BASED_ON references.
 *    Allocate all strings off the field block.
 *
 **************************************/

      // If it's based_on, flesh it out & check datatype.

      if (field_node)
      {
            if (field_node->syn_type == nod_index)
                  field_node = field_node->syn_arg[s_idx_field];
            resolve_really(variable, field_node);
            if (variable->fld_dtype == dtype_blob)
                  IBERROR(137);           // Msg137 variables may not be based on blob fields.
      }

      // Get rid of any other variables of the same name

      qli_fld* field;
      for (qli_fld** ptr = &QLI_variables; field = *ptr; ptr = &field->fld_next)
            if (!strcmp(field->fld_name->sym_string, variable->fld_name->sym_string))
            {
                  *ptr = field->fld_next;
                  ALLQ_release((qli_frb*) field->fld_name);
                  if (field->fld_query_name)
                        ALLQ_release((qli_frb*) field->fld_query_name);
                  ALLQ_release((qli_frb*) field);
                  break;
            }

      // Next, copy temporary field block into permanent pool.  Fold edit_string
      //   query_header into main block to save space and complexity.

      const TEXT* q;
      USHORT l = variable->fld_length;
      if (q = variable->fld_edit_string)
            l += strlen(q);
      if (q = variable->fld_query_header)
            l += strlen(q);

      qli_fld* new_fld = (qli_fld*) ALLOCPV(type_fld, l);
      new_fld->fld_name = copy_symbol(variable->fld_name);
      new_fld->fld_dtype = variable->fld_dtype;
      new_fld->fld_length = variable->fld_length;
      new_fld->fld_scale = variable->fld_scale;
      new_fld->fld_sub_type = variable->fld_sub_type;
      new_fld->fld_sub_type_missing = variable->fld_sub_type_missing;
      new_fld->fld_flags = variable->fld_flags | FLD_missing;

      // Copy query_name, edit string, query header

      TEXT* p = (TEXT*) new_fld->fld_data + new_fld->fld_length;
      if (q = variable->fld_edit_string)
      {
            new_fld->fld_edit_string = p;
            while (*p++ = *q++);
      }
      if (variable->fld_query_name)
            new_fld->fld_query_name = copy_symbol(variable->fld_query_name);
      if (q = variable->fld_query_header)
      {
            new_fld->fld_query_header = p;
            while (*p++ = *q++);
      }

      // Link new variable into variable chain

      new_fld->fld_next = QLI_variables;
      QLI_variables = new_fld;
}


static qli_syntax* decompile_field( qli_fld* field, qli_ctx* context)
{
/**************************************
 *
 *    d e c o m p i l e _ f i e l d
 *
 **************************************
 *
 * Functional description
 *    Take a perfectly good, completely compiled
 *    field block and regress to a qli_syntax node and
 *    and a qli_name block.
 *    (Needed to support SQL idiocies)
 *
 **************************************/
      const int args = context ? 2 : 1;

      qli_syntax* node = (qli_syntax*) ALLOCDV(type_syn, args);
      node->syn_type = nod_field;
      node->syn_count = args;

      qli_name* name = decompile_symbol(field->fld_name);
      node->syn_arg[0] = (qli_syntax*) name;

      if (context)
      {
            node->syn_arg[1] = node->syn_arg[0];
            if (context->ctx_symbol)
                  name = decompile_symbol(context->ctx_symbol);
            else
                  name = decompile_symbol(context->ctx_relation->rel_symbol);
            node->syn_arg[0] = (qli_syntax*) name;
      }

      return node;
}


static qli_name* decompile_symbol( qli_symbol* symbol)
{
/**************************************
 *
 *    d e c o m p i l e _ s y m b o l
 *
 **************************************
 *
 * Functional description
 *    Turn a symbol back into a name
 *    (Needed to support SQL idiocies)
 *
 **************************************/
      const int l = symbol->sym_length;

      qli_name* name = (qli_name*) ALLOCDV(type_nam, l);
      name->nam_length = l;
      name->nam_symbol = symbol;
      if (l)
            memcpy(name->nam_string, symbol->sym_string, l);

      /*
      TEXT* p = name->nam_string;
      const TEXT* q = symbol->sym_string;

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

            } while (--l);
      */

      return name;
}


static qli_nod* expand_assignment( qli_syntax* input, qli_lls* right, qli_lls* left)
{
/**************************************
 *
 *    e x p a n d _ a s s i g n m e n t
 *
 **************************************
 *
 * Functional description
 *    Expand an assigment statement.  All in all, not too tough.
 *
 **************************************/
      qli_nod* node = make_node(input->syn_type, e_asn_count);
      qli_nod* to = expand_expression(input->syn_arg[s_asn_to], left);
      node->nod_arg[e_asn_to] = to;
      qli_nod* from = expand_expression(input->syn_arg[s_asn_from], right);
      node->nod_arg[e_asn_from] = from;

      if (to->nod_type == nod_field || to->nod_type == nod_variable)
      {
            qli_fld* field = (qli_fld*) to->nod_arg[e_fld_field];
            if (field->fld_flags & FLD_computed)
            {
                  ERRQ_print_error(138, field->fld_name->sym_string);
                  // Msg138 can't do assignment to computed field
            }
            if (from->nod_type == nod_prompt)
                  from->nod_arg[e_prm_field] = to->nod_arg[e_fld_field];
            if (field->fld_validation)
                  node->nod_arg[e_asn_valid] = expand_expression(field->fld_validation, left);
      }

      if (!node->nod_arg[e_asn_valid])
            --node->nod_count;

      return node;
}


static qli_nod* expand_any( qli_syntax* input, qli_lls* stack)
{
/**************************************
 *
 *    e x p a n d _ a n y
 *
 **************************************
 *
 * Functional description
 *    Expand an any expression.  This would be trivial were it not
 *    for a funny SQL case when an expression needs to be checked
 *    for existence.
 *
 **************************************/
      qli_nod* node = make_node(input->syn_type, e_any_count);
      node->nod_count = 0;
      qli_nod* rse = expand_rse(input->syn_arg[0], &stack);
      node->nod_arg[e_any_rse] = rse;

      if (input->syn_count >= 2 && input->syn_arg[1])
      {
            qli_nod* boolean = make_node(nod_missing, 1);
            boolean->nod_arg[0] = expand_expression(input->syn_arg[1], stack);
            qli_nod* negation = make_node(nod_not, 1);
            negation->nod_arg[0] = boolean;
            rse->nod_arg[e_rse_boolean] = make_and(rse->nod_arg[e_rse_boolean], negation);
      }

      return node;
}


static qli_nod* expand_boolean( qli_syntax* input, qli_lls* stack)
{
/**************************************
 *
 *    e x p a n d _ b o o l e a n
 *
 **************************************
 *
 * Functional description
 *    Expand a statement.
 *
 **************************************/
      // Make node and process arguments

      qli_nod* node = make_node(input->syn_type, input->syn_count);
      qli_nod** ptr = node->nod_arg;
      qli_nod* value = expand_expression(input->syn_arg[0], stack);
      *ptr++ = value;

      for (int i = 1; i < input->syn_count; i++, ptr++)
      {
            if (!(*ptr = possible_literal(input->syn_arg[i], stack, true)))
                  *ptr = expand_expression(input->syn_arg[i], stack);
      }

      // Try to match any prompts against fields to determine prompt length

      if (value->nod_type != nod_field)
            return node;

      qli_fld* field = (qli_fld*) value->nod_arg[e_fld_field];
      ptr = &node->nod_arg[1];

      for (int i = 1; i < node->nod_count; i++, ptr++)
      {
            if ((*ptr)->nod_type == nod_prompt)
                  (*ptr)->nod_arg[e_prm_field] = (qli_nod*) field;
      }

      return node;
}


static void expand_control_break( qli_brk** ptr, qli_lls* right)
{
/**************************************
 *
 *    e x p a n d _ c o n t r o l _ b r e a k
 *
 **************************************
 *
 * Functional description
 *    Work on a report writer control break.  This is called recursively
 *    to handle multiple breaks.
 *
 **************************************/
      qli_brk* list = NULL;

      qli_brk* control;
      while (control = *ptr)
      {
            *ptr = control->brk_next;
            control->brk_next = list;
            list = control;
            if (control->brk_field)
                  control->brk_field = (qli_syntax*) expand_expression(control->brk_field, right);
            if (control->brk_line)
                  control->brk_line = (qli_syntax*) expand_print_list(control->brk_line, right);
      }

      *ptr = list;
}


static void expand_distinct( qli_nod* rse, qli_nod* node)
{
/**************************************
 *
 *    e x p a n d _ d i s t i n c t
 *
 **************************************
 *
 * Functional description
 *    We have run into a distinct count.  Add a reduced
 *    clause to it's parent.
 *
 **************************************/
      if (rse->nod_arg[e_rse_reduced])
            return;

      qli_lls* stack = NULL;
      ALLQ_push((blk*) node, &stack);
      ALLQ_push(0, &stack);
      qli_nod* list = make_list(stack);
      rse->nod_arg[e_rse_reduced] = list;
      list->nod_count = 1;
}


static void expand_edit_string( qli_nod* node, qli_print_item* item)
{
/**************************************
 *
 *    e x p a n d _ e d i t _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *    Default edit_string and query_header.
 *
 **************************************/
      switch (node->nod_type)
      {
      case nod_min:
      case nod_rpt_min:
      case nod_agg_min:
            if (!item->itm_query_header)
                  item->itm_query_header = "MIN";

      case nod_total:
      case nod_running_total:
      case nod_rpt_total:
      case nod_agg_total:
            if (!item->itm_query_header)
                  item->itm_query_header = "TOTAL";

      case nod_average:
      case nod_rpt_average:
      case nod_agg_average:
            if (!item->itm_query_header)
                  item->itm_query_header = "AVG";

      case nod_max:
      case nod_rpt_max:
      case nod_agg_max:
            if (!item->itm_query_header)
                  item->itm_query_header = "MAX";
            expand_edit_string(node->nod_arg[e_stt_value], item);
            return;

      case nod_count:
      case nod_running_count:
      case nod_rpt_count:
      case nod_agg_count:
            if (!item->itm_edit_string)
                  item->itm_edit_string = "ZZZ,ZZZ,ZZ9";
            if (!item->itm_query_header)
                  item->itm_query_header = "COUNT";
            break;

      case nod_map:
            {
                  qli_map* map = (qli_map*) node->nod_arg[e_map_map];
                  expand_edit_string(map->map_node, item);
            }
            return;

      case nod_field:
      case nod_variable:
            break;

      case nod_function:
            {
                  qli_fun* function = (qli_fun*) node->nod_arg[e_fun_function];
                  if (!item->itm_query_header)
                        item->itm_query_header = function->fun_symbol->sym_string;
            }
            return;

      default:
            return;
      }

      // Handle fields

      qli_fld* field = (qli_fld*) node->nod_arg[e_fld_field];

      if (!item->itm_edit_string)
            item->itm_edit_string = field->fld_edit_string;

      if (!item->itm_query_header)
            if (!(item->itm_query_header = field->fld_query_header))
                  item->itm_query_header = field->fld_name->sym_string;
}


static qli_nod* expand_erase( qli_syntax* input, qli_lls* right, qli_lls* /*left*/)
{
/**************************************
 *
 *    e x p a n d _ e r a s e
 *
 **************************************
 *
 * Functional description
 *    Expand a statement.
 *
 **************************************/
      qli_nod* loop = NULL;

      // If there is an rse, make up a FOR loop

      if (input->syn_arg[s_era_rse])
      {
            loop = make_node(nod_for, e_for_count);
            loop->nod_arg[e_for_rse] = expand_rse(input->syn_arg[s_era_rse], &right);
      }

      // Loop thru contexts counting them.
      int count = 0;
      qli_ctx* context = NULL;
      for (qli_lls* contexts = right; contexts; contexts = contexts->lls_next)
      {
            context = (qli_ctx*) contexts->lls_object;
            if (context->ctx_variable)
                  continue;
            count++;
            if (context->ctx_rse)
                  break;
      }

      if (count == 0)
            IBERROR(139);                 // Msg139 no context for ERASE
      else if (count > 1)
            IBERROR(140);                 // Msg140 can't erase from a join

      // Make up node big enough to hold fixed fields plus all contexts

      qli_nod* node = make_node(nod_erase, e_era_count);
      node->nod_arg[e_era_context] = (qli_nod*) context;

      if (!loop)
            return node;

      loop->nod_arg[e_for_statement] = node;

      return loop;
}


static qli_nod* expand_expression( qli_syntax* input, qli_lls* stack)
{
/**************************************
 *
 *    e x p a n d _ e x p r e s s i o n
 *
 **************************************
 *
 * Functional description
 *    Expand an expression.
 *
 **************************************/
      qli_nod* node;
      qli_ctx* context;
      qli_syntax* value;

      switch (input->syn_type)
      {
      case nod_field:
            return expand_field(input, stack, 0);

      case nod_null:
      case nod_user_name:
            return make_node(input->syn_type, 0);

      case nod_any:
      case nod_unique:
            return expand_any(input, stack);

      case nod_max:
      case nod_min:
      case nod_count:
      case nod_average:
      case nod_total:
      case nod_from:

      case nod_rpt_max:
      case nod_rpt_min:
      case nod_rpt_count:
      case nod_rpt_average:
      case nod_rpt_total:

      case nod_running_total:
      case nod_running_count:
            node = make_node(input->syn_type, e_stt_count);
            if (value = input->syn_arg[s_stt_rse])
                  node->nod_arg[e_stt_rse] = expand_rse(value, &stack);
            if (value = input->syn_arg[s_stt_value])
                  node->nod_arg[e_stt_value] = expand_expression(value, stack);
            if (value = input->syn_arg[s_stt_default])
                  node->nod_arg[e_stt_default] = expand_expression(value, stack);
            if (input->syn_arg[s_prt_distinct] && node->nod_arg[e_stt_rse] && node->nod_arg[e_stt_value])
            {
                  expand_distinct(node->nod_arg[e_stt_rse], node->nod_arg[e_stt_value]);
            }
            // count2 next 2 lines go
            if (input->syn_type == nod_count)
                  node->nod_arg[e_stt_value] = 0;
            return node;

      case nod_agg_max:
      case nod_agg_min:
      case nod_agg_count:
      case nod_agg_average:
      case nod_agg_total:
            node = make_node(input->syn_type, e_stt_count);
            for (; stack; stack = stack->lls_next)
            {
                  context = (qli_ctx*) stack->lls_object;
                  if (context->ctx_type == CTX_AGGREGATE)
                        break;
            }
            if (!stack)
                  ERRQ_print_error(454);
                  // could not resolve context for aggregate
/* count2
      if (value = input->syn_arg [s_stt_value])
          {
          node->nod_arg [e_stt_value] = expand_expression (value, stack->lls_next);
          if (input->syn_arg [s_prt_distinct])
            expand_distinct (context->ctx_sub_rse, node->nod_arg [e_stt_value]);
          }
*/
            if ((value = input->syn_arg[s_stt_value]) &&
                  (input->syn_arg[s_prt_distinct] || (input->syn_type != nod_agg_count)))
            {
                  node->nod_arg[e_stt_value] = expand_expression(value, stack->lls_next);
                  if (input->syn_arg[s_prt_distinct] ||
                        (input->syn_type == nod_agg_count && context->ctx_sub_rse))
                  {
                        expand_distinct(context->ctx_sub_rse, node->nod_arg[e_stt_value]);
                  }
            }
            return post_map(node, context);

      case nod_index:
            value = input->syn_arg[s_idx_field];
            if (value->syn_type != nod_field)
                  IBERROR(466);           // Msg466 Only fields may be subscripted
            return expand_field(value, stack, input->syn_arg[s_idx_subs]);

      case nod_list:
      case nod_upcase:
      case nod_lowcase:

      case nod_and:
      case nod_or:
      case nod_not:
      case nod_missing:
      case nod_add:
      case nod_subtract:
      case nod_multiply:
      case nod_divide:
      case nod_negate:
      case nod_concatenate:
      case nod_substr:
            break;

      case nod_eql:
      case nod_neq:
      case nod_gtr:
      case nod_geq:
      case nod_leq:
      case nod_lss:
      case nod_between:
      case nod_matches:
      case nod_sleuth:
      case nod_like:
      case nod_starts:
      case nod_containing:
            return expand_boolean(input, stack);

      case nod_edit_blob:
            node = make_node(input->syn_type, e_edt_count);
            node->nod_count = 0;
            if (input->syn_arg[0])
            {
                  node->nod_count = 1;
                  node->nod_arg[0] = expand_expression(input->syn_arg[0], stack);
            }
            return node;

      case nod_format:
            node = make_node(input->syn_type, e_fmt_count);
            node->nod_count = 1;
            node->nod_arg[e_fmt_value] = expand_expression(input->syn_arg[s_fmt_value], stack);
            node->nod_arg[e_fmt_edit] = (qli_nod*) input->syn_arg[s_fmt_edit];
            return node;

      case nod_function:
            return expand_function(input, stack);

      case nod_constant:
            {
                  node = make_node(input->syn_type, 0);
                  qli_const* constant = (qli_const*) input->syn_arg[0];
                  node->nod_desc = constant->con_desc;
            }
            return node;

      case nod_prompt:
            node = make_node(input->syn_type, e_prm_count);
            node->nod_arg[e_prm_prompt] = (qli_nod*) input->syn_arg[0];
            return node;

      case nod_star:
            {
                  qli_name* name = (qli_name*) input->syn_arg[0];
                  ERRQ_print_error(141, name->nam_string);
                  // Msg141 can't be used when a single element is required
            }

      default:
            ERRQ_bugcheck(135);                 // Msg135 expand_expression: not yet implemented
      }

      node = make_node(input->syn_type, input->syn_count);
      qli_nod** ptr = node->nod_arg;

      for (int i = 0; i < input->syn_count; i++)
            *ptr++ = expand_expression(input->syn_arg[i], stack);

      return node;
}


static qli_nod* expand_field( qli_syntax* input, qli_lls* stack, qli_syntax* subs)
{
/**************************************
 *
 *    e x p a n d _ f i e l d
 *
 **************************************
 *
 * Functional description
 *    Expand a field reference.  Error if it can't be resolved.
 *    If the field belongs to a group by SQL expression, make
 *    sure it goes there.
 *
 **************************************/
      qli_ctx* context;

      qli_fld* field = resolve(input, stack, &context);
      if (!field || (subs && (context->ctx_variable)))
      {
          TEXT s[160];
            TEXT* p = s;
            const TEXT* const limit = p + sizeof(s) - 1;
            for (USHORT i = 0; i < input->syn_count; i++)
            {
                  qli_name* name = (qli_name*) input->syn_arg[i];
                  const TEXT* q = name->nam_string;
                  USHORT l = name->nam_length;
                  if (p < limit)
                  {
                        if (l)
                              do {
                                    *p++ = *q++;
                              } while (--l && p < limit);
                        *p++ = '.';
                  }
            }
            *--p = 0;
            if (field)
                  ERRQ_print_error(467, s);
                  // Msg467 "%s" is not a field and so may not be subscripted
            else
                  ERRQ_print_error(142, s);
                  // Msg142 "%s" is undefined or used out of context
      }

      qli_nod* node = make_field(field, context);
      if (subs)
            node->nod_arg[e_fld_subs] = expand_expression(subs, stack);

      qli_ctx* parent = NULL;
      qli_lls* save_stack = stack;
      for (; stack; stack = stack->lls_next)
      {
            parent = (qli_ctx*) stack->lls_object;
            if (parent->ctx_type == CTX_AGGREGATE)
                  break;
      }

      if (!parent)
            return node;

      if (context->ctx_parent != parent)
      {
            // The parent context may be hidden because we are part of
            // a stream context.  Check out this possibility.

            for (; save_stack; save_stack = save_stack->lls_next)
            {
                  qli_ctx* stream_context = (qli_ctx*) save_stack->lls_object;
                  if (stream_context->ctx_type != CTX_STREAM ||
                        stream_context->ctx_stream->nod_type != nod_rse)
                  {
                        continue;
                  }

                  qli_ctx** ptr = (qli_ctx**) stream_context->ctx_stream->nod_arg + e_rse_count;
                  const qli_ctx* const* const end = ptr + stream_context->ctx_stream->nod_count;
                  for (; ptr < end; ptr++)
                        if (*ptr == context)
                              break;
                  if (ptr < end && stream_context->ctx_parent == parent)
                        break;
            }

            if (!save_stack)
                  return node;
      }

      return post_map(node, parent);
}


static qli_nod* expand_for( qli_syntax* input, qli_lls* right, qli_lls* left)
{
/**************************************
 *
 *    e x p a n d _ f o r
 *
 **************************************
 *
 * Functional description
 *    Expand a statement.
 *
 **************************************/
      qli_nod* node = make_node(input->syn_type, e_for_count);
      node->nod_arg[e_for_rse] = expand_rse(input->syn_arg[s_for_rse], &right);
      node->nod_arg[e_for_statement] = expand_statement(input->syn_arg[s_for_statement], right, left);

      return node;
}


static qli_nod* expand_function( qli_syntax* input, qli_lls* stack)
{
/**************************************
 *
 *    e x p a n d _ f u n c t i o n
 *
 **************************************
 *
 * Functional description
 *    Expand a functionn reference.
 *    For a field or expression reference
 *    tied to a relation in a database
 *    use only that database.  For a variable
 *      reference, use any database that matches.
 *
 **************************************/
      qli_symbol* symbol = 0;
      qli_fun* function = 0;
      qli_dbb* database = 0;

      qli_nod* node = make_node(input->syn_type, e_fun_count);
      node->nod_count = 1;
      qli_ctx* context;
      if (stack && (context = (qli_ctx*) stack->lls_object) && (context->ctx_type == CTX_RELATION))
      {
            if (context->ctx_primary)
                  context = context->ctx_primary;
            database = context->ctx_relation->rel_database;
            for (symbol = (qli_symbol*) input->syn_arg[s_fun_function]; symbol;
                  symbol = symbol->sym_homonym)
            {
                  if (symbol->sym_type == SYM_function)
                  {
                        function = (qli_fun*) symbol->sym_object;
                        if (function->fun_database == database)
                              break;
                  }
            }
      }
      else
            for (database = QLI_databases; database; database = database->dbb_next)
            {
                  for (symbol = (qli_symbol*) input->syn_arg[s_fun_function]; symbol;
                         symbol = symbol->sym_homonym)
                  {
                        if (symbol->sym_type == SYM_function)
                        {
                              function = (qli_fun*) symbol->sym_object;
                              if (function->fun_database == database)
                                    break;
                        }
                  }
                  if (symbol)
                        break;
            }


      if (!symbol)
      {
            symbol = (qli_symbol*) input->syn_arg[s_fun_function];
            ERRQ_error(412, SafeArg() << symbol->sym_string << database->dbb_filename);
      }

      node->nod_arg[e_fun_function] = (qli_nod*) function;

      node->nod_arg[e_fun_args] = expand_expression(input->syn_arg[s_fun_args], stack);

      return node;
}


static qli_nod* expand_group_by( qli_syntax* input, qli_lls* stack, qli_ctx* context)
{
/**************************************
 *
 *    e x p a n d _ g r o u p _ b y
 *
 **************************************
 *
 * Functional description
 *    Expand a GROUP BY clause.
 *
 **************************************/
      qli_nod* node = make_node(input->syn_type, input->syn_count);
      qli_nod** ptr2 = node->nod_arg;

      qli_syntax** ptr = input->syn_arg;
      for (const qli_syntax* const* const end = ptr + input->syn_count; ptr < end; ptr++, ptr2++)
      {
            *ptr2 = expand_expression(*ptr, stack);
            post_map(*ptr2, context);
      }

      return node;
}


static qli_nod* expand_modify( qli_syntax* input, qli_lls* right, qli_lls* left)
{
/**************************************
 *
 *    e x p a n d _ m o d i f y
 *
 **************************************
 *
 * Functional description
 *    Expand a statement.
 *
 **************************************/
      qli_nod* loop = NULL;

      // If there is an rse, make up a FOR loop

      if (input->syn_arg[s_mod_rse])
      {
            loop = make_node(nod_for, e_for_count);
            loop->nod_arg[e_for_rse] = expand_rse(input->syn_arg[s_mod_rse], &right);
      }

    qli_lls* contexts;

      // Loop thru contexts counting them.
      USHORT count = 0;
      for (contexts = right; contexts; contexts = contexts->lls_next)
      {
            qli_ctx* context = (qli_ctx*) contexts->lls_object;
            if (context->ctx_variable)
                  continue;
            ++count;
            if (context->ctx_rse)
                  break;
      }

      if (!count)
            IBERROR(148);                 // Msg148 no context for modify

      // Make up node big enough to hold fixed fields plus all contexts

      qli_nod* node = make_node(nod_modify, (int) e_mod_count + count);
      node->nod_count = count;
      qli_nod** ptr = &node->nod_arg[e_mod_count];

      // Loop thru contexts augmenting left context

      for (contexts = right; contexts; contexts = contexts->lls_next)
      {
            qli_ctx* context = (qli_ctx*) contexts->lls_object;
            if (context->ctx_variable)
                  continue;
            qli_ctx* new_context = (qli_ctx*) ALLOCD(type_ctx);
            *ptr++ = (qli_nod*) new_context;
            new_context->ctx_type = CTX_RELATION;
            new_context->ctx_source = context;
            new_context->ctx_symbol = context->ctx_symbol;
            new_context->ctx_relation = context->ctx_relation;
            ALLQ_push((blk*) new_context, &left);
            if (context->ctx_rse)
                  break;
      }

      // Process sub-statement, list of fields, or, sigh, none of the above

      qli_syntax* syn_list;
      if (input->syn_arg[s_mod_statement])
            node->nod_arg[e_mod_statement] = expand_statement(input->syn_arg[s_mod_statement], right, left);
      else if (syn_list = input->syn_arg[s_mod_list])
      {
            qli_nod* list = make_node(nod_list, syn_list->syn_count);
            node->nod_arg[e_mod_statement] = list;

            ptr = list->nod_arg;
            qli_syntax** syn_ptr = syn_list->syn_arg;
            for (USHORT i = 0; i < syn_list->syn_count; i++, syn_ptr++)
            {
                  *ptr++ = make_assignment(expand_expression((qli_syntax*) *syn_ptr, left),
                                                      (qli_nod*) *syn_ptr, right);
            }
      }
      else
            IBERROR(149);                 // Msg149 field list required for modify

      if (!loop)
            return node;

      loop->nod_arg[e_for_statement] = node;

      return loop;
}


static qli_nod* expand_output( qli_syntax* input, qli_lls* right, qli_prt** print)
{
/**************************************
 *
 *    e x p a n d _ o u t p u t
 *
 **************************************
 *
 * Functional description
 *    Handle the presence (or absence) of an output specification clause.
 *
 **************************************/
      if (print)
            *print = (qli_prt*) ALLOCD(type_prt);

      if (!input)
            return NULL;

      qli_nod* output = make_node(nod_output, e_out_count);
      ALLQ_push((blk*) output, &global_output_stack);

    qli_nod* node = possible_literal(input->syn_arg[s_out_file], right, false);
      if (!node)
            node = expand_expression(input->syn_arg[s_out_file], right);

      output->nod_arg[e_out_file] = node;
      output->nod_arg[e_out_pipe] = (qli_nod*) input->syn_arg[s_out_pipe];

      if (print)
            output->nod_arg[e_out_print] = (qli_nod*) * print;

      return output;
}


static qli_nod* expand_print( qli_syntax* input, qli_lls* right, qli_lls* /*left*/)
{
/**************************************
 *
 *    e x p a n d _ p r i n t
 *
 **************************************
 *
 * Functional description
 *    Expand a statement.
 *
 **************************************/
      qli_syntax* syn_rse = input->syn_arg[s_prt_rse];
      qli_lls* new_right = right;

      // If an output file or pipe is present, make up an output node

      qli_prt* print;
      expand_output(input->syn_arg[s_prt_output], right, &print);

      // If a record select expression is present, expand it and build a FOR
      // statement.

      qli_nod* loop = NULL;
      qli_nod* rse = NULL;
      if (syn_rse)
      {
            loop = make_node(nod_for, e_for_count);
            loop->nod_arg[e_for_rse] = rse = expand_rse(syn_rse, &new_right);
      }

      // If there were any print items, process them now.  Look first for things that
      // look like items, but are actually lists.  If there aren't items of any kind,
      // pick up all fields in the relations from the record selection expression.
      qli_lls* items = NULL;
      USHORT count = 0;
      qli_syntax* syn_list = input->syn_arg[s_prt_list];
      if (syn_list)
      {
        qli_syntax** sub = syn_list->syn_arg;
            for (const qli_syntax* const* const end = sub + syn_list->syn_count; sub < end; sub++)
            {
                  const qli_syntax* syn_item;
                  if (((*sub)->syn_type == nod_print_item) &&
                        (syn_item = (*sub)->syn_arg[s_itm_value]) && (syn_item->syn_type == nod_star))
                  {
                        count += generate_items(syn_item, new_right, (qli_lls*) &items, rse);
                  }
                  else
                  {
                        ALLQ_push((blk*) expand_print_item(*sub, new_right), &items);
                        count++;
                  }
            }
      }
      else if (syn_rse && (syn_list = syn_rse->syn_arg[s_rse_reduced]))
      {
        qli_syntax** sub = syn_list->syn_arg;
            for (const qli_syntax* const* const end = sub + syn_list->syn_count; sub < end; sub += 2)
            {
                  qli_print_item* item = (qli_print_item*) ALLOCD(type_itm);
                  item->itm_type = item_value;
                  item->itm_value = expand_expression(*sub, new_right);
                  expand_edit_string(item->itm_value, item);
                  ALLQ_push((blk*) item, &items);
                  count++;
            }
      }
      else
            for (; new_right; new_right = new_right->lls_next)
            {
                  qli_ctx* context = (qli_ctx*) new_right->lls_object;
                  qli_rel* relation = context->ctx_relation;
                  if (!relation || context->ctx_sub_rse)
                        continue;
                  for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
                  {
                        if ((field->fld_system_flag && field->fld_system_flag != relation->rel_system_flag) ||
                              field->fld_flags & FLD_array)
                        {
                              continue;
                        }
                        qli_nod* node = make_field(field, context);
                        if (rse && rse->nod_arg[e_rse_group_by] &&
                              invalid_nod_field(node, rse->nod_arg[e_rse_group_by]))
                        {
                              continue;
                        }
                        qli_print_item* item = (qli_print_item*) ALLOCD(type_itm);
                        item->itm_type = item_value;
                        item->itm_value = make_field(field, context);
                        expand_edit_string(item->itm_value, item);
                        ALLQ_push((blk*) item, &items);
                        count++;
                  }
                  if (rse = context->ctx_rse)
                        break;
            }

      // If no print object showed up, complain!

      if (!count)
            IBERROR(150);                 // Msg150 No items in print list

      // Build new print statement.  Unlike the syntax node, the print statement
      // has only print items in it.

      qli_nod* node = make_node(input->syn_type, e_prt_count);
      qli_nod* list = make_list(items);
      node->nod_arg[e_prt_list] = list;
      node->nod_arg[e_prt_output] = (qli_nod*) print;

      // If DISTINCT was requested, make up a reduced list.

      if (rse && input->syn_arg[s_prt_distinct])
      {
            qli_nod* reduced = make_node(nod_list, list->nod_count * 2);
            reduced->nod_count = 0;
            qli_nod** ptr = reduced->nod_arg;
            for (USHORT i = 0; i < list->nod_count; i++)
            {
                  qli_print_item* item = (qli_print_item*) list->nod_arg[i];
                  if (item->itm_value)
                  {
                        *ptr++ = item->itm_value;
                        ptr++;
                        reduced->nod_count++;
                  }
            }
            if (reduced->nod_count)
                  rse->nod_arg[e_rse_reduced] = reduced;
      }

      // If a FOR loop was generated, splice it in here.

      if (loop)
      {
            loop->nod_arg[e_for_statement] = node;
            node = loop;
            if (input->syn_arg[s_prt_order])
                  rse->nod_arg[e_rse_sort] = expand_sort(input->syn_arg[s_prt_order], new_right, list);
      }

      return node;
}


static qli_print_item* expand_print_item( qli_syntax* syn_item, qli_lls* right)
{
/**************************************
 *
 *    e x p a n d _ p r i n t _ i t e m
 *
 **************************************
 *
 * Functional description
 *    Expand a print item.  A print item can either be a value or a format
 *    specifier.
 *
 **************************************/
      qli_print_item* item = (qli_print_item*) ALLOCD(type_itm);

      switch (syn_item->syn_type)
      {
      case nod_print_item:
            {
                  item->itm_type = item_value;
                  qli_syntax* syn_expr = syn_item->syn_arg[s_itm_value];
                  qli_nod* node = item->itm_value = expand_expression(syn_expr, right);
                  item->itm_edit_string = (TEXT*) syn_item->syn_arg[s_itm_edit_string];
                  item->itm_query_header = (TEXT*) syn_item->syn_arg[s_itm_header];
                  expand_edit_string(node, item);
                  return item;
            }

      case nod_column:
            item->itm_type = item_column;
            break;

      case nod_tab:
            item->itm_type = item_tab;
            break;

      case nod_space:
            item->itm_type = item_space;
            break;

      case nod_skip:
            item->itm_type = item_skip;
            break;

      case nod_new_page:
            item->itm_type = item_new_page;
            break;

      case nod_column_header:
            item->itm_type = item_column_header;
            break;

      case nod_report_header:
            item->itm_type = item_report_header;
            break;

      }

      item->itm_count = (IPTR) syn_item->syn_arg[0];
      return item;
}


static qli_nod* expand_print_list( qli_syntax* input, qli_lls* stack)
{
/**************************************
 *
 *    e x p a n d _ p r i n t _ l i s t
 *
 **************************************
 *
 * Functional description
 *    Expand a print list.
 *
 **************************************/
      qli_lls* items = NULL;
      qli_syntax** ptr = input->syn_arg;

      for (const qli_syntax* const* const end = ptr + input->syn_count; ptr < end; ptr++)
      {
            ALLQ_push((blk*) expand_print_item(*ptr, stack), &items);
      }

      return make_list(items);
}


static qli_nod* expand_report( qli_syntax* input, qli_lls* right, qli_lls* /*left*/)
{
/**************************************
 *
 *    e x p a n d _ r e p o r t
 *
 **************************************
 *
 * Functional description
 *    Expand a report specification.
 *
 **************************************/
      qli_prt* print;

      // Start by processing record selection expression

      expand_output(input->syn_arg[s_prt_output], right, &print);
      qli_rpt* report = print->prt_report = (qli_rpt*) input->syn_arg[s_prt_list];

      if (!(print->prt_lines_per_page = report->rpt_lines))
            print->prt_lines_per_page = QLI_lines;

      if (!report->rpt_columns)
            report->rpt_columns = QLI_columns;

      qli_nod* loop = make_node(nod_report_loop, e_for_count);
      loop->nod_arg[e_for_rse] = expand_rse(input->syn_arg[s_prt_rse], &right);
      qli_nod* node = make_node(nod_report, e_prt_count);
      loop->nod_arg[e_for_statement] = node;

      node->nod_arg[e_prt_list] = (qli_nod*) report;
      node->nod_arg[e_prt_output] = (qli_nod*) print;

      // Process clauses where they exist

      expand_control_break(&report->rpt_top_rpt, right);
      expand_control_break(&report->rpt_top_page, right);
      expand_control_break(&report->rpt_top_breaks, right);

      qli_syntax* sub = (qli_syntax*) report->rpt_detail_line;
      if (sub)
            report->rpt_detail_line = expand_print_list(sub, right);

      expand_control_break(&report->rpt_bottom_breaks, right);
      expand_control_break(&report->rpt_bottom_page, right);
      expand_control_break(&report->rpt_bottom_rpt, right);

      return loop;
}


static qli_nod* expand_restructure( qli_syntax* input, qli_lls* right, qli_lls* /*left*/)
{
/**************************************
 *
 *    e x p a n d _ r e s t r u c t u r e
 *
 **************************************
 *
 * Functional description
 *    Transform a restructure statement into a FOR <rse> STORE.
 *
 **************************************/

      // Make a FOR loop to drive the restructure

      qli_nod* loop = make_node(nod_for, e_for_count);
      loop->nod_arg[e_for_rse] = expand_rse(input->syn_arg[s_asn_from], &right);

      // Make a STORE node.

      qli_nod* node = make_node(nod_store, e_sto_count);
      loop->nod_arg[e_for_statement] = node;
      qli_syntax* rel_node = input->syn_arg[s_asn_to];
      qli_ctx* context = (qli_ctx*) ALLOCD(type_ctx);
      node->nod_arg[e_sto_context] = (qli_nod*) context;
      context->ctx_type = CTX_RELATION;
      context->ctx_rse = (qli_nod*) -1;
      qli_rel* relation = context->ctx_relation = (qli_rel*) rel_node->syn_arg[s_rel_relation];

      // If we don't already know about the relation, find out now.

      if (!(relation->rel_flags & REL_fields))
            MET_fields(relation);

      // Match fields in target relation against fields in the input rse.  Fields
      // may match on either name or query name.

      qli_lls* stack = NULL;

      for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
            if (!(field->fld_flags & FLD_computed))
            {
                  for (qli_lls* search = right; search; search = search->lls_next)
                  {
                        qli_ctx* ctx = (qli_ctx*) search->lls_object;

                        // First look for an exact field name match

                        qli_fld* fld;
                        for (fld = ctx->ctx_relation->rel_fields; fld; fld = fld->fld_next)
                        {
                              if (compare_symbols(field->fld_name, fld->fld_name))
                                    break;
                        }
                        // Next try, target field name matching source query name

                        if (!fld)
                              for (fld = ctx->ctx_relation->rel_fields; fld; fld = fld->fld_next)
                              {
                                    if (compare_symbols(field->fld_name, fld->fld_query_name))
                                          break;
                              }
                        // If nothing yet, look for any old match

                        if (!fld)
                              for (fld = ctx->ctx_relation->rel_fields; fld; fld = fld->fld_next)
                              {
                                    if (compare_symbols(field-> fld_query_name, fld->fld_name) ||
                                          compare_symbols(field->fld_query_name, fld->fld_query_name))
                                    {
                                                break;
                                    }
                              }

                        if (fld)
                        {
                              qli_nod* assignment = make_node(nod_assign, e_asn_count);
                              assignment->nod_count = e_asn_count - 1;
                              assignment->nod_arg[e_asn_to] = make_field(field, context);
                              assignment->nod_arg[e_asn_from] = make_field(fld, ctx);
                              ALLQ_push((blk*) assignment, &stack);
                              goto found_field;
                        }

                        if (ctx->ctx_rse)
                              break;
                  }
                  found_field:;
            }

      node->nod_arg[e_sto_statement] = make_list(stack);

      return loop;
}


static qli_nod* expand_rse( qli_syntax* input, qli_lls** stack)
{
/**************************************
 *
 *    e x p a n d _ r s e
 *
 **************************************
 *
 * Functional description
 *    Expand a record selection expression, returning an updated context
 *    stack.
 *
 **************************************/
      qli_lls* old_stack = *stack;
      qli_lls* new_stack = *stack;
      qli_nod* boolean = NULL;
      qli_nod* node = make_node(input->syn_type, (int) e_rse_count + input->syn_count);
      node->nod_count = input->syn_count;
      qli_nod** ptr2 = &node->nod_arg[e_rse_count];

      // Decide whether or not this is a GROUP BY, real or imagined
      // If it is, disallow normal field type references

      qli_ctx* parent_context = NULL;
      qli_nod* parent_rse = NULL;

      if (input->syn_arg[s_rse_group_by] || input->syn_arg[s_rse_having])
            parent_context = (qli_ctx*) ALLOCD(type_ctx);
      qli_syntax* list = input->syn_arg[s_rse_list];
      if (list)
      {
            for (USHORT i = 0; i < list->syn_count; i++)
            {
                  const qli_syntax* value = list->syn_arg[i];
                  const qli_syntax* field = value->syn_arg[e_itm_value];
                  if (!field)
                        continue;

                  if (global_agg(field, input->syn_arg[s_rse_group_by]))
                  {
                        if (!parent_context)
                              parent_context = (qli_ctx*) ALLOCD(type_ctx);
                  }
                  else if (parent_context)
                  {
                        if (invalid_syn_field(field, input->syn_arg[s_rse_group_by]))
                              IBERROR(451);
                  }
            }
      }

      if (parent_context)
      {
            parent_context->ctx_type = CTX_AGGREGATE;
            parent_rse = make_node(nod_rse, e_rse_count + 1);
            parent_rse->nod_count = 1;
            parent_rse->nod_arg[e_rse_count] = (qli_nod*) parent_context;
            parent_context->ctx_sub_rse = node;
      }

      // Process the FIRST clause before the context gets augmented

      if (input->syn_arg[s_rse_first])
            node->nod_arg[e_rse_first] = expand_expression(input->syn_arg[e_rse_first], old_stack);

      // Process relations

      qli_syntax** ptr = input->syn_arg + s_rse_count;

      for (USHORT i = 0; i < input->syn_count; i++)
      {
            qli_syntax* rel_node = *ptr++;
            qli_syntax* over = *ptr++;
            qli_ctx* context = (qli_ctx*) ALLOCD(type_ctx);
            *ptr2++ = (qli_nod*) context;
            if (i == 0)
                  context->ctx_rse = node;
            if (rel_node->syn_type == nod_rse)
            {
                  context->ctx_type = CTX_STREAM;
                  context->ctx_stream = expand_rse(rel_node, &new_stack);
            }
            else
            {
                  context->ctx_type = CTX_RELATION;
                  qli_rel* relation = context->ctx_relation = (qli_rel*) rel_node->syn_arg[s_rel_relation];
                  if (!(relation->rel_flags & REL_fields))
                        MET_fields(relation);
                  qli_symbol* symbol = context->ctx_symbol = (qli_symbol*) rel_node->syn_arg[s_rel_context];
                  if (symbol)
                        symbol->sym_object = (BLK) context;
                  if (over)
                  {
                        qli_lls* short_stack = NULL;
                        ALLQ_push((blk*) context, &short_stack);
                        for (USHORT j = 0; j < over->syn_count; j++)
                        {
                              qli_syntax* field = over->syn_arg[j];
                              qli_nod* eql_node = make_node(nod_eql, 2);
                              eql_node->nod_arg[0] = expand_expression(field, short_stack);
                              eql_node->nod_arg[1] = expand_expression(field, new_stack);
                              boolean = make_and(eql_node, boolean);
                        }
                        ALLQ_pop(&short_stack);
                  }
            }
            ALLQ_push((blk*) context, &new_stack);
      }

      // Handle explicit boolean

      if (input->syn_arg[e_rse_boolean])
            boolean = make_and(boolean, expand_expression(input->syn_arg[e_rse_boolean], new_stack));

      // Handle implicit boolean from SQL xxx IN (yyy FROM relation)

      if (input->syn_arg[s_rse_outer])
      {
            qli_nod* eql_node = make_node((enum nod_t)(IPTR)input->syn_arg[s_rse_op], 2);
            eql_node->nod_arg[0] = expand_expression(input->syn_arg[s_rse_outer], old_stack);
            eql_node->nod_arg[1] = expand_expression(input->syn_arg[s_rse_inner], new_stack);
            if (input->syn_arg[s_rse_all_flag])
                  eql_node = negate(eql_node);
            boolean = make_and(eql_node, boolean);
      }

      node->nod_arg[e_rse_boolean] = boolean;

      if (input->syn_arg[s_rse_sort])
      {
            qli_nod* temp = expand_sort(input->syn_arg[e_rse_sort], new_stack, 0);
            if (parent_rse)
                  parent_rse->nod_arg[e_rse_sort] = temp;
            else
                  node->nod_arg[e_rse_sort] = temp;
      }

      if (input->syn_arg[s_rse_reduced])
            node->nod_arg[e_rse_reduced] = expand_sort(input->syn_arg[e_rse_reduced], new_stack, 0);

      if (input->syn_arg[s_rse_group_by])
      {
            parent_rse->nod_arg[e_rse_group_by] =
                  expand_group_by(input->syn_arg[s_rse_group_by], new_stack, parent_context);
      }

      node->nod_arg[e_rse_join_type] = (qli_nod*) input->syn_arg[s_rse_join_type];

      // If there is a parent context, set it up here

      *stack = new_stack;

      if (!parent_context)
            return node;

    qli_ctx* context = NULL;
      ptr2 = node->nod_arg + e_rse_count;
      for (const qli_nod* const* const end = ptr2 + node->nod_count; ptr2 < end; ptr2++)
      {
            context = (qli_ctx*) *ptr2;
            context->ctx_parent = parent_context;
      }

      if (!(parent_context->ctx_relation = context->ctx_relation))
            parent_context->ctx_stream = context->ctx_stream;
      ALLQ_push((blk*) parent_context, stack);

      if (input->syn_arg[s_rse_having])
            parent_rse->nod_arg[e_rse_having] = expand_expression(input->syn_arg[s_rse_having], *stack);

      return parent_rse;
}


static qli_nod* expand_sort( qli_syntax* input, qli_lls* stack, qli_nod* list)
{
/**************************************
 *
 *    e x p a n d _ s o r t
 *
 **************************************
 *
 * Functional description
 *    Expand a sort or reduced clause.  This is more than a little
 *    kludgy.  For pure undiscipled, pragmatic reasons, the count for
 *    a sort/ reduced clause for a syntax node is twice the number of
 *    actual keys.  For node nodes, however, the count is the accurate
 *    number of keys.  So be careful.
 *
 **************************************/
      qli_nod* node = make_node(nod_list, input->syn_count);
      node->nod_count = input->syn_count / 2;
      qli_nod** ptr = node->nod_arg;
      qli_syntax** syn_ptr = input->syn_arg;

      for (USHORT i = 0; i < node->nod_count; i++)
      {
            qli_syntax* expr = *syn_ptr++;
            if (expr->syn_type == nod_position)
            {
                  const IPTR position = (IPTR) expr->syn_arg[0];
                  if (!list || !position || position > list->nod_count)
                        IBERROR(152);     // Msg152 invalid ORDER BY ordinal
                  qli_print_item* item = (qli_print_item*) list->nod_arg[position - 1];
                  *ptr++ = item->itm_value;
            }
            else
                  *ptr++ = expand_expression(expr, stack);
            *ptr++ = (qli_nod*) * syn_ptr++;
      }

      return node;
}


static qli_nod* expand_statement( qli_syntax* input, qli_lls* right, qli_lls* left)
{
/**************************************
 *
 *    e x p a n d _ s t a t e m e n t
 *
 **************************************
 *
 * Functional description
 *    Expand a statement.
 *
 **************************************/
      qli_nod* node;
      qli_nod* (*routine) (qli_syntax*, qli_lls*, qli_lls*);

      switch (input->syn_type)
      {
      case nod_abort:
            node = make_node(input->syn_type, input->syn_count);
            if (input->syn_arg[0])
                  node->nod_arg[0] = expand_expression(input->syn_arg[0], right);
            return node;

      case nod_assign:
            routine = expand_assignment;
            break;

      case nod_commit_retaining:
            {
                  node = make_node(input->syn_type, input->syn_count);
                  for (USHORT i = 0; i < input->syn_count; i++)
                        node->nod_arg[i] = (qli_nod*) input->syn_arg[i];
                  return node;
            }

      case nod_erase:
            routine = expand_erase;
            break;

      case nod_for:
            routine = expand_for;
            break;

      case nod_if:
            node = make_node(input->syn_type, input->syn_count);
            node->nod_arg[e_if_boolean] = expand_expression(input->syn_arg[s_if_boolean], right);
            node->nod_arg[e_if_true] = expand_statement(input->syn_arg[s_if_true], right, left);
            if (input->syn_arg[s_if_false])
                  node->nod_arg[e_if_false] = expand_statement(input->syn_arg[s_if_false], right, left);
            else
                  node->nod_count = 2;
            return node;

      case nod_modify:
            routine = expand_modify;
            break;

      case nod_print:
      case nod_list_fields:
            routine = expand_print;
            break;

      case nod_report:
            routine = expand_report;
            break;

      case nod_restructure:
            routine = expand_restructure;
            break;

      case nod_store:
            routine = expand_store;
            break;

      case nod_repeat:
            node = make_node(input->syn_type, input->syn_count);
            node->nod_arg[e_rpt_value] = expand_expression(input->syn_arg[s_rpt_value], left);
            node->nod_arg[e_rpt_statement] =
                  expand_statement(input->syn_arg[s_rpt_statement], right, left);
            return node;

      case nod_list:
            {
                  qli_syntax** syn_ptr = input->syn_arg;
                  qli_lls* stack = NULL;
                  for (USHORT i = 0; i < input->syn_count; i++)
                  {
                        qli_syntax* syn_node = *syn_ptr++;
                        if (syn_node->syn_type == nod_declare)
                        {
                              qli_ctx* context = (qli_ctx*) ALLOCD(type_ctx);
                              context->ctx_type = CTX_VARIABLE;
                              qli_syntax* field_node = syn_node->syn_arg[1];
                              if (field_node)
                              {
                                    if (field_node->syn_type == nod_index)
                                          field_node = field_node->syn_arg[s_idx_field];
                                    resolve_really((qli_fld*) syn_node->syn_arg[0], field_node);
                              }
                              context->ctx_variable = (qli_fld*) syn_node->syn_arg[0];
                              ALLQ_push((blk*) context, &right);
                              ALLQ_push((blk*) context, &left);
                        }
                        else if (node = expand_statement(syn_node, right, left))
                              ALLQ_push((blk*) node, &stack);
                  }
                  return make_list(stack);
            }

      case nod_declare:
            return NULL;

      default:
            ERRQ_bugcheck(136);                 // Msg136 expand_statement: not yet implemented
      }

      return (*routine) (input, right, left);
}


static qli_nod* expand_store( qli_syntax* input, qli_lls* right, qli_lls* left)
{
/**************************************
 *
 *    e x p a n d _ s t o r e
 *
 **************************************
 *
 * Functional description
 *    Process, yea expand, on a mere STORE statement.  Make us
 *    something neat if nothing looks obvious.
 *
 **************************************/
      qli_nod* loop = NULL;

      // If there is an rse, make up a FOR loop

      if (input->syn_arg[s_sto_rse])
      {
            loop = make_node(nod_for, e_for_count);
            loop->nod_arg[e_for_rse] = expand_rse(input->syn_arg[s_sto_rse], &right);
      }

      qli_nod* node = make_node(input->syn_type, e_sto_count);

      qli_syntax* rel_node = input->syn_arg[s_sto_relation];
      qli_ctx* context = (qli_ctx*) ALLOCD(type_ctx);
      node->nod_arg[e_sto_context] = (qli_nod*) context;
      context->ctx_type = CTX_RELATION;
      context->ctx_rse = (qli_nod*) -1;
      qli_rel* relation = context->ctx_relation = (qli_rel*) rel_node->syn_arg[s_rel_relation];

      if (!(relation->rel_flags & REL_fields))
            MET_fields(relation);

      qli_symbol* symbol = context->ctx_symbol = (qli_symbol*) rel_node->syn_arg[s_rel_context];
      if (symbol)
            symbol->sym_object = (BLK) context;

      ALLQ_push((blk*) context, &left);

      //  If there are field and value lists, process them

      if (input->syn_arg[s_sto_values])
      {
            if (!input->syn_arg[s_sto_fields])
            {
                  qli_lls* stack = NULL;
                  for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
                  {
                        ALLQ_push((blk*) decompile_field(field, 0), &stack);
                  }
                  input->syn_arg[s_sto_fields] = (qli_syntax*) stack;
            }
            expand_values(input, right);
      }

      // Process sub-statement.  If there isn't one, make up a series of assignments.

      if (input->syn_arg[s_sto_statement])
      {
            qli_ctx* secondary = (qli_ctx*) ALLOCD(type_ctx);
            secondary->ctx_type = CTX_RELATION;
            secondary->ctx_primary = context;
            ALLQ_push((blk*) secondary, &right);
            node->nod_arg[e_sto_statement] =
                  expand_statement(input->syn_arg[s_sto_statement], right, left);
      }
      else
      {
            qli_lls* stack = NULL;
            for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
            {
                  if (field->fld_flags & FLD_computed)
                        continue;
                  if ((field->fld_system_flag && field->fld_system_flag != relation->rel_system_flag) ||
                        field->fld_flags & FLD_array)
                  {
                        continue;
                  }
                  qli_nod* assignment = make_assignment(make_field(field, context), 0, 0);
                  ALLQ_push((blk*) assignment, &stack);
            }
            node->nod_arg[e_sto_statement] = make_list(stack);
      }

      if (!loop)
            return node;

      loop->nod_arg[e_for_statement] = node;

      return loop;
}


static void expand_values( qli_syntax* input, qli_lls* right)
{
/**************************************
 *
 *    e x p a n d _ v a l u e s
 *
 **************************************
 *
 * Functional description
 *    We've got a grungy SQL insert, and we have
 *    to make the value list match the field list.
 *    On the way in, we got the right number of
 *    fields.  Now all that's needed is the values
 *    and matching the two lists, and generating
 *    assignments.  If the input is from a select,
 *    things may be harder, and if there are wild cards
 *    things will be harder still.  Wild cards come in
 *    two flavors * and <context>.*.  The first is
 *    a nod_prompt, the second a nod_star.
 *
 **************************************/

      // fields have already been checked and expanded.  Just count them

      qli_lls* fields = (qli_lls*) input->syn_arg[s_sto_fields];
      qli_lls* stack;
      int field_count = 0;
      for (stack = fields; stack; stack = stack->lls_next)
            field_count++;

      // We're going to want the values in the order listed in the command

      qli_lls* values = (qli_lls*) input->syn_arg[s_sto_values];
      while (values)
            ALLQ_push(ALLQ_pop(&values), &stack);

      // now go through, count, and expand where needed

      int value_count = 0;
      while (stack)
      {
            qli_syntax* value = (qli_syntax*) ALLQ_pop(&stack);
            if (input->syn_arg[s_sto_rse] && value->syn_type == nod_prompt)
            {
                  if (value->syn_arg[0] == 0)
                  {
                        qli_lls* temp = NULL;
                        for (; right; right = right->lls_next)
                              ALLQ_push(right->lls_object, &temp);

                        while (temp)
                        {
                              qli_ctx* context = (qli_ctx*) ALLQ_pop(&temp);
                              value_count +=
                                    generate_fields(context, (qli_lls*) &values, input->syn_arg[s_sto_rse]);
                        }
                  }
                  else
                        IBERROR(542);     // this was a prompting expression.  won't do at all
            }
            else if (input->syn_arg[s_sto_rse] && (value->syn_type == nod_star))
            {
                  qli_ctx* context = find_context((const qli_name*) value->syn_arg[0], right);
                  if (!context)
                        IBERROR(154);     // Msg154 unrecognized context
                  value_count += generate_fields(context, (qli_lls*) &values, input->syn_arg[s_sto_rse]);
            }
            else
            {
                  ALLQ_push((blk*) value, &values);
                  value_count++;
            }
      }

      // Make assignments from values to fields

      if (field_count != value_count)
            IBERROR(189);
            // Msg189 the number of values do not match the number of fields

      qli_syntax* list = (qli_syntax*) ALLOCDV(type_syn, value_count);
      list->syn_type = nod_list;
      list->syn_count = value_count;
      input->syn_arg[s_sto_statement] = list;
      qli_syntax** ptr = list->syn_arg + value_count;

      while (values)
      {
          qli_syntax* assignment = (qli_syntax*) ALLOCDV(type_syn, s_asn_count);
            *--ptr = assignment;
            assignment->syn_type = nod_assign;
            assignment->syn_count = s_asn_count;
            assignment->syn_arg[s_asn_to] = (qli_syntax*) ALLQ_pop(&fields);
            assignment->syn_arg[s_asn_from] = (qli_syntax*) ALLQ_pop(&values);
      }
}


static qli_ctx* find_context( const qli_name* name, qli_lls* contexts)
{
/**************************************
 *
 *    f i n d _ c o n t e x t
 *
 **************************************
 *
 * Functional description
 *    We've got a context name and we need to return
 *    the context block implicated.
 *
 **************************************/
      for (; contexts; contexts = contexts->lls_next)
      {
            qli_ctx* context = (qli_ctx*) contexts->lls_object;
            const qli_rel* relation = context->ctx_relation;
            if (compare_names(name, relation->rel_symbol))
                  return context;
            if (compare_names(name, context->ctx_symbol))
                  return context;
      }
      return NULL;
}


static int generate_fields( qli_ctx* context, qli_lls* values, qli_syntax* rse)
{
/**************************************
 *
 *    g e n e r a t e _ f i e l d s
 *
 **************************************
 *
 * Functional description
 *    Expand an asterisk expression, which
 *    could be <relation>.* or <alias>.* or <context>.*
 *    into a list of non-expanded field blocks for
 *    input to a store or update.
 *
 **************************************/

      if (context->ctx_type == CTX_VARIABLE)
            return 0;
      if (context->ctx_type == CTX_AGGREGATE)
            return 0;

      qli_syntax* group_list = rse->syn_arg[s_rse_group_by];
      qli_rel* relation = context->ctx_relation;
      int count = 0;

      for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
      {
            if ((field->fld_system_flag && field->fld_system_flag != relation->rel_system_flag) ||
                  field->fld_flags & FLD_array)
            {
                  continue;
            }
            qli_syntax* value = decompile_field(field, context);
            if (group_list && invalid_syn_field(value, group_list))
                  continue;
            ALLQ_push((blk*) value, (qli_lls**) values);
            count++;
      }

      return count;
}


static int generate_items(const qli_syntax* symbol, qli_lls* right, qli_lls* items, qli_nod* rse)
{
/**************************************
 *
 *    g e n e r a t e _ i t e m s
 *
 **************************************
 *
 * Functional description
 *    Expand an asterisk expression, which
 *    could be <relation>.* or <alias>.* or <context>.*
 *    into a list of reasonable print items.
 *
 *      If the original request included a group by,
 *    include only the grouping fields.
 *
 **************************************/
      qli_nod* group_list = rse ? rse->nod_arg[e_rse_group_by] : NULL;

      // first identify the relation or context

      const qli_name* name;
      if (symbol->syn_count == 1)
            name = (qli_name*) symbol->syn_arg[0];
      else
            IBERROR(153);
            // Msg153 asterisk expressions require exactly one qualifying context

      qli_ctx* context = find_context(name, right);
      if (!context)
            IBERROR(154);                 // Msg154 unrecognized context

      qli_rel* relation = context->ctx_relation;
      int count = 0;
      for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
      {
            if ((field->fld_system_flag && field->fld_system_flag != relation->rel_system_flag) ||
                  field->fld_flags & FLD_array)
            {
                  continue;
            }
            qli_nod* node = make_field(field, context);
            if (group_list && invalid_nod_field(node, group_list))
                  continue;
            qli_print_item* item = (qli_print_item*) ALLOCD(type_itm);
            item->itm_type = item_value;
            item->itm_value = make_field(field, context);
            expand_edit_string(item->itm_value, item);
            ALLQ_push((blk*) item, (qli_lls**) items);
            ++count;
      }

      return count;
}


static bool global_agg( const qli_syntax* item, const qli_syntax* group_list)
{
/**************************************
 *
 *    g l o b a l _ a g g
 *
 **************************************
 *
 * Functional description
 *    We've got a print list item that may contain
 *    a sql global aggregate.  If it does, we're
 *    going to make the whole thing a degenerate
 *    group by.  Anyway.  Look for aggregates buried
 *    deep within printable things.
 *
 *    This recurses.  If it finds a mixture of normal
 *    and aggregates it complains.
 *
 **************************************/
      bool normal_field = false;
      bool aggregate = false;

      switch (item->syn_type)
      {
      case nod_agg_average:
      case nod_agg_max:
      case nod_agg_min:
      case nod_agg_total:
      case nod_agg_count:
      case nod_running_total:
      case nod_running_count:
            return true;

      case nod_upcase:
      case nod_lowcase:
      case nod_add:
      case nod_subtract:
      case nod_multiply:
      case nod_divide:
      case nod_negate:
      case nod_concatenate:
      case nod_substr:
            {
                  const qli_syntax* const* ptr = item->syn_arg;
                  for (const qli_syntax* const* const end = ptr + item->syn_count; ptr < end; ptr++)
                  {
                        if ((*ptr)->syn_type == nod_constant)
                              continue;
                        if (global_agg(*ptr, group_list))
                              aggregate = true;
                        else if (!group_list || invalid_syn_field(*ptr, group_list))
                              normal_field = true;
                  }
            }

      default:
            break;
      }

      if (normal_field && aggregate)
            IBERROR(451);

      return aggregate;
}


static bool invalid_nod_field( const qli_nod* node, const qli_nod* list)
{
/**************************************
 *
 *    i n v a l i d _ n o d _ f i e l d
 *
 **************************************
 *
 * Functional description
 *
 *    Validate that an expanded field / context
 *    pair is in a specified list.  Thus is used
 *    in one instance to check that a simple field selected
 *    through a grouping rse is a grouping field -
 *    thus a valid field reference.
 *
 **************************************/
      if (!list)
            return true;

      bool invalid = false;

      if (node->nod_type == nod_field)
      {
            const qli_fld* field = (qli_fld*) node->nod_arg[e_fld_field];
            const qli_ctx* context = (qli_ctx*) node->nod_arg[e_fld_context];
            const qli_nod* const* ptr = list->nod_arg;
            for (const qli_nod* const* const end = ptr + list->nod_count; ptr < end; ptr++)
            {
                  if (field == (qli_fld*) (*ptr)->nod_arg[e_fld_field] &&
                        context == (qli_ctx*) (*ptr)->nod_arg[e_fld_context])
                  {
                        return false;
                  }
            }
            return true;
      }
      else
      {
            const qli_nod* const* ptr = node->nod_arg;
            for (const qli_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++)
            {
                  switch ((*ptr)->nod_type)
                  {
                  case nod_field:
                  case nod_add:
                  case nod_subtract:
                  case nod_multiply:
                  case nod_divide:
                  case nod_negate:
                  case nod_concatenate:
                  case nod_substr:
                  case nod_format:
                  case nod_choice:
                  case nod_function:
                  case nod_upcase:
                  case nod_lowcase:
                        invalid |= invalid_nod_field(*ptr, list);
                  }
            }
      }

      return invalid;
}


static bool invalid_syn_field( const qli_syntax* syn_node, const qli_syntax* list)
{
/**************************************
 *
 *    i n v a l i d _ s y n _ f i e l d
 *
 **************************************
 *
 * Functional description
 *    Make sure an unexpanded simple field selected
 *    through a grouping rse is a grouping field -
 *    thus a valid field reference.  For the sake of
 *      argument, we'll match qualified to unqualified
 *    reference, but qualified reference must match
 *    completely.
 *
 *    One more thought.  If this miserable thing is
 *    a wild card, let it through and expand it
 *    correctly later.
 *
 **************************************/
      if (syn_node->syn_type == nod_star)
            return false;

      if (!list)
            return true;

      bool invalid = false;

      if (syn_node->syn_type == nod_field)
      {
            const qli_name* fctx = NULL;
            const qli_name* fname = (qli_name*) syn_node->syn_arg[0];
            if (syn_node->syn_count == 2)
            {
                  fctx = fname;
                  fname = (qli_name*) syn_node->syn_arg[1];
            }

            for (SSHORT count = list->syn_count; count;)
            {
                  const qli_name* gctx = NULL;
                  const qli_syntax* element = list->syn_arg[--count];
                  const qli_name* gname = (qli_name*) element->syn_arg[0];
                  if (element->syn_count == 2)
                  {
                        gctx = gname;
                        gname = (qli_name*) element->syn_arg[1];
                  }
                  if (!strcmp(fname->nam_string, gname->nam_string))
                  {
                        if (!gctx || !fctx || !strcmp(fctx->nam_string, gctx->nam_string))
                        {
                              return false;
                        }
                  }
            }
            return true;
      }
      else
      {
            const qli_syntax* const* ptr = syn_node->syn_arg;
            for (const qli_syntax* const* const end = ptr + syn_node->syn_count; ptr < end; ptr++)
            {
                  switch ((*ptr)->syn_type)
                  {
                  case nod_field:
                  case nod_add:
                  case nod_subtract:
                  case nod_multiply:
                  case nod_divide:
                  case nod_negate:
                  case nod_concatenate:
                  case nod_substr:
                  case nod_format:
                  case nod_choice:
                  case nod_function:
                  case nod_upcase:
                  case nod_lowcase:
                        invalid |= invalid_syn_field(*ptr, list);
                  }
            }
      }

      return invalid;
}


static qli_nod* make_and( qli_nod* expr1, qli_nod* expr2)
{
/**************************************
 *
 *    m a k e _ a n d
 *
 **************************************
 *
 * Functional description
 *    Combine two expressions, each possible null, into at most
 *    a single boolean.
 *
 **************************************/
      if (!expr1)
            return expr2;

      if (!expr2)
            return expr1;

      qli_nod* node = make_node(nod_and, 2);
      node->nod_arg[0] = expr1;
      node->nod_arg[1] = expr2;

      return node;
}


static qli_nod* make_assignment( qli_nod* target, qli_nod* initial, qli_lls* right)
{
/**************************************
 *
 *    m a k e _ a s s i g n m e n t
 *
 **************************************
 *
 * Functional description
 *    Generate a prompt and assignment to a field.
 *
 **************************************/
      qli_fld* field = (qli_fld*) target->nod_arg[e_fld_field];
      qli_lls* stack = NULL;
      ALLQ_push((blk*) target->nod_arg[e_fld_context], &stack);

      qli_nod* prompt;

      if (field->fld_dtype == dtype_blob)
      {
            prompt = make_node(nod_edit_blob, e_edt_count);
            prompt->nod_count = 0;
            prompt->nod_arg[e_edt_name] = (qli_nod*) field->fld_name->sym_string;
            if (initial)
            {
                  prompt->nod_count = 1;
                  prompt->nod_arg[e_edt_input] = expand_expression((qli_syntax*) initial, right);
            }
      }
      else
      {
            prompt = make_node(nod_prompt, e_prm_count);
            prompt->nod_arg[e_prm_prompt] = (qli_nod*) field->fld_name->sym_string;
            prompt->nod_arg[e_prm_field] = (qli_nod*) field;
      }

      qli_nod* assignment = make_node(nod_assign, e_asn_count);
      assignment->nod_arg[e_asn_to] = target;
      assignment->nod_arg[e_asn_from] = prompt;

      if (field->fld_validation)
            assignment->nod_arg[e_asn_valid] = expand_expression(field->fld_validation, stack);
      else
            --assignment->nod_count;

      ALLQ_pop(&stack);

      return assignment;
}


static qli_nod* make_field( qli_fld* field, qli_ctx* context)
{
/**************************************
 *
 *    m a k e _ f i e l d
 *
 **************************************
 *
 * Functional description
 *    Make a field block.  Not too tough.
 *
 **************************************/
      qli_nod* node = make_node(nod_field, e_fld_count);
      node->nod_count = 0;
      node->nod_arg[e_fld_field] = (qli_nod*) field;
      node->nod_arg[e_fld_context] = (qli_nod*) context;

      if (context->ctx_variable)
            node->nod_type = nod_variable;

      return node;
}


static qli_nod* make_list( qli_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.
 *
 **************************************/
      qli_lls* temp = stack;
      USHORT count = 0;

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

      qli_nod* node = make_node(nod_list, count);
      qli_nod** ptr = &node->nod_arg[count];

      while (stack)
            *--ptr = (qli_nod*) ALLQ_pop(&stack);

      return node;
}


static qli_nod* make_node( nod_t type, USHORT count)
{
/**************************************
 *
 *    m a k e _ n o d e
 *
 **************************************
 *
 * Functional description
 *    Allocate a node and fill in some basic stuff.
 *
 **************************************/
      qli_nod* node = (qli_nod*) ALLOCDV(type_nod, count);
      node->nod_type = type;
      node->nod_count = count;

      return node;
}


static qli_nod* negate( qli_nod* expr)
{
/**************************************
 *
 *    n e g a t e
 *
 **************************************
 *
 * Functional description
 *    Build negation of expression.
 *
 **************************************/
      qli_nod* node = make_node(nod_not, 1);
      node->nod_arg[0] = expr;

      return node;
}


static qli_nod* possible_literal(qli_syntax* input, qli_lls* stack, bool upper_flag)
{
/**************************************
 *
 *    p o s s i b l e _ l i t e r a l
 *
 **************************************
 *
 * Functional description
 *    Check to see if a value node is an unresolved name.  If so,
 *    transform it into a constant expression.  This is used to
 *    correct "informalities" in relational expressions.
 *
 **************************************/
      qli_ctx* context;

      // If the value isn't a field, is qualified, or can be resolved,
      // it doesn't qualify for conversion.  Return NULL.

      if (input->syn_type != nod_field || input->syn_count != 1 || resolve(input, stack, &context))
      {
            return NULL;
      }

      const qli_name* name = (qli_name*) input->syn_arg[0];
      USHORT l = name->nam_length;
      qli_const* constant = (qli_const*) ALLOCDV(type_con, l);
      constant->con_desc.dsc_dtype = dtype_text;
      constant->con_desc.dsc_length = l;
      constant->con_desc.dsc_address = constant->con_data;
      QLI_validate_desc(constant->con_desc);
      TEXT* p = (TEXT*) constant->con_data;
      const TEXT* q = name->nam_string;

      if (upper_flag)
      {
            if (l)
                  do {
                        const TEXT c = *q++;
                        *p++ = UPPER(c);
                  } while (--l);
      }
      else if (l)
            do {
                  const TEXT c = *q++;
                  *p++ = (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c;

            } while (--l);

      qli_nod* node = make_node(nod_constant, 0);
      node->nod_desc = constant->con_desc;

      return node;
}


static qli_nod* post_map( qli_nod* node, qli_ctx* context)
{
/**************************************
 *
 *    p o s t _ m a p
 *
 **************************************
 *
 * Functional description
 *    Post an item to a map for a context.
 *
 **************************************/
      qli_map* map;

      // Check to see if the item has already been posted

      for (map = context->ctx_map; map; map = map->map_next)
      {
            if (CMP_node_match(node, map->map_node))
                  break;
      }

      if (!map)
      {
            map = (qli_map*) ALLOCD(type_map);
            map->map_next = context->ctx_map;
            context->ctx_map = map;
            map->map_node = node;
      }

      qli_nod* new_node = make_node(nod_map, e_map_count);
      new_node->nod_count = 0;
      new_node->nod_arg[e_map_context] = (qli_nod*) context;
      new_node->nod_arg[e_map_map] = (qli_nod*) map;
      new_node->nod_desc = node->nod_desc;

      return new_node;
}


static qli_fld* resolve( qli_syntax* node, qli_lls* stack, qli_ctx** out_context)
{
/**************************************
 *
 *    r e s o l v e
 *
 **************************************
 *
 * Functional description
 *    Resolve a field node against a context stack.  Return both the
 *    field block (by value)  and the corresponding context block (by
 *    reference).  Return NULL if field can't be resolved.
 *
 **************************************/
      qli_rel* relation;

      // Look thru context stack looking for a context that will resolve
      // all name segments.  If the context is a secondary context, require
      // that the context name be given explicitly (used for special STORE
      // context).

      qli_name** base = (qli_name**) node->syn_arg;

      for (; stack; stack = stack->lls_next)
      {
            qli_ctx* context = (qli_ctx*) stack->lls_object;
            *out_context = context;
            qli_name** ptr = base + node->syn_count;
            const qli_name* name = *--ptr;

            switch (context->ctx_type)
            {
            case CTX_VARIABLE:
                  if (ptr == base)
                        for (qli_fld* field = context->ctx_variable; field; field = field->fld_next)
                        {
                              if (compare_names(name, field->fld_name) ||
                                    compare_names(name, field->fld_query_name))
                              {
                                    return field;
                              }
                        }
                  break;

            case CTX_RELATION:
                  if (context->ctx_primary)
                  {
                        *out_context = context = context->ctx_primary;
                        if (!compare_names((qli_name*) node->syn_arg[0], context->ctx_symbol))
                              break;
                  }
                  relation = context->ctx_relation;

                  for (qli_fld* field = relation->rel_fields; field; field = field->fld_next)
                        if (compare_names(name, field->fld_name) || compare_names(name, field->fld_query_name))
                        {
                              if (ptr == base)
                                    return field;

                              name = *--ptr;

                              if (compare_names(name, relation->rel_symbol))
                              {
                                    if (ptr == base)
                                          return field;

                                    name = *--ptr;
                              }

                              if (compare_names(name, context->ctx_symbol))
                              {
                                    if (ptr == base)
                                          return field;
                              }
                              break;
                        }
                  break;
            }
      }

      // We didn't resolve all name segments.  Let somebody else worry about it.

      return NULL;
}



static void resolve_really( qli_fld* variable, const qli_syntax* field_node)
{
/**************************************
 *
 *    r e s o l v e _ r e a l l y
 *
 **************************************
 *
 * Functional description
 *    Resolve a field reference entirely.
 *
 **************************************/

      // For ease, break down the syntax block.
      // It should contain at least one name; two names are a  potential ambiguity:
      // check for a qli_dbb (<db>.<glo_fld>), then for a rel (<rel>.<fld>).

      USHORT offset = field_node->syn_count;
      const qli_name* fld_name = (qli_name*) field_node->syn_arg[--offset];

      qli_name* rel_name = NULL;
      //qli_name* db_name = NULL;
      if (offset)
      {
            rel_name = (qli_name*) field_node->syn_arg[--offset];
            //if (offset)
            //    db_name = (qli_name*) field_node->syn_arg[--offset];
      }

    bool resolved = false;
    bool local = false;
    qli_fld* field = NULL;

      if (field_node->syn_count == 1)
            resolved = MET_declare(0, variable, fld_name);
      else if (field_node->syn_count == 2)
      {
            for (qli_symbol* symbol = rel_name->nam_symbol; symbol; symbol = symbol->sym_homonym)
            {
                  if (symbol->sym_type == SYM_database)
                  {
                        qli_dbb* dbb = (qli_dbb*) symbol->sym_object;
                        resolved = MET_declare(dbb, variable, fld_name);
                        break;                  // should be only one db in homonym list
                  }
            }

            if (!resolved)
            {
                  for (qli_dbb* dbb = QLI_databases; dbb && !resolved; dbb = dbb->dbb_next)
                        for (qli_symbol* symbol = rel_name->nam_symbol; symbol; symbol = symbol->sym_homonym)
                        {
                              qli_rel* relation;
                              if (symbol->sym_type == SYM_relation &&
                                    (relation = (qli_rel*) symbol->sym_object) && relation->rel_database == dbb)
                              {
                                    if (!relation->rel_fields)
                                          MET_fields(relation);
                                    for (field = relation->rel_fields; field; field = field->fld_next)
                                    {
                                          resolved = local = compare_names(fld_name, field->fld_name);
                                          if (resolved)
                                                break;
                                    }
                                    break;      // should be only one rel in homonym list for each db
                              }
                        }
            }
      }
      else
      {
            qli_rel* relation = variable->fld_relation;
            if (!relation->rel_fields)
                  MET_fields(relation);
            for (field = relation->rel_fields; field; field = field->fld_next)
            {
                  resolved = local = compare_names(fld_name, field->fld_name);
                  if (resolved)
                        break;
            }
      }

      if (!resolved)
            IBERROR(155);
            // Msg155 field referenced in BASED ON can not be resolved against readied databases

      if (local)
      {
            variable->fld_dtype = field->fld_dtype;
            variable->fld_length = field->fld_length;
            variable->fld_scale = field->fld_scale;
            variable->fld_sub_type = field->fld_sub_type;
            variable->fld_sub_type_missing = field->fld_sub_type_missing;
            if (!variable->fld_edit_string)
                  variable->fld_edit_string = field->fld_edit_string;
            if (!variable->fld_query_header)
                  variable->fld_query_header = field->fld_query_header;
            if (!variable->fld_query_name)
                  variable->fld_query_name = field->fld_query_name;
      }
}




Generated by  Doxygen 1.6.0   Back to index