sql.c - ctags-5.8

Global variables defined

Data types defined

Functions defined

Macros defined

Source code

/*
*    $Id: sql.c 703 2009-03-14 22:06:12Z dfishburn $
*
*    Copyright (c) 2002-2003, Darren Hiebert
*
*    This source code is released for free distribution under the terms of the
*    GNU General Public License.
*
*    This module contains functions for generating tags for PL/SQL language
*    files.
*/

/*
*     INCLUDE FILES
*/
#include "general.h"    /* must always come first */

#include <ctype.h>    /* to define isalpha () */
#include <setjmp.h>
#ifdef DEBUG
#include <stdio.h>
#endif

#include "debug.h"
#include "entry.h"
#include "keyword.h"
#include "parse.h"
#include "read.h"
#include "routines.h"
#include "vstring.h"

/*
*    On-line "Oracle Database PL/SQL Language Reference":
*    http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/toc.htm
*
*    Sample PL/SQL code is available from:
*    http://www.orafaq.com/faqscrpt.htm#GENPLSQL
*
*    On-line SQL Anywhere Documentation
*    http://www.ianywhere.com/developer/product_manuals/sqlanywhere/index.html
*/

/*
*     MACROS
*/
#define isType(token,t)        (boolean) ((token)->type == (t))
#define isKeyword(token,k)    (boolean) ((token)->keyword == (k))

/*
*     DATA DECLARATIONS
*/

typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;

/*
* Used to specify type of keyword.
*/
typedef enum eKeywordId {
    KEYWORD_NONE = -1,
    KEYWORD_is,
    KEYWORD_begin,
    KEYWORD_body,
    KEYWORD_cursor,
    KEYWORD_declare,
    KEYWORD_end,
    KEYWORD_function,
    KEYWORD_if,
    KEYWORD_loop,
    KEYWORD_case,
    KEYWORD_for,
    KEYWORD_call,
    KEYWORD_package,
    KEYWORD_pragma,
    KEYWORD_procedure,
    KEYWORD_record,
    KEYWORD_object,
    KEYWORD_ref,
    KEYWORD_rem,
    KEYWORD_return,
    KEYWORD_returns,
    KEYWORD_subtype,
    KEYWORD_table,
    KEYWORD_trigger,
    KEYWORD_type,
    KEYWORD_index,
    KEYWORD_event,
    KEYWORD_publication,
    KEYWORD_service,
    KEYWORD_domain,
    KEYWORD_datatype,
    KEYWORD_result,
    KEYWORD_url,
    KEYWORD_internal,
    KEYWORD_external,
    KEYWORD_when,
    KEYWORD_then,
    KEYWORD_variable,
    KEYWORD_exception,
    KEYWORD_at,
    KEYWORD_on,
    KEYWORD_primary,
    KEYWORD_references,
    KEYWORD_unique,
    KEYWORD_check,
    KEYWORD_constraint,
    KEYWORD_foreign,
    KEYWORD_ml_table,
    KEYWORD_ml_table_lang,
    KEYWORD_ml_table_dnet,
    KEYWORD_ml_table_java,
    KEYWORD_ml_table_chk,
    KEYWORD_ml_conn,
    KEYWORD_ml_conn_lang,
    KEYWORD_ml_conn_dnet,
    KEYWORD_ml_conn_java,
    KEYWORD_ml_conn_chk,
    KEYWORD_local,
    KEYWORD_temporary,
    KEYWORD_drop,
    KEYWORD_view,
    KEYWORD_synonym,
    KEYWORD_handler,
    KEYWORD_comment,
    KEYWORD_create,
    KEYWORD_go
} keywordId;

/*
* Used to determine whether keyword is valid for the token language and
*    what its ID is.
*/
typedef struct sKeywordDesc {
    const char *name;
    keywordId id;
} keywordDesc;

typedef enum eTokenType {
    TOKEN_UNDEFINED,
    TOKEN_BLOCK_LABEL_BEGIN,
    TOKEN_BLOCK_LABEL_END,
    TOKEN_CHARACTER,
    TOKEN_CLOSE_PAREN,
    TOKEN_SEMICOLON,
    TOKEN_COMMA,
    TOKEN_IDENTIFIER,
    TOKEN_KEYWORD,
    TOKEN_OPEN_PAREN,
    TOKEN_OPERATOR,
    TOKEN_OTHER,
    TOKEN_STRING,
    TOKEN_PERIOD,
    TOKEN_OPEN_CURLY,
    TOKEN_CLOSE_CURLY,
    TOKEN_OPEN_SQUARE,
    TOKEN_CLOSE_SQUARE,
    TOKEN_TILDE,
    TOKEN_FORWARD_SLASH
} tokenType;

typedef struct sTokenInfoSQL {
    tokenType    type;
    keywordId    keyword;
    vString *    string;
    vString *    scope;
    int         begin_end_nest_lvl;
    unsigned long lineNumber;
    fpos_t filePosition;
} tokenInfo;

/*
*    DATA DEFINITIONS
*/

static langType Lang_sql;

static jmp_buf Exception;

typedef enum {
    SQLTAG_CURSOR,
    SQLTAG_PROTOTYPE,
    SQLTAG_FUNCTION,
    SQLTAG_FIELD,
    SQLTAG_LOCAL_VARIABLE,
    SQLTAG_BLOCK_LABEL,
    SQLTAG_PACKAGE,
    SQLTAG_PROCEDURE,
    SQLTAG_RECORD,
    SQLTAG_SUBTYPE,
    SQLTAG_TABLE,
    SQLTAG_TRIGGER,
    SQLTAG_VARIABLE,
    SQLTAG_INDEX,
    SQLTAG_EVENT,
    SQLTAG_PUBLICATION,
    SQLTAG_SERVICE,
    SQLTAG_DOMAIN,
    SQLTAG_VIEW,
    SQLTAG_SYNONYM,
    SQLTAG_MLTABLE,
    SQLTAG_MLCONN,
    SQLTAG_COUNT
} sqlKind;

static kindOption SqlKinds [] = {
    { TRUE'c', "cursor",          "cursors"                   },
    { FALSE, 'd', "prototype",      "prototypes"               },
    { TRUE'f', "function",      "functions"               },
    { TRUE'F', "field",          "record fields"           },
    { FALSE, 'l', "local",          "local variables"           },
    { TRUE'L', "label",          "block label"               },
    { TRUE'P', "package",      "packages"               },
    { TRUE'p', "procedure",      "procedures"               },
    { FALSE, 'r', "record",          "records"                   },
    { TRUE's', "subtype",      "subtypes"               },
    { TRUE't', "table",          "tables"                   },
    { TRUE'T', "trigger",      "triggers"               },
    { TRUE'v', "variable",      "variables"               },
    { TRUE'i', "index",          "indexes"                   },
    { TRUE'e', "event",          "events"                   },
    { TRUE'U', "publication""publications"           },
    { TRUE'R', "service",      "services"               },
    { TRUE'D', "domain",          "domains"                   },
    { TRUE'V', "view",          "views"                   },
    { TRUE'n', "synonym",      "synonyms"               },
    { TRUE'x', "mltable",      "MobiLink Table Scripts" },
    { TRUE'y', "mlconn",          "MobiLink Conn Scripts"  }
};

static const keywordDesc SqlKeywordTable [] = {
    /* keyword        keyword ID */
    { "as",                                KEYWORD_is                      },
    { "is",                                KEYWORD_is                      },
    { "begin",                            KEYWORD_begin                  },
    { "body",                            KEYWORD_body                  },
    { "cursor",                            KEYWORD_cursor                  },
    { "declare",                        KEYWORD_declare                  },
    { "end",                            KEYWORD_end                      },
    { "function",                        KEYWORD_function              },
    { "if",                                KEYWORD_if                      },
    { "loop",                            KEYWORD_loop                  },
    { "case",                            KEYWORD_case                  },
    { "for",                            KEYWORD_for                      },
    { "call",                            KEYWORD_call                  },
    { "package",                        KEYWORD_package                  },
    { "pragma",                            KEYWORD_pragma                  },
    { "procedure",                        KEYWORD_procedure              },
    { "record",                            KEYWORD_record                  },
    { "object",                            KEYWORD_object                  },
    { "ref",                            KEYWORD_ref                      },
    { "rem",                            KEYWORD_rem                      },
    { "return",                            KEYWORD_return                  },
    { "returns",                        KEYWORD_returns                  },
    { "subtype",                        KEYWORD_subtype                  },
    { "table",                            KEYWORD_table                  },
    { "trigger",                        KEYWORD_trigger                  },
    { "type",                            KEYWORD_type                  },
    { "index",                            KEYWORD_index                  },
    { "event",                            KEYWORD_event                  },
    { "publication",                    KEYWORD_publication              },
    { "service",                        KEYWORD_service                  },
    { "domain",                            KEYWORD_domain                  },
    { "datatype",                        KEYWORD_datatype              },
    { "result",                            KEYWORD_result                  },
    { "url",                            KEYWORD_url                      },
    { "internal",                        KEYWORD_internal              },
    { "external",                        KEYWORD_external              },
    { "when",                            KEYWORD_when                  },
    { "then",                            KEYWORD_then                  },
    { "variable",                        KEYWORD_variable              },
    { "exception",                        KEYWORD_exception              },
    { "at",                                KEYWORD_at                      },
    { "on",                                KEYWORD_on                      },
    { "primary",                        KEYWORD_primary                  },
    { "references",                        KEYWORD_references              },
    { "unique",                            KEYWORD_unique                  },
    { "check",                            KEYWORD_check                  },
    { "constraint",                        KEYWORD_constraint              },
    { "foreign",                        KEYWORD_foreign                  },
    { "ml_add_table_script",            KEYWORD_ml_table              },
    { "ml_add_lang_table_script",        KEYWORD_ml_table_lang          },
    { "ml_add_dnet_table_script",        KEYWORD_ml_table_dnet          },
    { "ml_add_java_table_script",        KEYWORD_ml_table_java          },
    { "ml_add_lang_table_script_chk",    KEYWORD_ml_table_chk          },
    { "ml_add_connection_script",        KEYWORD_ml_conn                  },
    { "ml_add_lang_connection_script",    KEYWORD_ml_conn_lang          },
    { "ml_add_dnet_connection_script",    KEYWORD_ml_conn_dnet          },
    { "ml_add_java_connection_script",    KEYWORD_ml_conn_java          },
    { "ml_add_lang_conn_script_chk",    KEYWORD_ml_conn_chk           },
    { "local",                            KEYWORD_local                  },
    { "temporary",                        KEYWORD_temporary              },
    { "drop",                            KEYWORD_drop                  },
    { "view",                            KEYWORD_view                  },
    { "synonym",                        KEYWORD_synonym                  },
    { "handler",                        KEYWORD_handler                  },
    { "comment",                        KEYWORD_comment                  },
    { "create",                            KEYWORD_create                  },
    { "go",                                KEYWORD_go                      }
};

/*
*     FUNCTION DECLARATIONS
*/

/* Recursive calls */
static void parseBlock (tokenInfo *const token, const boolean local);
static void parseKeywords (tokenInfo *const token);
static void parseSqlFile (tokenInfo *const token);

/*
*     FUNCTION DEFINITIONS
*/

static boolean isIdentChar1 (const int c)
{
    /*
     * Other databases are less restrictive on the first character of
     * an identifier.
     * isIdentChar1 is used to identify the first character of an
     * identifier, so we are removing some restrictions.
     */
    return (boolean)
        (isalpha (c) || c == '@' || c == '_' );
}

static boolean isIdentChar (const int c)
{
    return (boolean)
        (isalpha (c) || isdigit (c) || c == '$' ||
         c == '@' || c == '_' || c == '#');
}

static boolean isCmdTerm (tokenInfo *const token)
{
    DebugStatement (
            debugPrintf (DEBUG_PARSE
                , "\n isCmdTerm: token same  tt:%d  tk:%d\n"
                , token->type
                , token->keyword
                );
            );

    /*
     * Based on the various customer sites I have been at
     * the most common command delimiters are
     *       ;
     *       ~
     *       /
     *       go
     * This routine will check for any of these, more
     * can easily be added by modifying readToken and
     * either adding the character to:
     *       enum eTokenType
     *       enum eTokenType
     */
    return ( isType (token, TOKEN_SEMICOLON) ||
            isType (token, TOKEN_TILDE) ||
            isType (token, TOKEN_FORWARD_SLASH) ||
            isKeyword (token, KEYWORD_go)
            );
}

static boolean isMatchedEnd(tokenInfo *const token, int nest_lvl)
{
    boolean terminated = FALSE;
    /*
     * Since different forms of SQL allow the use of
     * BEGIN
     * ...
     * END
     * blocks, some statements may not be terminated using
     * the standard delimiters:
     *       ;
     *       ~
     *       /
     *       go
     * This routine will check to see if we encounter and END
     * for the matching nest level of BEGIN ... END statements.
     * If we find one, then we can assume, the statement was terminated
     * since we have fallen through to the END statement of the BEGIN
     * block.
     */
    if ( nest_lvl > 0 && isKeyword (token, KEYWORD_end) )
    {
        if ( token->begin_end_nest_lvl == nest_lvl )
            terminated = TRUE;
    }

    return terminated;
}

static void buildSqlKeywordHash (void)
{
    const size_t count = sizeof (SqlKeywordTable) /
        sizeof (SqlKeywordTable [0]);
    size_t i;
    for (i = 0    ;  i < count  ;  ++i)
    {
        const keywordDesc* const p = &SqlKeywordTable [i];
        addKeyword (p->name, Lang_sql, (int) p->id);
    }
}

static tokenInfo *newToken (void)
{
    tokenInfo *const token = xMalloc (1, tokenInfo);

    token->type               = TOKEN_UNDEFINED;
    token->keyword            = KEYWORD_NONE;
    token->string             = vStringNew ();
    token->scope              = vStringNew ();
    token->begin_end_nest_lvl = 0;
    token->lineNumber         = getSourceLineNumber ();
    token->filePosition       = getInputFilePosition ();

    return token;
}

static void deleteToken (tokenInfo *const token)
{
    vStringDelete (token->string);
    vStringDelete (token->scope);
    eFree (token);
}

/*
*     Tag generation functions
*/

static void makeConstTag (tokenInfo *const token, const sqlKind kind)
{
    if (SqlKinds [kind].enabled)
    {
        const char *const name = vStringValue (token->string);
        tagEntryInfo e;
        initTagEntry (&e, name);

        e.lineNumber   = token->lineNumber;
        e.filePosition = token->filePosition;
        e.kindName       = SqlKinds [kind].name;
        e.kind           = SqlKinds [kind].letter;

        makeTagEntry (&e);
    }
}

static void makeSqlTag (tokenInfo *const token, const sqlKind kind)
{
    vString *    fulltag;

    if (SqlKinds [kind].enabled)
    {
        /*
         * If a scope has been added to the token, change the token
         * string to include the scope when making the tag.
         */
        if ( vStringLength(token->scope) > 0 )
        {
            fulltag = vStringNew ();
            vStringCopy(fulltag, token->scope);
            vStringCatS (fulltag, ".");
            vStringCatS (fulltag, vStringValue(token->string));
            vStringTerminate(fulltag);
            vStringCopy(token->string, fulltag);
            vStringDelete (fulltag);
        }
        makeConstTag (token, kind);
    }
}

/*
*     Parsing functions
*/

static void parseString (vString *const string, const int delimiter)
{
    boolean end = FALSE;
    while (! end)
    {
        int c = fileGetc ();
        if (c == EOF)
            end = TRUE;
        /*
        else if (c == '\\')
        {
            c = fileGetc(); // This maybe a ' or ". //
            vStringPut(string, c);
        }
        */
        else if (c == delimiter)
            end = TRUE;
        else
            vStringPut (string, c);
    }
    vStringTerminate (string);
}

/*    Read a C identifier beginning with "firstChar" and places it into "name".
*/
static void parseIdentifier (vString *const string, const int firstChar)
{
    int c = firstChar;
    Assert (isIdentChar1 (c));
    do
    {
        vStringPut (string, c);
        c = fileGetc ();
    } while (isIdentChar (c));
    vStringTerminate (string);
    if (!isspace (c))
        fileUngetc (c);        /* unget non-identifier character */
}

static void readToken (tokenInfo *const token)
{
    int c;

    token->type            = TOKEN_UNDEFINED;
    token->keyword        = KEYWORD_NONE;
    vStringClear (token->string);

getNextChar:
    do
    {
        c = fileGetc ();
        token->lineNumber   = getSourceLineNumber ();
        token->filePosition = getInputFilePosition ();
        /*
         * Added " to the list of ignores, not sure what this
         * might break but it gets by this issue:
         *      create table "t1" (...)
         *
         * Darren, the code passes all my tests for both
         * Oracle and SQL Anywhere, but maybe you can tell me
         * what this may effect.
         */
    }
    while (c == '\t'  ||  c == ' ' ||  c == '\n');

    switch (c)
    {
        case EOF: longjmp (Exception, (int)ExceptionEOF);    break;
        case '(': token->type = TOKEN_OPEN_PAREN;        break;
        case ')': token->type = TOKEN_CLOSE_PAREN;        break;
        case ';': token->type = TOKEN_SEMICOLON;        break;
        case '.': token->type = TOKEN_PERIOD;            break;
        case ',': token->type = TOKEN_COMMA;            break;
        case '{': token->type = TOKEN_OPEN_CURLY;        break;
        case '}': token->type = TOKEN_CLOSE_CURLY;        break;
        case '~': token->type = TOKEN_TILDE;            break;
        case '[': token->type = TOKEN_OPEN_SQUARE;        break;
        case ']': token->type = TOKEN_CLOSE_SQUARE;        break;

        case '\'':
        case '"':
                  token->type = TOKEN_STRING;
                  parseString (token->string, c);
                  token->lineNumber = getSourceLineNumber ();
                  token->filePosition = getInputFilePosition ();
                  break;

        case '-':
                  c = fileGetc ();
                  if (c == '-')        /* -- is this the start of a comment? */
                  {
                      fileSkipToCharacter ('\n');
                      goto getNextChar;
                  }
                  else
                  {
                      if (!isspace (c))
                          fileUngetc (c);
                      token->type = TOKEN_OPERATOR;
                  }
                  break;

        case '<':
        case '>':
                  {
                      const int initial = c;
                      int d = fileGetc ();
                      if (d == initial)
                      {
                          if (initial == '<')
                              token->type = TOKEN_BLOCK_LABEL_BEGIN;
                          else
                              token->type = TOKEN_BLOCK_LABEL_END;
                      }
                      else
                      {
                          fileUngetc (d);
                          token->type = TOKEN_UNDEFINED;
                      }
                      break;
                  }

        case '\\':
                  c = fileGetc ();
                  if (c != '\\'  && c != '"'  && c != '\''  &&  !isspace (c))
                      fileUngetc (c);
                  token->type = TOKEN_CHARACTER;
                  token->lineNumber = getSourceLineNumber ();
                  token->filePosition = getInputFilePosition ();
                  break;

        case '/':
                  {
                      int d = fileGetc ();
                      if ( (d != '*') &&        /* is this the start of a comment? */
                              (d != '/') )            /* is a one line comment? */
                      {
                          token->type = TOKEN_FORWARD_SLASH;
                          fileUngetc (d);
                      }
                      else
                      {
                          if (d == '*')
                          {
                              do
                              {
                                  fileSkipToCharacter ('*');
                                  c = fileGetc ();
                                  if (c == '/')
                                      break;
                                  else
                                      fileUngetc (c);
                              } while (c != EOF && c != '\0');
                              goto getNextChar;
                          }
                          else if (d == '/')    /* is this the start of a comment?  */
                          {
                              fileSkipToCharacter ('\n');
                              goto getNextChar;
                          }
                      }
                      break;
                  }

        default:
                  if (! isIdentChar1 (c))
                      token->type = TOKEN_UNDEFINED;
                  else
                  {
                      parseIdentifier (token->string, c);
                      token->lineNumber = getSourceLineNumber ();
                      token->filePosition = getInputFilePosition ();
                      token->keyword = analyzeToken (token->string, Lang_sql);
                      if (isKeyword (token, KEYWORD_rem))
                      {
                          vStringClear (token->string);
                          fileSkipToCharacter ('\n');
                          goto getNextChar;
                      }
                      else if (isKeyword (token, KEYWORD_NONE))
                          token->type = TOKEN_IDENTIFIER;
                      else
                          token->type = TOKEN_KEYWORD;
                  }
                  break;
    }
}

/*
*     Token parsing functions
*/

/*
* static void addContext (tokenInfo* const parent, const tokenInfo* const child)
* {
*       if (vStringLength (parent->string) > 0)
*       {
*           vStringCatS (parent->string, ".");
*       }
*       vStringCatS (parent->string, vStringValue(child->string));
*       vStringTerminate(parent->string);
* }
*/

static void addToScope (tokenInfo* const token, vString* const extra)
{
    if (vStringLength (token->scope) > 0)
    {
        vStringCatS (token->scope, ".");
    }
    vStringCatS (token->scope, vStringValue(extra));
    vStringTerminate(token->scope);
}

/*
*     Scanning functions
*/

static void findToken (tokenInfo *const token, const tokenType type)
{
    while (! isType (token, type))
    {
        readToken (token);
    }
}

static void findCmdTerm (tokenInfo *const token, const boolean check_first)
{
    int begin_end_nest_lvl = token->begin_end_nest_lvl;

    if ( check_first )
    {
        if ( isCmdTerm(token) )
            return;
    }
    do
    {
        readToken (token);
    } while ( !isCmdTerm(token) && !isMatchedEnd(token, begin_end_nest_lvl) );
}

static void skipToMatched(tokenInfo *const token)
{
    int nest_level = 0;
    tokenType open_token;
    tokenType close_token;

    switch (token->type)
    {
        case TOKEN_OPEN_PAREN:
            open_token  = TOKEN_OPEN_PAREN;
            close_token = TOKEN_CLOSE_PAREN;
            break;
        case TOKEN_OPEN_CURLY:
            open_token  = TOKEN_OPEN_CURLY;
            close_token = TOKEN_CLOSE_CURLY;
            break;
        case TOKEN_OPEN_SQUARE:
            open_token  = TOKEN_OPEN_SQUARE;
            close_token = TOKEN_CLOSE_SQUARE;
            break;
        default:
            return;
    }

    /*
     * This routine will skip to a matching closing token.
     * It will also handle nested tokens like the (, ) below.
     *     (    name varchar(30), text binary(10)  )
     */

    if (isType (token, open_token))
    {
        nest_level++;
        while (! (isType (token, close_token) && (nest_level == 0)))
        {
            readToken (token);
            if (isType (token, open_token))
            {
                nest_level++;
            }
            if (isType (token, close_token))
            {
                if (nest_level > 0)
                {
                    nest_level--;
                }
            }
        }
        readToken (token);
    }
}

static void skipArgumentList (tokenInfo *const token)
{
    /*
     * Other databases can have arguments with fully declared
     * datatypes:
     *     (    name varchar(30), text binary(10)  )
     * So we must check for nested open and closing parantheses
     */

    if (isType (token, TOKEN_OPEN_PAREN))    /* arguments? */
    {
        skipToMatched (token);
    }
}

static void parseSubProgram (tokenInfo *const token)
{
    tokenInfo *const name  = newToken ();

    /*
     * This must handle both prototypes and the body of
     * the procedures.
     *
     * Prototype:
     *       FUNCTION func_name RETURN integer;
     *       PROCEDURE proc_name( parameters );
     * Procedure
     *       FUNCTION GET_ML_USERNAME RETURN VARCHAR2
     *       IS
     *       BEGIN
     *           RETURN v_sync_user_id;
     *       END GET_ML_USERNAME;
     *
     *       PROCEDURE proc_name( parameters )
     *           IS
     *           BEGIN
     *           END;
     *       CREATE PROCEDURE proc_name( parameters )
     *           EXTERNAL NAME ... ;
     *       CREATE PROCEDURE proc_name( parameters )
     *           BEGIN
     *           END;
     *
     *       CREATE FUNCTION f_GetClassName(
     *           IN @object VARCHAR(128)
     *          ,IN @code   VARCHAR(128)
     *       )
     *       RETURNS VARCHAR(200)
     *       DETERMINISTIC
     *       BEGIN
     *
     *           IF( @object = 'user_state' ) THEN
     *               SET something = something;
     *           END IF;
     *
     *           RETURN @name;
     *       END;
     */
    const sqlKind kind = isKeyword (token, KEYWORD_function) ?
        SQLTAG_FUNCTION : SQLTAG_PROCEDURE;
    Assert (isKeyword (token, KEYWORD_function) ||
            isKeyword (token, KEYWORD_procedure));
    readToken (name);
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        readToken (name);
        readToken (token);
    }
    if (isType (token, TOKEN_OPEN_PAREN))
    {
        /* Reads to the next token after the TOKEN_CLOSE_PAREN */
        skipArgumentList(token);
    }

    if (kind == SQLTAG_FUNCTION)
    {
        if (isKeyword (token, KEYWORD_return) || isKeyword (token, KEYWORD_returns))
        {
            /* Read datatype */
            readToken (token);
            /*
             * Read token after which could be the
             * command terminator if a prototype
             * or an open parantheses
             */
            readToken (token);
            if (isType (token, TOKEN_OPEN_PAREN))
            {
                /* Reads to the next token after the TOKEN_CLOSE_PAREN */
                skipArgumentList(token);
            }
        }
    }
    if( isCmdTerm (token) )
    {
        makeSqlTag (name, SQLTAG_PROTOTYPE);
    }
    else
    {
        while (!(isKeyword (token, KEYWORD_is) ||
                    isKeyword (token, KEYWORD_begin) ||
                    isKeyword (token, KEYWORD_at) ||
                    isKeyword (token, KEYWORD_internal) ||
                    isKeyword (token, KEYWORD_external) ||
                    isKeyword (token, KEYWORD_url) ||
                    isCmdTerm (token)
                )
              )
        {
            if ( isKeyword (token, KEYWORD_result) )
            {
                readToken (token);
                if (isType (token, TOKEN_OPEN_PAREN))
                {
                    /* Reads to the next token after the TOKEN_CLOSE_PAREN */
                    skipArgumentList(token);
                }
            } else {
                readToken (token);
            }
        }
        if (isKeyword (token, KEYWORD_at) ||
                isKeyword (token, KEYWORD_url) ||
                isKeyword (token, KEYWORD_internal) ||
                isKeyword (token, KEYWORD_external) )
        {
            addToScope(token, name->string);
            if (isType (name, TOKEN_IDENTIFIER) ||
                    isType (name, TOKEN_STRING) ||
                    !isKeyword (token, KEYWORD_NONE)
               )
                makeSqlTag (name, kind);

            vStringClear (token->scope);
        }
        if (isKeyword (token, KEYWORD_is) ||
                isKeyword (token, KEYWORD_begin) )
        {
            addToScope(token, name->string);
            if (isType (name, TOKEN_IDENTIFIER) ||
                    isType (name, TOKEN_STRING) ||
                    !isKeyword (token, KEYWORD_NONE)
               )
                makeSqlTag (name, kind);

            parseBlock (token, TRUE);
            vStringClear (token->scope);
        }
    }
    deleteToken (name);
}

static void parseRecord (tokenInfo *const token)
{
    /*
     * Make it a bit forgiving, this is called from
     * multiple functions, parseTable, parseType
     */
    if (!isType (token, TOKEN_OPEN_PAREN))
        readToken (token);

    Assert (isType (token, TOKEN_OPEN_PAREN));
    do
    {
        if ( isType (token, TOKEN_COMMA) || isType (token, TOKEN_OPEN_PAREN) )
            readToken (token);

        /*
         * Create table statements can end with various constraints
         * which must be excluded from the SQLTAG_FIELD.
         *      create table t1 (
         *          c1 integer,
         *          c2 char(30),
         *          c3 numeric(10,5),
         *          c4 integer,
         *          constraint whatever,
         *          primary key(c1),
         *          foreign key (),
         *          check ()
         *      )
         */
        if (! (isKeyword(token, KEYWORD_primary) ||
                    isKeyword(token, KEYWORD_references) ||
                    isKeyword(token, KEYWORD_unique) ||
                    isKeyword(token, KEYWORD_check) ||
                    isKeyword(token, KEYWORD_constraint) ||
                    isKeyword(token, KEYWORD_foreign) ) )
        {
            if (isType (token, TOKEN_IDENTIFIER) ||
                    isType (token, TOKEN_STRING))
                makeSqlTag (token, SQLTAG_FIELD);
        }

        while (!(isType (token, TOKEN_COMMA) ||
                    isType (token, TOKEN_CLOSE_PAREN) ||
                    isType (token, TOKEN_OPEN_PAREN)
                ))
        {
            readToken (token);
            /*
             * A table structure can look like this:
             *      create table t1 (
             *          c1 integer,
             *          c2 char(30),
             *          c3 numeric(10,5),
             *          c4 integer
             *      )
             * We can't just look for a COMMA or CLOSE_PAREN
             * since that will not deal with the numeric(10,5)
             * case.  So we need to skip the argument list
             * when we find an open paren.
             */
            if (isType (token, TOKEN_OPEN_PAREN))
            {
                /* Reads to the next token after the TOKEN_CLOSE_PAREN */
                skipArgumentList(token);
            }
        }
    } while (! isType (token, TOKEN_CLOSE_PAREN));
}

static void parseType (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();
    vString * saveScope = vStringNew ();

    vStringCopy(saveScope, token->scope);
    /* If a scope has been set, add it to the name */
    addToScope (name, token->scope);
    readToken (name);
    if (isType (name, TOKEN_IDENTIFIER))
    {
        readToken (token);
        if (isKeyword (token, KEYWORD_is))
        {
            readToken (token);
            addToScope (token, name->string);
            switch (token->keyword)
            {
                case KEYWORD_record:
                case KEYWORD_object:
                    makeSqlTag (name, SQLTAG_RECORD);
                    parseRecord (token);
                    break;

                case KEYWORD_table:
                    makeSqlTag (name, SQLTAG_TABLE);
                    break;

                case KEYWORD_ref:
                    readToken (token);
                    if (isKeyword (token, KEYWORD_cursor))
                        makeSqlTag (name, SQLTAG_CURSOR);
                    break;

                default: break;
            }
            vStringClear (token->scope);
        }
    }
    vStringCopy(token->scope, saveScope);
    deleteToken (name);
    vStringDelete(saveScope);
}

static void parseSimple (tokenInfo *const token, const sqlKind kind)
{
    /* This will simply make the tagname from the first word found */
    readToken (token);
    if (isType (token, TOKEN_IDENTIFIER) ||
            isType (token, TOKEN_STRING))
        makeSqlTag (token, kind);
}

static void parseDeclare (tokenInfo *const token, const boolean local)
{
    /*
     * PL/SQL declares are of this format:
     *      IS|AS
     *      [declare]
     *         CURSOR curname ...
     *         varname1 datatype;
     *         varname2 datatype;
     *         varname3 datatype;
     *      begin
     */

    if (isKeyword (token, KEYWORD_declare))
        readToken (token);
    while (! isKeyword (token, KEYWORD_begin) && ! isKeyword (token, KEYWORD_end))
    {
        switch (token->keyword)
        {
            case KEYWORD_cursor:    parseSimple (token, SQLTAG_CURSOR); break;
            case KEYWORD_function:    parseSubProgram (token); break;
            case KEYWORD_procedure: parseSubProgram (token); break;
            case KEYWORD_subtype:    parseSimple (token, SQLTAG_SUBTYPE); break;
            case KEYWORD_trigger:    parseSimple (token, SQLTAG_TRIGGER); break;
            case KEYWORD_type:        parseType (token); break;

            default:
                                    if (isType (token, TOKEN_IDENTIFIER))
                                    {
                                        if (local)
                                        {
                                            makeSqlTag (token, SQLTAG_LOCAL_VARIABLE);
                                        }
                                        else
                                        {
                                            makeSqlTag (token, SQLTAG_VARIABLE);
                                        }
                                    }
                                    break;
        }
        findToken (token, TOKEN_SEMICOLON);
        readToken (token);
    }
}

static void parseDeclareANSI (tokenInfo *const token, const boolean local)
{
    tokenInfo *const type = newToken ();
    /*
     * ANSI declares are of this format:
     *     BEGIN
     *         DECLARE varname1 datatype;
     *         DECLARE varname2 datatype;
     *         ...
     *
     * This differ from PL/SQL where DECLARE preceeds the BEGIN block
     * and the DECLARE keyword is not repeated.
     */
    while (isKeyword (token, KEYWORD_declare))
    {
        readToken (token);
        readToken (type);

        if (isKeyword (type, KEYWORD_cursor))
            makeSqlTag (token, SQLTAG_CURSOR);
        else if (isKeyword (token, KEYWORD_local) &&
                isKeyword (type, KEYWORD_temporary))
        {
            /*
             * DECLARE LOCAL TEMPORARY TABLE table_name (
             *      c1 int,
             *      c2 int
             * );
             */
            readToken (token);
            if (isKeyword (token, KEYWORD_table))
            {
                readToken (token);
                if (isType(token, TOKEN_IDENTIFIER) ||
                        isType(token, TOKEN_STRING) )
                {
                    makeSqlTag (token, SQLTAG_TABLE);
                }
            }
        }
        else if (isType (token, TOKEN_IDENTIFIER) ||
                isType (token, TOKEN_STRING))
        {
            if (local)
                makeSqlTag (token, SQLTAG_LOCAL_VARIABLE);
            else
                makeSqlTag (token, SQLTAG_VARIABLE);
        }
        findToken (token, TOKEN_SEMICOLON);
        readToken (token);
    }
    deleteToken (type);
}

static void parseLabel (tokenInfo *const token)
{
    /*
     * A label has this format:
     *       <<tobacco_dependency>>
     *       DECLARE
     *          v_senator VARCHAR2(100) := 'THURMOND, JESSE';
     *       BEGIN
     *          IF total_contributions (v_senator, 'TOBACCO') > 25000
     *          THEN
     *             <<alochol_dependency>>
     *             DECLARE
     *                v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES';
     *             BEGIN
     *                ...
     */

    Assert (isType (token, TOKEN_BLOCK_LABEL_BEGIN));
    readToken (token);
    if (isType (token, TOKEN_IDENTIFIER))
    {
        makeSqlTag (token, SQLTAG_BLOCK_LABEL);
        readToken (token);          /* read end of label */
    }
}

static void parseStatements (tokenInfo *const token)
{
    boolean isAnsi   = TRUE;
    boolean stmtTerm = FALSE;
    do
    {
        if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
            parseLabel (token);
        else
        {
            switch (token->keyword)
            {
                case KEYWORD_exception:
                    /*
                     * EXCEPTION
                     *     <exception handler>;
                     *
                     * Where an exception handler could be:
                     *     BEGIN
                     *        WHEN OTHERS THEN
                     *            x := x + 3;
                     *     END;
                     * In this case we need to skip this keyword and
                     * move on to the next token without reading until
                     * TOKEN_SEMICOLON;
                     */
                    readToken (token);
                    continue;

                case KEYWORD_when:
                    /*
                     * WHEN statements can be used in exception clauses
                     * and CASE statements.  The CASE statement should skip
                     * these given below we skip over to an END statement.
                     * But for an exception clause, we can have:
                     *       EXCEPTION
                     *           WHEN OTHERS THEN
                     *           BEGIN
                     *                  x := x + 3;
                     *           END;
                     * If we skip to the TOKEN_SEMICOLON, we miss the begin
                     * of a nested BEGIN END block.  So read the next token
                     * after the THEN and restart the LOOP.
                     */
                    while (! isKeyword (token, KEYWORD_then))
                        readToken (token);
                    readToken (token);
                    continue;

                case KEYWORD_if:
                    /*
                     * We do not want to look for a ; since for an empty
                     * IF block, it would skip over the END.
                     *    IF...THEN
                     *    END IF;
                     *
                     *    or non-ANSI
                     *    IF ...
                     *    BEGIN
                     *    END
                     */
                    while ( ! isKeyword (token, KEYWORD_then)  &&
                            ! isKeyword (token, KEYWORD_begin) )
                    {
                        readToken (token);
                    }

                    if( isKeyword (token, KEYWORD_begin ) )
                    {
                        isAnsi = FALSE;
                        parseBlock(token, FALSE);

                        /*
                         * Handle the non-Ansi IF blocks.
                         * parseBlock consumes the END, so if the next
                         * token in a command terminator (like GO)
                         * we know we are done with this statement.
                         */
                        if ( isCmdTerm (token) )
                            stmtTerm = TRUE;
                    }
                    else
                    {
                        readToken (token);
                        parseStatements (token);
                        /*
                         * parseStatements returns when it finds an END, an IF
                         * should follow the END for ANSI anyway.
                         *    IF...THEN
                         *    END IF;
                         */
                        if( isKeyword (token, KEYWORD_end ) )
                            readToken (token);

                        if( ! isKeyword (token, KEYWORD_if ) )
                        {
                            /*
                             * Well we need to do something here.
                             * There are lots of different END statements
                             * END;
                             * END CASE;
                             * ENDIF;
                             * ENDCASE;
                             */
                        }
                    }
                    break;

                case KEYWORD_loop:
                case KEYWORD_case:
                case KEYWORD_for:
                    /*
                     *    LOOP...
                     *    END LOOP;
                     *
                     *    CASE
                     *    WHEN '1' THEN
                     *    END CASE;
                     *
                     *    FOR loop_name AS cursor_name CURSOR FOR ...
                     *    END FOR;
                     */
                    readToken (token);
                    parseStatements (token);

                    if( isKeyword (token, KEYWORD_end ) )
                        readToken (token);

                    break;

                case KEYWORD_create:
                    readToken (token);
                    parseKeywords(token);
                    break;

                case KEYWORD_declare:
                case KEYWORD_begin:
                    parseBlock (token, TRUE);
                    break;

                case KEYWORD_end:
                    break;

                default:
                    readToken (token);
                    break;
            }
            /*
             * Not all statements must end in a semi-colon
             *       begin
             *           if current publisher <> 'publish' then
             *             signal UE_FailStatement
             *           end if
             *       end;
             * The last statement prior to an end ("signal" above) does
             * not need a semi-colon, nor does the end if, since it is
             * also the last statement prior to the end of the block.
             *
             * So we must read to the first semi-colon or an END block
             */
            while ( ! stmtTerm &&
                    ! (   isKeyword (token, KEYWORD_end) ||
                         (isCmdTerm(token))              )
                    )
            {
                readToken (token);

                if (isType (token, TOKEN_OPEN_PAREN)  ||
                    isType (token, TOKEN_OPEN_CURLY)  ||
                    isType (token, TOKEN_OPEN_SQUARE)  )
                        skipToMatched (token);

            }
        }
        /*
         * We assumed earlier all statements ended with a command terminator.
         * See comment above, now, only read if the current token
         * is not a command terminator.
         */
        if ( isCmdTerm(token) )
        {
            readToken (token);
        }
    } while (! isKeyword (token, KEYWORD_end) && ! stmtTerm );
}

static void parseBlock (tokenInfo *const token, const boolean local)
{
    if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
    {
        parseLabel (token);
        readToken (token);
    }
    if (! isKeyword (token, KEYWORD_begin))
    {
        readToken (token);
        /*
         * These are Oracle style declares which generally come
         * between an IS/AS and BEGIN block.
         */
        parseDeclare (token, local);
    }
    if (isKeyword (token, KEYWORD_begin))
    {
        readToken (token);
        /*
         * Check for ANSI declarations which always follow
         * a BEGIN statement.  This routine will not advance
         * the token if none are found.
         */
        parseDeclareANSI (token, local);
        token->begin_end_nest_lvl++;
        while (! isKeyword (token, KEYWORD_end))
        {
            parseStatements (token);
        }
        token->begin_end_nest_lvl--;

        /*
         * Read the next token (we will assume
         * it is the command delimiter)
         */
        readToken (token);

        /*
         * Check if the END block is terminated
         */
        if ( !isCmdTerm (token)    )
        {
            /*
             * Not sure what to do here at the moment.
             * I think the routine that calls parseBlock
             * must expect the next token has already
             * been read since it is possible this
             * token is not a command delimiter.
             */
            /* findCmdTerm (token, FALSE); */
        }
    }
}

static void parsePackage (tokenInfo *const token)
{
    /*
     * Packages can be specified in a number of ways:
     *        CREATE OR REPLACE PACKAGE pkg_name AS
     * or
     *        CREATE OR REPLACE PACKAGE owner.pkg_name AS
     * or by specifying a package body
     *       CREATE OR REPLACE PACKAGE BODY pkg_name AS
     *       CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS
     */
    tokenInfo *const name = newToken ();
    readToken (name);
    if (isKeyword (name, KEYWORD_body))
    {
        /*
         * Ignore the BODY tag since we will process
         * the body or prototypes in the same manner
         */
        readToken (name);
    }
    /* Check for owner.pkg_name */
    while (! isKeyword (token, KEYWORD_is))
    {
        readToken (token);
        if ( isType(token, TOKEN_PERIOD) )
        {
            readToken (name);
        }
    }
    if (isKeyword (token, KEYWORD_is))
    {
        if (isType (name, TOKEN_IDENTIFIER) ||
                isType (name, TOKEN_STRING))
            makeSqlTag (name, SQLTAG_PACKAGE);
        parseBlock (token, FALSE);
    }
    findCmdTerm (token, FALSE);
    deleteToken (name);
}

static void parseTable (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats:
     *       create table t1 (c1 int);
     *       create global tempoary table t2 (c1 int);
     *       create table "t3" (c1 int);
     *       create table bob.t4 (c1 int);
     *       create table bob."t5" (c1 int);
     *       create table "bob"."t6" (c1 int);
     *       create table bob."t7" (c1 int);
     * Proxy tables use this format:
     *       create existing table bob."t7" AT '...';
     * SQL Server and Sybase formats
     *     create table OnlyTable (
     *     create table dbo.HasOwner (
     *     create table [dbo].[HasOwnerSquare] (
     *     create table master.dbo.HasDb (
     *     create table master..HasDbNoOwner (
     *     create table [master].dbo.[HasDbAndOwnerSquare] (
     *     create table [master]..[HasDbNoOwnerSquare] (
     */

    /* This could be a database, owner or table name */
    readToken (name);
    if (isType (name, TOKEN_OPEN_SQUARE))
    {
        readToken (name);
        /* Read close square */
        readToken (token);
    }
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        /*
         * This could be a owner or table name.
         * But this is also a special case since the table can be
         * referenced with a blank owner:
         *     dbname..tablename
         */
        readToken (name);
        if (isType (name, TOKEN_OPEN_SQUARE))
        {
            readToken (name);
            /* Read close square */
            readToken (token);
        }
        /* Check if a blank name was provided */
        if (isType (name, TOKEN_PERIOD))
        {
            readToken (name);
            if (isType (name, TOKEN_OPEN_SQUARE))
            {
                readToken (name);
                /* Read close square */
                readToken (token);
            }
        }
        readToken (token);
        if (isType (token, TOKEN_PERIOD))
        {
            /* This can only be the table name */
            readToken (name);
            if (isType (name, TOKEN_OPEN_SQUARE))
            {
                readToken (name);
                /* Read close square */
                readToken (token);
            }
            readToken (token);
        }
    }
    if (isType (token, TOKEN_OPEN_PAREN))
    {
        if (isType (name, TOKEN_IDENTIFIER) ||
                isType (name, TOKEN_STRING))
        {
            makeSqlTag (name, SQLTAG_TABLE);
            vStringCopy(token->scope, name->string);
            parseRecord (token);
            vStringClear (token->scope);
        }
    }
    else if (isKeyword (token, KEYWORD_at))
    {
        if (isType (name, TOKEN_IDENTIFIER))
        {
            makeSqlTag (name, SQLTAG_TABLE);
        }
    }
    findCmdTerm (token, FALSE);
    deleteToken (name);
}

static void parseIndex (tokenInfo *const token)
{
    tokenInfo *const name  = newToken ();
    tokenInfo *const owner = newToken ();

    /*
     * This deals with these formats
     *       create index i1 on t1(c1) create index "i2" on t1(c1)
     *       create virtual unique clustered index "i3" on t1(c1)
     *       create unique clustered index "i4" on t1(c1)
     *       create clustered index "i5" on t1(c1)
     *       create bitmap index "i6" on t1(c1)
     */

    readToken (name);
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        readToken (name);
        readToken (token);
    }
    if ( isKeyword (token, KEYWORD_on) &&
            (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING) ) )
    {
        readToken (owner);
        readToken (token);
        if (isType (token, TOKEN_PERIOD))
        {
            readToken (owner);
            readToken (token);
        }
        addToScope(name, owner->string);
        makeSqlTag (name, SQLTAG_INDEX);
    }
    findCmdTerm (token, FALSE);
    deleteToken (name);
    deleteToken (owner);
}

static void parseEvent (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       create event e1 handler begin end;
     *       create event "e2" handler begin end;
     *       create event dba."e3" handler begin end;
     *       create event "dba"."e4" handler begin end;
     */

    readToken (name);
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        readToken (name);
    }
    while (! (isKeyword (token, KEYWORD_handler) ||
                (isType (token, TOKEN_SEMICOLON)))      )
    {
        readToken (token);
    }

    if ( isKeyword (token, KEYWORD_handler) ||
            isType (token, TOKEN_SEMICOLON)   )
    {
        makeSqlTag (name, SQLTAG_EVENT);
    }

    if (isKeyword (token, KEYWORD_handler))
    {
        readToken (token);
        if ( isKeyword (token, KEYWORD_begin) )
        {
            parseBlock (token, TRUE);
        }
        findCmdTerm (token, TRUE);
    }
    deleteToken (name);
}

static void parseTrigger (tokenInfo *const token)
{
    tokenInfo *const name  = newToken ();
    tokenInfo *const table = newToken ();

    /*
     * This deals with these formats
     *       create or replace trigger tr1 begin end;
     *       create trigger "tr2" begin end;
     *       drop trigger "droptr1";
     *       create trigger "tr3" CALL sp_something();
     *       create trigger "owner"."tr4" begin end;
     *       create trigger "tr5" not valid;
     *       create trigger "tr6" begin end;
     */

    readToken (name);
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        readToken (name);
        readToken (token);
    }

    while ( !isKeyword (token, KEYWORD_on) &&
            !isCmdTerm (token)        )
    {
        readToken (token);
    }

    /*if (! isType (token, TOKEN_SEMICOLON) ) */
    if (! isCmdTerm (token) )
    {
        readToken (table);
        readToken (token);
        if (isType (token, TOKEN_PERIOD))
        {
            readToken (table);
            readToken (token);
        }

        while (! (isKeyword (token, KEYWORD_begin) ||
                    (isKeyword (token, KEYWORD_call)) ||
                    (    isCmdTerm (token)))    )
        {
            if ( isKeyword (token, KEYWORD_declare) )
            {
                addToScope(token, name->string);
                parseDeclare(token, TRUE);
                vStringClear(token->scope);
            }
            else
                readToken (token);
        }

        if ( isKeyword (token, KEYWORD_begin) ||
                isKeyword (token, KEYWORD_call)   )
        {
            addToScope(name, table->string);
            makeSqlTag (name, SQLTAG_TRIGGER);
            addToScope(token, table->string);
            if ( isKeyword (token, KEYWORD_begin) )
            {
                parseBlock (token, TRUE);
            }
            vStringClear(token->scope);
        }
    }

    findCmdTerm (token, TRUE);
    deleteToken (name);
    deleteToken (table);
}

static void parsePublication (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       create or replace publication pu1 ()
     *       create publication "pu2" ()
     *       create publication dba."pu3" ()
     *       create publication "dba"."pu4" ()
     */

    readToken (name);
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        readToken (name);
        readToken (token);
    }
    if (isType (token, TOKEN_OPEN_PAREN))
    {
        if (isType (name, TOKEN_IDENTIFIER) ||
                isType (name, TOKEN_STRING))
        {
            makeSqlTag (name, SQLTAG_PUBLICATION);
        }
    }
    findCmdTerm (token, FALSE);
    deleteToken (name);
}

static void parseService (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       CREATE SERVICE s1 TYPE 'HTML'
     *           AUTHORIZATION OFF USER DBA AS
     *           SELECT *
     *             FROM SYS.SYSTABLE;
     *       CREATE SERVICE "s2" TYPE 'HTML'
     *           AUTHORIZATION OFF USER DBA AS
     *           CALL sp_Something();
     */

    readToken (name);
    readToken (token);
    if (isKeyword (token, KEYWORD_type))
    {
        if (isType (name, TOKEN_IDENTIFIER) ||
                isType (name, TOKEN_STRING))
        {
            makeSqlTag (name, SQLTAG_SERVICE);
        }
    }
    findCmdTerm (token, FALSE);
    deleteToken (name);
}

static void parseDomain (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       CREATE DOMAIN|DATATYPE [AS] your_name ...;
     */

    readToken (name);
    if (isKeyword (name, KEYWORD_is))
    {
        readToken (name);
    }
    readToken (token);
    if (isType (name, TOKEN_IDENTIFIER) ||
            isType (name, TOKEN_STRING))
    {
        makeSqlTag (name, SQLTAG_DOMAIN);
    }
    findCmdTerm (token, FALSE);
    deleteToken (name);
}

static void parseDrop (tokenInfo *const token)
{
    /*
     * This deals with these formats
     *       DROP TABLE|PROCEDURE|DOMAIN|DATATYPE name;
     *
     * Just simply skip over these statements.
     * They are often confused with PROCEDURE prototypes
     * since the syntax is similar, this effectively deals with
     * the issue for all types.
     */

    findCmdTerm (token, FALSE);
}

static void parseVariable (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       create variable varname1 integer;
     *       create variable @varname2 integer;
     *       create variable "varname3" integer;
     *       drop   variable @varname3;
     */

    readToken (name);
    readToken (token);
    if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
            && !isType (token, TOKEN_SEMICOLON) )
    {
        makeSqlTag (name, SQLTAG_VARIABLE);
    }
    findCmdTerm (token, TRUE);

    deleteToken (name);
}

static void parseSynonym (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       create variable varname1 integer;
     *       create variable @varname2 integer;
     *       create variable "varname3" integer;
     *       drop   variable @varname3;
     */

    readToken (name);
    readToken (token);
    if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
            && isKeyword (token, KEYWORD_for) )
    {
        makeSqlTag (name, SQLTAG_SYNONYM);
    }
    findCmdTerm (token, TRUE);

    deleteToken (name);
}

static void parseView (tokenInfo *const token)
{
    tokenInfo *const name = newToken ();

    /*
     * This deals with these formats
     *       create variable varname1 integer;
     *       create variable @varname2 integer;
     *       create variable "varname3" integer;
     *       drop   variable @varname3;
     */

    readToken (name);
    readToken (token);
    if (isType (token, TOKEN_PERIOD))
    {
        readToken (name);
        readToken (token);
    }
    if ( isType (token, TOKEN_OPEN_PAREN) )
    {
        skipArgumentList(token);

    }

    while (!(isKeyword (token, KEYWORD_is) ||
                isType (token, TOKEN_SEMICOLON)
            ))
    {
        readToken (token);
    }

    if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
            && isKeyword (token, KEYWORD_is) )
    {
        makeSqlTag (name, SQLTAG_VIEW);
    }

    findCmdTerm (token, TRUE);

    deleteToken (name);
}

static void parseMLTable (tokenInfo *const token)
{
    tokenInfo *const version = newToken ();
    tokenInfo *const table     = newToken ();
    tokenInfo *const event     = newToken ();

    /*
     * This deals with these formats
     *      call dbo.ml_add_table_script( 'version', 'table_name', 'event',
     *           'some SQL statement'
     *           );
     */

    readToken (token);
    if ( isType (token, TOKEN_OPEN_PAREN) )
    {
        readToken (version);
        readToken (token);
        while (!(isType (token, TOKEN_COMMA) ||
                    isType (token, TOKEN_CLOSE_PAREN)
                ))
        {
            readToken (token);
        }

        if (isType (token, TOKEN_COMMA))
        {
            readToken (table);
            readToken (token);
            while (!(isType (token, TOKEN_COMMA) ||
                        isType (token, TOKEN_CLOSE_PAREN)
                    ))
            {
                readToken (token);
            }

            if (isType (token, TOKEN_COMMA))
            {
                readToken (event);

                if (isType (version, TOKEN_STRING) &&
                        isType (table, TOKEN_STRING) &&
                        isType (event, TOKEN_STRING) )
                {
                    addToScope(version, table->string);
                    addToScope(version, event->string);
                    makeSqlTag (version, SQLTAG_MLTABLE);
                }
            }
            if( !isType (token, TOKEN_CLOSE_PAREN) )
                findToken (token, TOKEN_CLOSE_PAREN);
        }
    }

    findCmdTerm (token, TRUE);

    deleteToken (version);
    deleteToken (table);
    deleteToken (event);
}

static void parseMLConn (tokenInfo *const token)
{
    tokenInfo *const version = newToken ();
    tokenInfo *const event     = newToken ();

    /*
     * This deals with these formats
     *      call ml_add_connection_script( 'version', 'event',
     *           'some SQL statement'
     *           );
     */

    readToken (token);
    if ( isType (token, TOKEN_OPEN_PAREN) )
    {
        readToken (version);
        readToken (token);
        while (!(isType (token, TOKEN_COMMA) ||
                    isType (token, TOKEN_CLOSE_PAREN)
                ))
        {
            readToken (token);
        }

        if (isType (token, TOKEN_COMMA))
        {
            readToken (event);

            if (isType (version, TOKEN_STRING) &&
                    isType (event, TOKEN_STRING) )
            {
                addToScope(version, event->string);
                makeSqlTag (version, SQLTAG_MLCONN);
            }
        }
        if( !isType (token, TOKEN_CLOSE_PAREN) )
            findToken (token, TOKEN_CLOSE_PAREN);

    }

    findCmdTerm (token, TRUE);

    deleteToken (version);
    deleteToken (event);
}

static void parseComment (tokenInfo *const token)
{
    /*
     * This deals with this statement:
     *       COMMENT TO PRESERVE FORMAT ON PROCEDURE "DBA"."test" IS
     *       {create PROCEDURE DBA."test"()
     *       BEGIN
     *        signal dave;
     *       END
     *       }
     *       ;
     * The comment can contain anything between the CURLY
     * braces
     *       COMMENT ON USER "admin" IS
     *            'Administration Group'
     *            ;
     * Or it could be a simple string with no curly braces
     */
    while (! isKeyword (token, KEYWORD_is))
    {
        readToken (token);
    }
    readToken (token);
    if ( isType(token, TOKEN_OPEN_CURLY) )
    {
        findToken (token, TOKEN_CLOSE_CURLY);
    }

    findCmdTerm (token, TRUE);
}


static void parseKeywords (tokenInfo *const token)
{
        switch (token->keyword)
        {
            case KEYWORD_begin:            parseBlock (token, FALSE); break;
            case KEYWORD_comment:        parseComment (token); break;
            case KEYWORD_cursor:        parseSimple (token, SQLTAG_CURSOR); break;
            case KEYWORD_datatype:        parseDomain (token); break;
            case KEYWORD_declare:        parseBlock (token, FALSE); break;
            case KEYWORD_domain:        parseDomain (token); break;
            case KEYWORD_drop:            parseDrop (token); break;
            case KEYWORD_event:            parseEvent (token); break;
            case KEYWORD_function:        parseSubProgram (token); break;
            case KEYWORD_if:            parseStatements (token); break;
            case KEYWORD_index:            parseIndex (token); break;
            case KEYWORD_ml_table:        parseMLTable (token); break;
            case KEYWORD_ml_table_lang: parseMLTable (token); break;
            case KEYWORD_ml_table_dnet: parseMLTable (token); break;
            case KEYWORD_ml_table_java: parseMLTable (token); break;
            case KEYWORD_ml_table_chk:  parseMLTable (token); break;
            case KEYWORD_ml_conn:        parseMLConn (token); break;
            case KEYWORD_ml_conn_lang:    parseMLConn (token); break;
            case KEYWORD_ml_conn_dnet:    parseMLConn (token); break;
            case KEYWORD_ml_conn_java:    parseMLConn (token); break;
            case KEYWORD_ml_conn_chk:    parseMLConn (token); break;
            case KEYWORD_package:        parsePackage (token); break;
            case KEYWORD_procedure:        parseSubProgram (token); break;
            case KEYWORD_publication:    parsePublication (token); break;
            case KEYWORD_service:        parseService (token); break;
            case KEYWORD_subtype:        parseSimple (token, SQLTAG_SUBTYPE); break;
            case KEYWORD_synonym:        parseSynonym (token); break;
            case KEYWORD_table:            parseTable (token); break;
            case KEYWORD_trigger:        parseTrigger (token); break;
            case KEYWORD_type:            parseType (token); break;
            case KEYWORD_variable:        parseVariable (token); break;
            case KEYWORD_view:            parseView (token); break;
            default:                    break;
        }
}

static void parseSqlFile (tokenInfo *const token)
{
    do
    {
        readToken (token);

        if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
            parseLabel (token);
        else
            parseKeywords (token);
    } while (! isKeyword (token, KEYWORD_end));
}

static void initialize (const langType language)
{
    Assert (sizeof (SqlKinds) / sizeof (SqlKinds [0]) == SQLTAG_COUNT);
    Lang_sql = language;
    buildSqlKeywordHash ();
}

static void findSqlTags (void)
{
    tokenInfo *const token = newToken ();
    exception_t exception = (exception_t) (setjmp (Exception));

    while (exception == ExceptionNone)
        parseSqlFile (token);

    deleteToken (token);
}

extern parserDefinition* SqlParser (void)
{
    static const char *const extensions [] = { "sql", NULL };
    parserDefinition* def = parserNew ("SQL");
    def->kinds        = SqlKinds;
    def->kindCount    = KIND_COUNT (SqlKinds);
    def->extensions = extensions;
    def->parser        = findSqlTags;
    def->initialize = initialize;
    return def;
}

/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */