8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-27 20:03:03 +01:00
firebird-mirror/src/qli/picstr.cpp
2010-10-12 08:02:57 +00:00

1204 lines
23 KiB
C++

/*
* PROGRAM: JRD Command Oriented Query Language
* MODULE: picstr.cpp
* DESCRIPTION: Picture String Handler
*
* The contents of this file are subject to the Interbase Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy
* of the License at http://www.Inprise.com/IPL.html
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
* or implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code was created by Inprise Corporation
* and its predecessors. Portions created by Inprise Corporation are
* Copyright (C) Inprise Corporation.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
*/
#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include "../qli/dtr.h"
#include "../qli/exe.h"
#include "../qli/format.h"
#include "../qli/all_proto.h"
#include "../qli/err_proto.h"
#include "../qli/picst_proto.h"
#include "../qli/mov_proto.h"
#include "../common/classes/timestamp.h"
#include "../common/classes/VaryStr.h"
#include "../yvalve/gds_proto.h"
#include "../common/classes/FpeControl.h"
const int PRECISION = 10000;
static TEXT* cvt_to_ascii(SLONG, TEXT*, int);
static const TEXT* default_edit_string(const dsc*, TEXT*);
static void edit_alpha(const dsc*, pics*, TEXT**, USHORT);
static void edit_date(const dsc*, pics*, TEXT**);
static void edit_float(const dsc*, pics*, TEXT**);
static void edit_numeric(const dsc*, pics*, TEXT**);
static int generate(pics*);
static void literal(pics*, TEXT, TEXT**);
static const TEXT* alpha_weekdays[] =
{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
};
static const TEXT* alpha_months[] =
{
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
pics* PIC_analyze(const TEXT* string, const dsc* desc)
{
/**************************************
*
* P I C _ a n a l y z e
*
**************************************
*
* Functional description
* Analyze a picture in preparation for formatting.
*
**************************************/
if (!string)
{
if (!desc)
return NULL;
string = default_edit_string(desc, NULL);
}
pics* picture = (pics*) ALLOCD(type_pic);
picture->pic_string = picture->pic_pointer = string;
// Make a first pass just counting characters
bool debit = false;
TEXT c;
while ((c = generate(picture)) && c != '?')
{
c = UPPER(c);
switch (c)
{
case 'X':
case 'A':
++picture->pic_chars;
break;
case '9':
case 'Z':
case '*':
// Count all digits;
// count them as fractions only after a decimal pt and
// before an E
++picture->pic_digits;
if (picture->pic_decimals && !picture->pic_exponents)
++picture->pic_fractions;
break;
case 'H':
++picture->pic_hex_digits;
break;
case '.':
++picture->pic_decimals;
break;
case '-':
case '+':
picture->pic_flags |= PIC_signed;
case '$':
if (picture->pic_chars || picture->pic_exponents)
++picture->pic_literals;
else if (picture->pic_floats)
++picture->pic_digits;
else
++picture->pic_floats;
break;
case 'M':
++picture->pic_months;
break;
case 'D':
// DB is ambiguous, could be Day Blank or DeBit...
if (UPPER(*picture->pic_pointer) == 'B')
{
++picture->pic_pointer;
++picture->pic_literals;
debit = true;
}
++picture->pic_days;
break;
case 'Y':
++picture->pic_years;
break;
case 'J':
++picture->pic_julians;
break;
case 'W':
++picture->pic_weekdays;
break;
case 'N':
++picture->pic_nmonths;
break;
case 'E':
++picture->pic_exponents;
break;
case 'G':
++picture->pic_float_digits;
break;
case '(':
case ')':
picture->pic_flags |= PIC_signed;
++picture->pic_brackets;
break;
case '\'':
case '"':
picture->pic_flags |= PIC_literal;
{
TEXT d;
// Shouldn't UPPER be used on d before comparing with c?
while ((d = generate(picture)) && d != c)
++picture->pic_literals;
}
picture->pic_flags &= ~PIC_literal;
break;
case '\\':
++picture->pic_literals;
picture->pic_flags |= PIC_literal;
generate(picture);
picture->pic_flags &= ~PIC_literal;
break;
case 'C':
case 'R':
picture->pic_flags |= PIC_signed;
++picture->pic_brackets;
if (generate(picture))
++picture->pic_brackets;
else
++picture->pic_count;
break;
case 'P':
++picture->pic_meridian;
break;
case 'T':
if (picture->pic_hours < 2)
++picture->pic_hours;
else if (picture->pic_minutes < 2)
++picture->pic_minutes;
else
++picture->pic_seconds;
break;
case 'B':
case '%':
case ',':
case '/':
default:
++picture->pic_literals;
break;
}
}
if (c == '?')
picture->pic_missing = PIC_analyze(picture->pic_pointer, 0);
// if a DB showed up, and the string is numeric, treat the DB as DeBit
if (debit && (picture->pic_digits || picture->pic_hex_digits))
{
--picture->pic_days;
--picture->pic_literals;
picture->pic_flags |= PIC_signed;
++picture->pic_brackets;
++picture->pic_brackets;
}
picture->pic_print_length =
picture->pic_digits +
picture->pic_hex_digits +
picture->pic_chars +
picture->pic_floats +
picture->pic_literals +
picture->pic_decimals +
picture->pic_months + picture->pic_days + picture->pic_weekdays + picture->pic_years +
picture->pic_nmonths + picture->pic_julians +
picture->pic_brackets +
picture->pic_exponents +
picture->pic_float_digits +
picture->pic_hours + picture->pic_minutes + picture->pic_seconds +
picture->pic_meridian;
if (picture->pic_missing)
{
picture->pic_length = MAX(picture->pic_print_length, picture->pic_missing->pic_print_length);
picture->pic_missing->pic_length = picture->pic_length;
}
else
picture->pic_length = picture->pic_print_length;
if (picture->pic_days || picture->pic_weekdays || picture->pic_months || picture->pic_nmonths ||
picture->pic_years || picture->pic_hours || picture->pic_julians)
{
picture->pic_type = pic_date;
}
else if (picture->pic_exponents || picture->pic_float_digits)
picture->pic_type = pic_float;
else if (picture->pic_digits || picture->pic_hex_digits)
picture->pic_type = pic_numeric;
else
picture->pic_type = pic_alpha;
return picture;
}
void PIC_edit(const dsc* desc, pics* picture, TEXT** output, USHORT max_length)
{
/**************************************
*
* P I C _ e d i t
*
**************************************
*
* Functional description
* Edit data from a descriptor through an edit string to a running
* output pointer. For text strings, check that we don't overflow
* the output buffer.
*
**************************************/
switch (picture->pic_type)
{
case pic_alpha:
edit_alpha(desc, picture, output, max_length);
return;
case pic_numeric:
edit_numeric(desc, picture, output);
return;
case pic_date:
edit_date(desc, picture, output);
return;
case pic_float:
edit_float(desc, picture, output);
return;
default:
ERRQ_bugcheck(68); // Msg 68 PIC_edit: class not yet implemented
}
}
void PIC_missing( qli_const* constant, pics* picture)
{
/**************************************
*
* P I C _ m i s s i n g
*
**************************************
*
* Functional description
* Create a literal picture string from
* a descriptor for a missing value so
* we can print the missing value
*
**************************************/
const dsc* desc = &constant->con_desc;
const int l = MAX(desc->dsc_length, picture->pic_length);
qli_str* scratch = (qli_str*) ALLOCDV(type_str, l + 3);
TEXT* p = scratch->str_data;
*p++ = '\"';
PIC_edit(desc, picture, &p, l);
*p++ = '\"';
*p = 0;
pics* missing_picture = PIC_analyze(scratch->str_data, desc);
picture->pic_missing = missing_picture;
picture->pic_length = MAX(picture->pic_print_length, missing_picture->pic_print_length);
missing_picture->pic_length = picture->pic_length;
}
static TEXT* cvt_to_ascii( SLONG number, TEXT* pointer, int length)
{
/**************************************
*
* c v t _ t o _ a s c i i
*
**************************************
*
* Functional description
* Convert a number to a number of ascii digits (plus terminating
* null), updating pointer.
*
**************************************/
pointer += length + 1;
TEXT* p = pointer;
*--p = 0;
while (--length >= 0)
{
*--p = (number % 10) + '0';
number /= 10;
}
return pointer;
}
static const TEXT* default_edit_string(const dsc* desc, TEXT* buff)
{
/**************************************
*
* d e f a u l t _ e d i t _ s t r i n g
*
**************************************
*
* Functional description
* Given a skeletal descriptor, generate a default edit string.
*
**************************************/
TEXT buffer[32];
if (!buff)
buff = buffer;
const SSHORT scale = desc->dsc_scale;
switch (desc->dsc_dtype)
{
case dtype_text:
sprintf(buff, "X(%d)", desc->dsc_length);
break;
case dtype_cstring:
sprintf(buff, "X(%d)", desc->dsc_length - 1);
break;
case dtype_varying:
sprintf(buff, "X(%d)", desc->dsc_length - 2);
break;
case dtype_short:
if (!scale)
return "-(5)9";
if (scale < 0 && scale > -5)
sprintf(buff, "-(%d).9(%d)", 6 + scale, -scale);
else if (scale < 0)
sprintf(buff, "-.9(%d)", -scale);
else
sprintf(buff, "-(%d)9", 5 + scale);
break;
case dtype_long:
if (!scale)
return "-(10)9";
if (scale < 0 && scale > -10)
sprintf(buff, "-(%d).9(%d)", 10 + scale, -scale);
else if (scale < 0)
sprintf(buff, "-.9(%d)", -scale);
else
sprintf(buff, "-(%d)9", 11 + scale);
break;
case dtype_int64:
// replace 16 with 20 later
// (as soon as I have sorted out the rounding issues)
// FSG
if (!scale)
return "-(16)9";
if (scale < 0 && scale > -16)
sprintf(buff, "-(%d).9(%d)", 16 + scale, -scale);
else if (scale < 0)
sprintf(buff, "-.9(%d)", -scale);
else
sprintf(buff, "-(%d)9", 17 + scale);
break;
case dtype_sql_date:
case dtype_timestamp:
return "DD-MMM-YYYY";
case dtype_sql_time:
return "TT:TT:TT.TTTT";
case dtype_real:
return "G(8)";
case dtype_double:
return "G(16)";
default:
return "X(11)";
}
if (buff == buffer)
{
qli_str* string = (qli_str*) ALLOCDV(type_str, strlen(buff));
strcpy(string->str_data, buff);
buff = string->str_data;
}
return buff;
}
static void edit_alpha(const dsc* desc,
pics* picture, TEXT** output, USHORT max_length)
{
/**************************************
*
* e d i t _ a l p h a
*
**************************************
*
* Functional description
* Edit data from a descriptor through an edit string to a running
* output pointer.
*
**************************************/
Firebird::VaryStr<512> temp;
const TEXT* p = NULL;
const USHORT l = MOVQ_get_string(desc, &p, &temp, sizeof(temp));
const TEXT* const end = p + l;
picture->pic_pointer = picture->pic_string;
picture->pic_count = 0;
TEXT* out = *output;
while (p < end)
{
if ((out - *output) >= max_length)
break;
TEXT c = generate(picture);
if (!c || c == '?')
break;
c = UPPER(c);
switch (c)
{
case 'X':
*out++ = *p++;
break;
case 'A':
if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))
*out++ = *p++;
else
IBERROR(69); // Msg 69 conversion error
break;
case 'B':
*out++ = ' ';
break;
case '"':
case '\'':
case '\\':
literal(picture, c, &out);
break;
default:
*out++ = c;
break;
}
}
*output = out;
}
static void edit_date( const dsc* desc, pics* picture, TEXT** output)
{
/**************************************
*
* e d i t _ d a t e
*
**************************************
*
* Functional description
* Edit data from a descriptor through an edit string to a running
* output pointer.
*
**************************************/
SLONG date[2];
DSC temp_desc;
TEXT d, temp[256];
temp_desc.dsc_dtype = dtype_timestamp;
temp_desc.dsc_scale = 0;
temp_desc.dsc_sub_type = 0;
temp_desc.dsc_length = sizeof(date);
temp_desc.dsc_address = (UCHAR*) date;
QLI_validate_desc(temp_desc);
MOVQ_move(desc, &temp_desc);
tm times;
isc_decode_date((ISC_QUAD*) date, &times);
TEXT* p = temp;
const TEXT* nmonth = p;
p = cvt_to_ascii((SLONG) times.tm_mon + 1, p, picture->pic_nmonths);
const TEXT* day = p;
p = cvt_to_ascii((SLONG) times.tm_mday, p, picture->pic_days);
const TEXT* year = p;
p = cvt_to_ascii((SLONG) times.tm_year + 1900, p, picture->pic_years);
const TEXT* julians = p;
p = cvt_to_ascii((SLONG) times.tm_yday + 1, p, picture->pic_julians);
const TEXT* meridian = "";
if (picture->pic_meridian)
{
if (times.tm_hour >= 12)
{
meridian = "PM";
if (times.tm_hour > 12)
times.tm_hour -= 12;
}
else
meridian = "AM";
}
const SLONG seconds = date[1] % (60 * PRECISION);
TEXT* hours = p;
p = cvt_to_ascii((SLONG) times.tm_hour, p, picture->pic_hours);
p = cvt_to_ascii((SLONG) times.tm_min, --p, picture->pic_minutes);
p = cvt_to_ascii((SLONG) seconds, --p, 6);
if (*hours == '0')
*hours = ' ';
SLONG rel_day = (date[0] + 3) % 7;
if (rel_day < 0)
rel_day += 7;
const TEXT* weekday = alpha_weekdays[rel_day];
const TEXT* month = alpha_months[times.tm_mon];
picture->pic_pointer = picture->pic_string;
picture->pic_count = 0;
TEXT* out = *output;
bool sig_day = false;
bool blank = true;
for (;;)
{
TEXT c = generate(picture);
if (!c || c == '?')
break;
c = UPPER(c);
switch (c)
{
case 'Y':
*out++ = *year++;
break;
case 'M':
if (*month)
*out++ = *month++;
break;
case 'N':
*out++ = *nmonth++;
break;
case 'D':
d = *day++;
if (!sig_day && d == '0' && blank)
*out++ = ' ';
else
{
sig_day = true;
*out++ = d;
}
break;
case 'J':
if (*julians)
*out++ = *julians++;
break;
case 'W':
if (*weekday)
*out++ = *weekday++;
break;
case 'B':
*out++ = ' ';
break;
case 'P':
if (*meridian)
*out++ = *meridian++;
break;
case 'T':
if (*hours)
*out++ = *hours++;
break;
case '"':
case '\'':
case '\\':
literal(picture, c, &out);
break;
default:
*out++ = c;
break;
}
if (c != 'B')
blank = false;
}
*output = out;
}
static void edit_float( const dsc* desc, pics* picture, TEXT** output)
{
/**************************************
*
* e d i t _ f l o a t
*
**************************************
*
* Functional description
* Edit data from a descriptor through an edit string to a running
* output pointer.
*
**************************************/
TEXT temp[512];
USHORT l, width, decimal_digits, w_digits, f_digits;
double number = MOVQ_get_double(desc);
const bool negative = (number < 0);
if (negative)
number = -number;
// If exponents are explicitly requested (E-format edit_string), generate them.
// Otherwise, the rules are: if the number in f-format will fit into the allotted
// space, print it in f-format; otherwise print it in e-format.
// (G-format is untrustworthy.)
if (isnan(number))
sprintf(temp, "NaN");
else if (isinf(number))
sprintf(temp, "Infinity");
else if (picture->pic_exponents)
{
width = picture->pic_print_length - picture->pic_floats - picture->pic_literals;
decimal_digits = picture->pic_fractions;
sprintf(temp, "%*.*e", width, decimal_digits, number);
}
else if (number == 0)
sprintf(temp, "%.0f", number);
else
{
width = picture->pic_float_digits - 1 + picture->pic_floats;
f_digits = (width > 2) ? width - 2 : 0;
sprintf(temp, "%.*f", f_digits, number);
w_digits = strlen(temp);
if (f_digits)
{
TEXT* p = temp + w_digits; // find the end
w_digits = w_digits - (f_digits + 1);
while (*--p == '0')
--f_digits;
if (*p != '.')
++p;
*p = 0; // move the end
}
if ((w_digits > width) || (!f_digits && w_digits == 1 && temp[0] == '0'))
{
// if the number doesn't fit in the default window, revert
// to exponential notation; displaying the maximum number of
// mantissa digits.
if (number < 1e100)
decimal_digits = (width > 6) ? width - 6 : 0;
else
decimal_digits = (width > 7) ? width - 7 : 0;
sprintf(temp, "%.*e", decimal_digits, number);
}
}
TEXT* p = temp;
picture->pic_pointer = picture->pic_string;
picture->pic_count = 0;
TEXT* out = *output;
for (l = picture->pic_length - picture->pic_print_length; l > 0; --l)
*out++ = ' ';
bool is_signed = false;
for (;;)
{
const TEXT e = generate(picture);
TEXT c = e;
if (!c || c == '?')
break;
c = UPPER(c);
switch (c)
{
case 'G':
if (!is_signed)
{
if (negative)
*out++ = '-';
else
*out++ = ' ';
is_signed = true;
}
else if (*p)
*out++ = *p++;
break;
case 'B':
*out++ = ' ';
break;
case '"':
case '\'':
case '\\':
literal(picture, c, &out);
break;
case '9':
case 'Z':
{
if (!(*p) || *p > '9' || *p < '0')
break;
TEXT d = *p++;
if (c == '9' && d == ' ')
d = '0';
else if (c == 'Z' && d == '0')
d = ' ';
*out++ = d;
}
break;
case '.':
*out++ = (*p == c) ? *p++ : c;
break;
case 'E':
if (!*p)
break;
*out++ = e;
if (UPPER(*p) == c)
++p;
break;
case '+':
case '-':
if (!*p)
break;
if (*p != '+' && *p != '-')
{
if (is_signed)
*out++ = c;
else if (c == '-' && !negative)
*out++ = ' ';
else if (c == '+' && negative)
*out++ = '-';
else
*out++ = c;
is_signed = true;
}
else if (*p == '-' || c == '+')
*out++ = *p++;
else
{
*out++ = ' ';
p++;
}
break;
default:
*out++ = c;
break;
}
}
*output = out;
}
static void edit_numeric(const dsc* desc, pics* picture, TEXT** output)
{
/**************************************
*
* e d i t _ n u m e r i c
*
**************************************
*
* Functional description
* Edit data from a descriptor through an edit string to a running
* output pointer.
*
**************************************/
bool overflow = false;
TEXT* out = *output;
double number = MOVQ_get_double(desc);
const bool negative = (number < 0);
if (negative)
{
number = -number;
if (!(picture->pic_flags & PIC_signed))
overflow = true;
}
SSHORT scale = picture->pic_fractions;
if (scale)
{
if (scale < 0)
{
do {
number /= 10.;
} while (++scale);
}
else if (scale > 0)
{
do {
number *= 10.;
} while (--scale);
}
}
TEXT temp[512];
TEXT* p = temp;
TEXT* digits = p;
double check;
USHORT power;
if (picture->pic_digits && !overflow)
{
for (check = number, power = picture->pic_digits; power; --power)
check /= 10.0;
if (check >= 1)
overflow = true;
else
{
sprintf(digits, "%0*.0f", picture->pic_digits, number);
p = digits + strlen(digits);
}
}
picture->pic_pointer = picture->pic_string;
bool hex_overflow = false;
const TEXT* hex = 0;
if (picture->pic_hex_digits)
{
hex = p;
p += picture->pic_hex_digits;
for (check = number, power = picture->pic_hex_digits; power; --power)
check /= 16.0;
if (check >= 1)
hex_overflow = true;
else
{
SLONG nh = static_cast<SLONG>(number);
while (p-- > hex)
{
*p = "0123456789abcdef"[nh & 15];
nh >>= 4;
}
}
}
for (USHORT l = picture->pic_length - picture->pic_print_length; l-- > 0;)
*out++ = ' ';
const SLONG n = (number + 0.5 < 1) ? 0 : 1;
TEXT* float_ptr = NULL;
TEXT float_char;
TEXT d;
bool signif = false;
for (;;)
{
TEXT c = generate(picture);
if (!c || c == '?')
break;
c = UPPER(c);
if (overflow && c != 'H')
{
*out++ = '*';
continue;
}
switch (c)
{
case '9':
signif = true;
*out++ = *digits++;
break;
case 'H':
if (hex_overflow)
{
*out++ = '*';
continue;
}
case '*':
case 'Z':
d = (c == 'H') ? *hex++ : *digits++;
if (signif || d != '0')
{
*out++ = d;
signif = true;
}
else
*out++ = (c == '*') ? '*' : ' ';
break;
case '+':
case '-':
case '$':
if (c == '+' && negative)
c = '-';
else if (c == '-' && !negative)
c = ' ';
float_char = c;
if (!float_ptr)
{
float_ptr = out;
*out++ = n ? c : ' ';
break;
}
d = *digits++;
if (signif || d != '0')
{
*out++ = d;
signif = true;
break;
}
*float_ptr = ' ';
float_ptr = out;
*out++ = n ? c : ' ';
break;
case '(':
case ')':
*out++ = negative ? c : ' ';
break;
case 'C':
case 'D':
d = generate(picture);
if (negative)
{
*out++ = c;
*out++ = UPPER(d);
}
else if (d)
{
*out++ = ' ';
*out++ = ' ';
}
break;
case '.':
signif = true;
*out++ = c;
break;
case 'B':
*out++ = ' ';
break;
case '"':
case '\'':
case '\\':
literal(picture, c, &out);
break;
case ',':
if (signif)
*out++ = c;
else if (float_ptr)
{
*float_ptr = ' ';
float_ptr = out;
*out++ = float_char;
}
else
*out++ = ' ';
break;
default:
*out++ = c;
break;
}
if (picture->pic_flags & PIC_suppress_blanks && out[-1] == ' ')
--out;
}
*output = out;
}
static int generate( pics* picture)
{
/**************************************
*
* g e n e r a t e
*
**************************************
*
* Functional description
* Generate the next character from a picture string.
*
**************************************/
TEXT c;
for (;;)
{
// If we're in a repeat, pass it back
if (picture->pic_count > 0)
{
--picture->pic_count;
return picture->pic_character;
}
// Get the next character. If null, we're done
c = *picture->pic_pointer++;
// If we're in literal mode, just return the character
if (picture->pic_flags & PIC_literal)
break;
// If the next character is also a paren, it is a debit indicating
// bracket. If so, swallow the second.
if ((c == ')' || c == '(') && *picture->pic_pointer == c)
{
picture->pic_pointer++;
return (picture->pic_character = c);
}
// If the character is null and not a repeat count, we're done
if (!c || c != '(')
break;
// We're got a potential repeat count. If real, extract it.
const TEXT* p = picture->pic_pointer;
while (*p >= '0' && *p <= '9')
picture->pic_count = picture->pic_count * 10 + *p++ - '0';
if (p == picture->pic_pointer)
{
c = '(';
break;
}
if (*p == ')')
++p;
picture->pic_pointer = p;
// Someone may have done something very stupid, like specify a repeat
// count of zero. It's too late, as we've already gen'd one instance
// of the edit character -- but let's not access violate, shall we?
if (picture->pic_count)
--picture->pic_count;
}
return (picture->pic_character = c);
}
static void literal( pics* picture, TEXT c, TEXT** ptr)
{
/**************************************
*
* l i t e r a l
*
**************************************
*
* Functional description
* Handle a literal string in a picture string.
*
**************************************/
TEXT* p = *ptr;
picture->pic_flags |= PIC_literal;
TEXT d;
if (c == '\\')
*p++ = generate(picture);
else
while ((d = generate(picture)) && d != c)
*p++ = d;
*ptr = p;
picture->pic_flags &= ~PIC_literal;
}