/* * 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 #include #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 "../jrd/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, ×); 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(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; }