mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-28 03:23:03 +01:00
1d9e007baa
-Use C++ struct declaration style.
1253 lines
26 KiB
C++
1253 lines
26 KiB
C++
/*
|
|
* PROGRAM: JRD Command Oriented Query Language
|
|
* MODULE: lex.cpp
|
|
* DESCRIPTION: Lexical analyser
|
|
*
|
|
* 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): ______________________________________.
|
|
*
|
|
* 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
|
|
*
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include "../qli/dtr.h"
|
|
#include "../qli/parse.h"
|
|
#include "../jrd/ibase.h"
|
|
#include "../qli/all_proto.h"
|
|
#include "../qli/err_proto.h"
|
|
#include "../qli/hsh_proto.h"
|
|
#include "../qli/lex_proto.h"
|
|
#include "../qli/proc_proto.h"
|
|
#include "../jrd/gds_proto.h"
|
|
#include "../jrd/utl_proto.h"
|
|
#include "../jrd/gdsassert.h"
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_CTYPES_H
|
|
#include <ctypes.h>
|
|
#endif
|
|
|
|
#ifdef VMS
|
|
#include <descrip.h>
|
|
const SLONG LIB$_INPSTRTRU = 0x15821c;
|
|
#endif
|
|
|
|
#if (defined WIN_NT)
|
|
#include <io.h> // isatty
|
|
#endif
|
|
|
|
#ifdef SMALL_FILE_NAMES
|
|
static const char* SCRATCH = "fb_q";
|
|
#else
|
|
static const char* SCRATCH = "fb_query_";
|
|
#endif
|
|
|
|
const char* FOPEN_INPUT_TYPE = "r";
|
|
|
|
static bool get_line(FILE *, TEXT *, USHORT);
|
|
static int nextchar(const bool);
|
|
static void next_line(const bool);
|
|
static void retchar();
|
|
static bool scan_number(SSHORT, TEXT **);
|
|
static int skip_white(void);
|
|
|
|
static qli_lls* QLI_statements;
|
|
static int QLI_position;
|
|
static FILE *input_file = NULL, *trace_file = NULL;
|
|
static char trace_file_name[MAXPATHLEN];
|
|
static SLONG trans_limit;
|
|
|
|
const SLONG TRANS_LIMIT = 10;
|
|
|
|
const char CHR_ident = char(1);
|
|
const char CHR_letter = char(2);
|
|
const char CHR_digit = char(4);
|
|
const char CHR_quote = char(8);
|
|
const char CHR_white = char(16);
|
|
const char CHR_eol = char(32);
|
|
|
|
const char CHR_IDENT = CHR_ident;
|
|
const char CHR_LETTER = CHR_letter + CHR_ident;
|
|
const char CHR_DIGIT = CHR_digit + CHR_ident;
|
|
const char CHR_QUOTE = CHR_quote;
|
|
const char CHR_WHITE = CHR_white;
|
|
const char CHR_EOL = CHR_eol;
|
|
|
|
static const char* const eol_string = "end of line";
|
|
static const char* const eof_string = "*end_of_file*";
|
|
|
|
static const char classes[256] =
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, CHR_WHITE, CHR_EOL, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
CHR_WHITE, 0, CHR_QUOTE, CHR_IDENT, CHR_LETTER, 0, 0, CHR_QUOTE,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
CHR_DIGIT, CHR_DIGIT, CHR_DIGIT, CHR_DIGIT, CHR_DIGIT, CHR_DIGIT,
|
|
CHR_DIGIT, CHR_DIGIT,
|
|
CHR_DIGIT, CHR_DIGIT, 0, 0, 0, 0, 0, 0,
|
|
0, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER, CHR_LETTER, 0, 0, 0, 0, CHR_IDENT,
|
|
0, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER,
|
|
CHR_LETTER, CHR_LETTER, CHR_LETTER, 0
|
|
};
|
|
|
|
|
|
|
|
bool LEX_active_procedure(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ a c t i v e _ p r o c e d u r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return true if we're running out of a
|
|
* procedure and false otherwise. Somebody
|
|
* somewhere may care.
|
|
*
|
|
**************************************/
|
|
|
|
return (QLI_line->line_type == line_blob);
|
|
}
|
|
|
|
|
|
void LEX_edit( SLONG start, SLONG stop)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ e d i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Dump the last full statement into a scratch file, then
|
|
* push the scratch file on the input stack.
|
|
*
|
|
**************************************/
|
|
TEXT filename[MAXPATHLEN];
|
|
|
|
FILE* scratch = (FILE*) gds__temp_file(TRUE, SCRATCH, filename);
|
|
if (scratch == (FILE *) - 1)
|
|
IBERROR(61); // Msg 61 couldn't open scratch file
|
|
#ifdef WIN_NT
|
|
stop--;
|
|
#endif
|
|
|
|
if (fseek(trace_file, start, 0)) {
|
|
fseek(trace_file, (SLONG) 0, 2);
|
|
IBERROR(59); // Msg 59 fseek failed
|
|
}
|
|
|
|
while (++start <= stop) {
|
|
const SSHORT c = getc(trace_file);
|
|
if (c == EOF)
|
|
break;
|
|
putc(c, scratch);
|
|
}
|
|
|
|
fclose(scratch);
|
|
|
|
if (gds__edit(filename, TRUE))
|
|
LEX_push_file(filename, true);
|
|
|
|
unlink(filename);
|
|
|
|
fseek(trace_file, (SLONG) 0, 2);
|
|
}
|
|
|
|
|
|
qli_tok* LEX_edit_string(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ e d i t _ s t r i n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Parse the next token as an edit_string.
|
|
*
|
|
**************************************/
|
|
SSHORT c;
|
|
qli_tok* token = QLI_token;
|
|
|
|
do {
|
|
c = skip_white();
|
|
} while (c == '\n');
|
|
TEXT* p = token->tok_string;
|
|
*p = c;
|
|
|
|
if (!QLI_line) {
|
|
token->tok_symbol = NULL;
|
|
token->tok_keyword = KW_none;
|
|
return NULL;
|
|
}
|
|
|
|
while (!(classes[c] & (CHR_white | CHR_eol))) {
|
|
*p++ = c;
|
|
if (classes[c] & CHR_quote)
|
|
for (;;) {
|
|
const SSHORT d = nextchar(false);
|
|
if (d == '\n') {
|
|
retchar();
|
|
break;
|
|
}
|
|
*p++ = d;
|
|
if (d == c)
|
|
break;
|
|
}
|
|
c = nextchar(true);
|
|
}
|
|
|
|
retchar();
|
|
|
|
if (p[-1] == ',') {
|
|
retchar();
|
|
--p;
|
|
}
|
|
|
|
token->tok_length = p - token->tok_string;
|
|
*p = 0;
|
|
token->tok_symbol = NULL;
|
|
token->tok_keyword = KW_none;
|
|
|
|
if (sw_trace)
|
|
puts(token->tok_string);
|
|
|
|
return token;
|
|
}
|
|
|
|
|
|
qli_tok* LEX_filename(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ f i l e n a m e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Parse the next token as a filename.
|
|
*
|
|
**************************************/
|
|
SSHORT c;
|
|
|
|
qli_tok* token = QLI_token;
|
|
|
|
do {
|
|
c = skip_white();
|
|
} while (c == '\n');
|
|
TEXT* p = token->tok_string;
|
|
*p++ = c;
|
|
|
|
// If there isn't a line, we're all done
|
|
|
|
if (!QLI_line) {
|
|
token->tok_symbol = NULL;
|
|
token->tok_keyword = KW_none;
|
|
return NULL;
|
|
}
|
|
|
|
// notice if this looks like a quoted file name
|
|
|
|
SSHORT save = 0;
|
|
if (classes[c] & CHR_quote) {
|
|
token->tok_type = tok_quoted;
|
|
save = c;
|
|
}
|
|
|
|
// Look for white space or end of line, allowing embedded quoted strings.
|
|
|
|
for (;;) {
|
|
c = nextchar(true);
|
|
char char_class = classes[c];
|
|
if (c == '"' && c != save) {
|
|
*p++ = c;
|
|
for (;;) {
|
|
c = nextchar(true);
|
|
char_class = classes[c];
|
|
if ((char_class & CHR_eol) || c == '"')
|
|
break;
|
|
*p++ = c;
|
|
}
|
|
}
|
|
|
|
if (char_class & (CHR_white | CHR_eol))
|
|
break;
|
|
*p++ = c;
|
|
}
|
|
|
|
retchar();
|
|
|
|
// Drop trailing semi-colon to avoid confusion
|
|
|
|
if (p[-1] == ';') {
|
|
retchar();
|
|
--p;
|
|
}
|
|
|
|
// complain on unterminated quoted string
|
|
|
|
if ((token->tok_type == tok_quoted) && (p[-1] != save))
|
|
IBERROR(60); // Msg 60 unterminated quoted string
|
|
|
|
token->tok_length = p - token->tok_string;
|
|
*p = 0;
|
|
token->tok_symbol = NULL;
|
|
token->tok_keyword = KW_none;
|
|
|
|
if (sw_trace)
|
|
puts(token->tok_string);
|
|
|
|
return token;
|
|
}
|
|
|
|
|
|
void LEX_fini(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ f i n i
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Shut down LEX in a more or less clean way.
|
|
*
|
|
**************************************/
|
|
|
|
if (trace_file && (trace_file != (FILE *) - 1)) {
|
|
fclose(trace_file);
|
|
unlink(trace_file_name);
|
|
}
|
|
}
|
|
|
|
|
|
void LEX_flush(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ f l u s h
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Flush the input stream after an error.
|
|
*
|
|
**************************************/
|
|
|
|
trans_limit = 0;
|
|
|
|
if (!QLI_line)
|
|
return;
|
|
|
|
// Pop off line sources until we're down to the last one.
|
|
|
|
while (QLI_line->line_next)
|
|
LEX_pop_line();
|
|
|
|
// Look for a semi-colon
|
|
|
|
if (QLI_semi)
|
|
while (QLI_line && QLI_token->tok_keyword != KW_SEMI)
|
|
LEX_token();
|
|
else
|
|
while (QLI_line && QLI_token->tok_type != tok_eol)
|
|
LEX_token();
|
|
}
|
|
|
|
|
|
#if defined(UNIX) || defined(WIN_NT)
|
|
bool LEX_get_line(TEXT * prompt,
|
|
TEXT * buffer,
|
|
int size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ g e t _ l i n e ( U N I X )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Give a prompt and read a line. If the line is terminated by
|
|
* an EOL, return true. If the buffer is exhausted and non-blanks
|
|
* would be discarded, return an error. If EOF is detected,
|
|
* return false. Regardless, a null terminated string is returned.
|
|
*
|
|
**************************************/
|
|
// UNIX flavor
|
|
|
|
if (prompt)
|
|
printf(prompt);
|
|
|
|
errno = 0;
|
|
TEXT* p = buffer;
|
|
|
|
bool overflow_flag = false;
|
|
SSHORT c;
|
|
|
|
while (true) {
|
|
c = getc(input_file);
|
|
if (c == EOF) {
|
|
if (SYSCALL_INTERRUPTED(errno) && !QLI_abort) {
|
|
errno = 0;
|
|
continue;
|
|
}
|
|
|
|
// The check for this actually being a terminal that is at
|
|
// end of file is to prevent looping through a redirected
|
|
// stdin (e.g., a script file).
|
|
|
|
if (prompt && isatty(fileno(stdin))) {
|
|
rewind(stdin);
|
|
putchar('\n');
|
|
}
|
|
if (QLI_abort)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
if (--size > 0)
|
|
*p++ = c;
|
|
else if (c != ' ' && c != '\n')
|
|
overflow_flag = true;
|
|
if (c == '\n')
|
|
break;
|
|
}
|
|
|
|
*p = 0;
|
|
if (c == EOF)
|
|
return false;
|
|
|
|
if (overflow_flag) {
|
|
buffer[0] = 0;
|
|
IBERROR(476); // Msg 476 input line too long
|
|
}
|
|
|
|
if (sw_verify)
|
|
fputs(buffer, stdout);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef VMS
|
|
bool LEX_get_line(TEXT * prompt,
|
|
TEXT * buffer,
|
|
int size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ g e t _ l i n e ( V M S )
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Give a prompt and read a line. If the line is terminated by
|
|
* an EOL, return true. If the buffer is exhausted and non-blanks
|
|
* would be discarded, return an error. If EOF is detected,
|
|
* return false. Regardless, a null terminated string is returned.
|
|
*
|
|
**************************************/
|
|
struct dsc$descriptor_s line_desc, prompt_desc;
|
|
SLONG status;
|
|
SSHORT length;
|
|
|
|
// We're going to add a null to the end, so don't read too much
|
|
|
|
--size;
|
|
|
|
line_desc.dsc$b_class = DSC$K_CLASS_S;
|
|
line_desc.dsc$b_dtype = DSC$K_DTYPE_T;
|
|
line_desc.dsc$w_length = size;
|
|
line_desc.dsc$a_pointer = buffer;
|
|
|
|
if (prompt) {
|
|
prompt_desc.dsc$b_class = DSC$K_CLASS_S;
|
|
prompt_desc.dsc$b_dtype = DSC$K_DTYPE_T;
|
|
prompt_desc.dsc$w_length = strlen(prompt);
|
|
prompt_desc.dsc$a_pointer = prompt;
|
|
status = lib$get_input(&line_desc, &prompt_desc, &length);
|
|
}
|
|
else
|
|
status = lib$get_input(&line_desc, NULL, &length);
|
|
|
|
SCHAR* p = buffer + length;
|
|
|
|
if (!(status & 1)) {
|
|
if (status != LIB$_INPSTRTRU)
|
|
return false;
|
|
buffer[0] = 0;
|
|
IBERROR(476); // Msg 476 input line too long
|
|
}
|
|
else if (length < size)
|
|
*p++ = '\n';
|
|
|
|
*p = 0;
|
|
|
|
if (sw_verify) {
|
|
line_desc.dsc$w_length = length;
|
|
lib$put_output(&line_desc);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
void LEX_init(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ i n i t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Initialize for lexical scanning. While we're at it, open a
|
|
* scratch trace file to keep all input.
|
|
*
|
|
**************************************/
|
|
trace_file = (FILE*) gds__temp_file(TRUE, SCRATCH, trace_file_name);
|
|
if (trace_file == (FILE *) - 1)
|
|
IBERROR(61); // Msg 61 couldn't open scratch file
|
|
|
|
QLI_token = (qli_tok*) ALLOCPV(type_tok, MAXSYMLEN);
|
|
|
|
QLI_line = (qli_line*) ALLOCPV(type_line, 0);
|
|
QLI_line->line_size = sizeof(QLI_line->line_data);
|
|
QLI_line->line_ptr = QLI_line->line_data;
|
|
QLI_line->line_type = line_stdin;
|
|
QLI_line->line_source_file = stdin;
|
|
|
|
QLI_semi = false;
|
|
input_file = stdin;
|
|
HSH_init();
|
|
}
|
|
|
|
|
|
void LEX_mark_statement(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ m a r k _ s t a t e m e n t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Push a position in the trace file onto
|
|
* the statement stack.
|
|
*
|
|
**************************************/
|
|
qli_line* temp;
|
|
|
|
for (temp = QLI_line;
|
|
temp->line_next && QLI_statements;
|
|
temp = temp->line_next)
|
|
{
|
|
if (temp->line_next->line_position == (IPTR) QLI_statements->lls_object)
|
|
return;
|
|
}
|
|
|
|
qli_lls* statement = (qli_lls*) ALLOCP(type_lls);
|
|
statement->lls_object = (BLK)(IPTR) temp->line_position;
|
|
statement->lls_next = QLI_statements;
|
|
QLI_statements = statement;
|
|
}
|
|
|
|
|
|
void LEX_pop_line(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ p o p _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* We're done with the current line source. Close it out
|
|
* and release the line block.
|
|
*
|
|
**************************************/
|
|
qli_line* temp = QLI_line;
|
|
QLI_line = temp->line_next;
|
|
|
|
if (temp->line_type == line_blob)
|
|
PRO_close(temp->line_database, temp->line_source_blob);
|
|
else if (temp->line_type == line_file)
|
|
fclose(temp->line_source_file);
|
|
|
|
ALLQ_release((FRB) temp);
|
|
}
|
|
|
|
|
|
void LEX_procedure( DBB database, FB_API_HANDLE blob)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ p r o c e d u r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Push a blob-resident procedure onto the input line source
|
|
* stack;
|
|
*
|
|
**************************************/
|
|
qli_line* temp = (qli_line*) ALLOCPV(type_line, QLI_token->tok_length);
|
|
temp->line_source_blob = blob;
|
|
strncpy(temp->line_source_name, QLI_token->tok_string,
|
|
QLI_token->tok_length);
|
|
temp->line_type = line_blob;
|
|
temp->line_database = database;
|
|
temp->line_size = sizeof(temp->line_data);
|
|
temp->line_ptr = temp->line_data;
|
|
temp->line_position = QLI_position;
|
|
temp->line_next = QLI_line;
|
|
QLI_line = temp;
|
|
}
|
|
|
|
|
|
bool LEX_push_file(const TEXT* filename,
|
|
const bool error_flag)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ p u s h _ f i l e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Open a command file. If the open succeedes, push the input
|
|
* source onto the source stack. If the open fails, give an error
|
|
* if the error flag is set, otherwise return quietly.
|
|
*
|
|
**************************************/
|
|
FILE *file = fopen(filename, FOPEN_INPUT_TYPE);
|
|
if (!file) {
|
|
TEXT buffer[64];
|
|
sprintf(buffer, "%s.com", filename);
|
|
if (!(file = fopen(buffer, FOPEN_INPUT_TYPE))) {
|
|
if (error_flag)
|
|
ERRQ_msg_put(67, filename, NULL, NULL, NULL, NULL);
|
|
// Msg 67 can't open command file \"%s\"\n
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qli_line* line = (qli_line*) ALLOCPV(type_line, strlen(filename));
|
|
line->line_type = line_file;
|
|
line->line_source_file = file;
|
|
line->line_size = sizeof(line->line_data);
|
|
line->line_ptr = line->line_data;
|
|
*line->line_ptr = 0;
|
|
strcpy(line->line_source_name, filename);
|
|
line->line_next = QLI_line;
|
|
QLI_line = line;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LEX_push_string(const TEXT* const string)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ p u s h _ s t r i n g
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Push a simple string on as an input source.
|
|
*
|
|
**************************************/
|
|
qli_line* line = (qli_line*) ALLOCPV(type_line, 0);
|
|
line->line_type = line_string;
|
|
line->line_size = strlen(string);
|
|
line->line_ptr = line->line_data;
|
|
strcpy(line->line_data, string);
|
|
line->line_data[line->line_size] = '\n';
|
|
line->line_next = QLI_line;
|
|
QLI_line = line;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LEX_put_procedure(FB_API_HANDLE blob, SLONG start, SLONG stop)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ p u t _ p r o c e d u r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Write text from the scratch trace file into a blob.
|
|
*
|
|
**************************************/
|
|
ISC_STATUS_ARRAY status_vector;
|
|
|
|
if (fseek(trace_file, start, 0)) {
|
|
fseek(trace_file, (SLONG) 0, 2);
|
|
IBERROR(62); // Msg 62 fseek failed
|
|
}
|
|
|
|
int length = stop - start;
|
|
|
|
TEXT buffer[1024];
|
|
|
|
while (length) {
|
|
TEXT* p = buffer;
|
|
while (length) {
|
|
--length;
|
|
const SSHORT c = getc(trace_file);
|
|
*p++ = c;
|
|
if (c == '\n') {
|
|
#ifdef WIN_NT
|
|
// account for the extra line-feed on OS/2 and Windows NT
|
|
|
|
if (length)
|
|
--length;
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
const SSHORT l = p - buffer;
|
|
if (l)
|
|
if (isc_put_segment(status_vector, &blob, l, buffer))
|
|
ERRQ_bugcheck(58); // Msg 58 isc_put_segment failed
|
|
}
|
|
|
|
fseek(trace_file, (SLONG) 0, 2);
|
|
}
|
|
|
|
|
|
void LEX_real(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ r e a l
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* If the token is an end of line, get the next token.
|
|
*
|
|
**************************************/
|
|
|
|
while (QLI_token->tok_type == tok_eol)
|
|
LEX_token();
|
|
}
|
|
|
|
|
|
qli_lls* LEX_statement_list(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ s t a t e m e n t _ l i s t
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Somebody outside needs to know
|
|
* where the top of the statement list
|
|
* is.
|
|
*
|
|
**************************************/
|
|
|
|
return QLI_statements;
|
|
}
|
|
|
|
|
|
qli_tok* LEX_token(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* L E X _ t o k e n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Parse and return the next token.
|
|
*
|
|
**************************************/
|
|
qli_tok* token = QLI_token;
|
|
TEXT* p = token->tok_string;
|
|
|
|
// Get next significant byte. If it's the last EOL of a blob, throw it away
|
|
|
|
SSHORT c;
|
|
|
|
for (;;) {
|
|
c = skip_white();
|
|
if (c != '\n' || QLI_line->line_type != line_blob)
|
|
break;
|
|
qli_line* prior = QLI_line;
|
|
next_line(true);
|
|
if (prior == QLI_line)
|
|
break;
|
|
}
|
|
|
|
// If we hit end of file, make up a phoney token
|
|
|
|
if (!QLI_line) {
|
|
const TEXT* q = eof_string;
|
|
while (*p++ = *q++);
|
|
token->tok_type = tok_eof;
|
|
token->tok_keyword = KW_none;
|
|
return NULL;
|
|
}
|
|
|
|
*p++ = c;
|
|
QLI_token->tok_position = QLI_line->line_position +
|
|
QLI_line->line_ptr - QLI_line->line_data - 1;
|
|
|
|
// On end of file, generate furious but phone end of line tokens
|
|
|
|
char char_class = classes[c];
|
|
|
|
if (char_class & CHR_letter) {
|
|
for (c = nextchar(true); classes[c] & CHR_ident; c = nextchar(true))
|
|
*p++ = c;
|
|
retchar();
|
|
token->tok_type = tok_ident;
|
|
}
|
|
else if (((char_class & CHR_digit) || c == '.') && scan_number(c, &p))
|
|
token->tok_type = tok_number;
|
|
else if (char_class & CHR_quote) {
|
|
token->tok_type = tok_quoted;
|
|
while (true) {
|
|
const SSHORT next = nextchar(false);
|
|
if (!next || next == '\n') {
|
|
retchar();
|
|
IBERROR(63); // Msg 63 unterminated quoted string
|
|
break;
|
|
}
|
|
*p++ = next;
|
|
if ((p - token->tok_string) >= MAXSYMLEN)
|
|
ERRQ_msg_put(470, (TEXT *) MAXSYMLEN, NULL, NULL, NULL, NULL); // Msg 470 literal too long
|
|
|
|
// If there are 2 quotes in a row, interpret 2nd as a literal
|
|
|
|
if (next == c) {
|
|
const SSHORT peek = nextchar(false);
|
|
retchar();
|
|
if (peek != c)
|
|
break;
|
|
nextchar(false);
|
|
}
|
|
}
|
|
}
|
|
else if (c == '\n') {
|
|
// end of line, signal it properly with a phoney token.
|
|
token->tok_type = tok_eol;
|
|
--p;
|
|
const TEXT* q = eol_string;
|
|
while (*q)
|
|
*p++ = *q++;
|
|
}
|
|
else {
|
|
token->tok_type = tok_punct;
|
|
*p++ = nextchar(true);
|
|
if (!HSH_lookup(token->tok_string, 2))
|
|
{
|
|
retchar();
|
|
--p;
|
|
}
|
|
}
|
|
|
|
token->tok_length = p - token->tok_string;
|
|
*p = '\0';
|
|
|
|
if (token->tok_string[0] == '$' &&
|
|
trans_limit < TRANS_LIMIT && (p = getenv(token->tok_string + 1)))
|
|
{
|
|
LEX_push_string(p);
|
|
++trans_limit;
|
|
token = LEX_token();
|
|
--trans_limit;
|
|
return token;
|
|
}
|
|
|
|
qli_symbol* symbol = HSH_lookup(token->tok_string, token->tok_length);
|
|
token->tok_symbol = symbol;
|
|
if (symbol && symbol->sym_type == SYM_keyword)
|
|
token->tok_keyword = (KWWORDS) symbol->sym_keyword;
|
|
else
|
|
token->tok_keyword = KW_none;
|
|
|
|
if (sw_trace)
|
|
puts(token->tok_string);
|
|
|
|
return token;
|
|
}
|
|
|
|
|
|
static bool get_line(FILE * file,
|
|
TEXT * buffer,
|
|
USHORT size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* g e t _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Read a line. If the line is terminated by
|
|
* an EOL, return true. If the buffer is exhausted and non-blanks
|
|
* would be discarded, return an error. If EOF is detected,
|
|
* return false. Regardless, a null terminated string is returned.
|
|
*
|
|
**************************************/
|
|
bool overflow_flag = false;
|
|
SSHORT c;
|
|
|
|
errno = 0;
|
|
TEXT* p = buffer;
|
|
SLONG length = size;
|
|
|
|
while (true) {
|
|
c = getc(file);
|
|
if (c == EOF) {
|
|
if (SYSCALL_INTERRUPTED(errno) && !QLI_abort) {
|
|
errno = 0;
|
|
continue;
|
|
}
|
|
if (QLI_abort)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
if (--length > 0)
|
|
*p++ = c;
|
|
else if (c != ' ' && c != '\n')
|
|
overflow_flag = true;
|
|
if (c == '\n')
|
|
break;
|
|
}
|
|
|
|
*p = 0;
|
|
if (c == EOF)
|
|
return false;
|
|
|
|
if (overflow_flag)
|
|
IBERROR(477); // Msg 477 input line too long
|
|
|
|
if (sw_verify)
|
|
fputs(buffer, stdout);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int nextchar(const bool eof_ok)
|
|
{
|
|
/**************************************
|
|
*
|
|
* n e x t c h a r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get the next character from the input stream.
|
|
*
|
|
**************************************/
|
|
// Get the next character in the current line. If we run out,
|
|
// get the next line. If the line source runs out, pop the
|
|
// line source. If we run out of line sources, we are distinctly
|
|
// at end of file.
|
|
|
|
while (QLI_line) {
|
|
const SSHORT c = *QLI_line->line_ptr++;
|
|
if (c)
|
|
return c;
|
|
next_line(eof_ok);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void next_line(const bool eof_ok)
|
|
{
|
|
/**************************************
|
|
*
|
|
* n e x t _ l i n e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get the next line from the input stream.
|
|
*
|
|
**************************************/
|
|
TEXT *p;
|
|
|
|
while (QLI_line) {
|
|
bool flag = false;
|
|
|
|
// Get next line from where ever. If it comes from either the terminal
|
|
// or command file, check for another command file.
|
|
|
|
if (QLI_line->line_type == line_blob) {
|
|
// If the current blob segment contains another line, use it
|
|
|
|
if ((p = QLI_line->line_ptr) != QLI_line->line_data
|
|
&& p[-1] == '\n' && *p)
|
|
flag = true;
|
|
else {
|
|
// Initialize line block for retrieval
|
|
|
|
p = QLI_line->line_data;
|
|
QLI_line->line_ptr = QLI_line->line_data;
|
|
|
|
flag = PRO_get_line(QLI_line->line_source_blob, p,
|
|
QLI_line->line_size);
|
|
if (flag && QLI_echo)
|
|
printf("%s", QLI_line->line_data);
|
|
}
|
|
}
|
|
else {
|
|
// Initialize line block for retrieval
|
|
|
|
QLI_line->line_ptr = QLI_line->line_data;
|
|
p = QLI_line->line_data;
|
|
|
|
if (QLI_line->line_type == line_stdin)
|
|
flag = LEX_get_line(QLI_prompt, p, (int) QLI_line->line_size);
|
|
else if (QLI_line->line_type == line_file) {
|
|
flag = get_line(QLI_line->line_source_file, p, QLI_line->line_size);
|
|
if (QLI_echo)
|
|
printf("%s", QLI_line->line_data);
|
|
}
|
|
if (flag) {
|
|
TEXT* q;
|
|
for (q = p; classes[static_cast<UCHAR>(*q)] & CHR_white; q++);
|
|
if (*q == '@') {
|
|
TEXT filename[MAXPATHLEN];
|
|
for (p = q + 1, q = filename; *p && *p != '\n';)
|
|
*q++ = *p++;
|
|
*q = 0;
|
|
QLI_line->line_ptr = p;
|
|
LEX_push_file(filename, true);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If get got a line, we're almost done
|
|
|
|
if (flag)
|
|
break;
|
|
|
|
// We hit end of file. Either complain about the circumstances
|
|
// or just close out the current input source. Don't close the
|
|
// input source if it's the terminal and we're at a continuation
|
|
// prompt.
|
|
|
|
if (eof_ok && (QLI_line->line_next || QLI_prompt != QLI_cont_string)) {
|
|
LEX_pop_line();
|
|
return;
|
|
}
|
|
|
|
// this is an unexpected end of file
|
|
|
|
if (QLI_line->line_type == line_blob)
|
|
ERRQ_print_error(64, QLI_line->line_source_name, NULL, NULL, NULL,
|
|
NULL);
|
|
// Msg 64 unexpected end of procedure in procedure %s
|
|
else if (QLI_line->line_type == line_file)
|
|
ERRQ_print_error(65, QLI_line->line_source_name, NULL, NULL, NULL,
|
|
NULL);
|
|
// Msg 65 unexpected end of file in file %s
|
|
else {
|
|
if (QLI_line->line_type == line_string)
|
|
LEX_pop_line();
|
|
IBERROR(66); // Msg 66 unexpected eof
|
|
}
|
|
}
|
|
|
|
if (!QLI_line)
|
|
return;
|
|
|
|
QLI_line->line_position = QLI_position;
|
|
|
|
// Dump output to the trace file
|
|
|
|
if (QLI_line->line_type == line_blob)
|
|
while (*p)
|
|
p++;
|
|
else {
|
|
while (*p)
|
|
putc(*p++, trace_file);
|
|
QLI_position += (TEXT *) p - QLI_line->line_data;
|
|
#ifdef WIN_NT
|
|
// account for the extra line-feed on OS/2 and Windows NT
|
|
// to determine file position
|
|
|
|
QLI_position++;
|
|
#endif
|
|
}
|
|
|
|
QLI_line->line_length = (TEXT *) p - QLI_line->line_data;
|
|
}
|
|
|
|
|
|
static void retchar()
|
|
{
|
|
/**************************************
|
|
*
|
|
* r e t c h a r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return a character to the input stream.
|
|
*
|
|
**************************************/
|
|
|
|
// CVC: Too naive implementation: what if the pointer is at the beginning?
|
|
fb_assert(QLI_line)
|
|
--QLI_line->line_ptr;
|
|
}
|
|
|
|
|
|
static bool scan_number(SSHORT c,
|
|
TEXT** ptr)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s c a n _ n u m b e r
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Pass on a possible numeric literal.
|
|
*
|
|
**************************************/
|
|
bool dot = false;
|
|
|
|
TEXT* p = *ptr;
|
|
|
|
// If this is a leading decimal point, check that the next
|
|
// character is really a digit, otherwise backout
|
|
|
|
if (c == '.') {
|
|
c = nextchar(true);
|
|
retchar();
|
|
if (!(classes[c] & CHR_digit))
|
|
return false;
|
|
dot = true;
|
|
}
|
|
|
|
// Gobble up digits up to a single decimal point
|
|
|
|
for (;;) {
|
|
c = nextchar(true);
|
|
if (classes[c] & CHR_digit)
|
|
*p++ = c;
|
|
else if (!dot && c == '.') {
|
|
*p++ = c;
|
|
dot = true;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
// If this is an exponential, eat the exponent sign and digits
|
|
|
|
if (UPPER(c) == 'E') {
|
|
*p++ = c;
|
|
c = nextchar(true);
|
|
if (c == '+' || c == '-') {
|
|
*p++ = c;
|
|
c = nextchar(true);
|
|
}
|
|
while (classes[c] & CHR_digit) {
|
|
*p++ = c;
|
|
c = nextchar(true);
|
|
}
|
|
}
|
|
|
|
retchar();
|
|
*ptr = p;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static int skip_white(void)
|
|
{
|
|
/**************************************
|
|
*
|
|
* s k i p _ w h i t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Skip over while space and comments in input stream
|
|
*
|
|
**************************************/
|
|
SSHORT c;
|
|
|
|
while (true) {
|
|
c = nextchar(true);
|
|
const char char_class = classes[c];
|
|
if (char_class & CHR_white)
|
|
continue;
|
|
if (c == '/') {
|
|
SSHORT next = nextchar(true);
|
|
if (next != '*') {
|
|
retchar();
|
|
return c;
|
|
}
|
|
c = nextchar(false);
|
|
while ((next = nextchar(false)) && !(c == '*' && next == '/'))
|
|
c = next;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|