| /* |
| * Copyright (c) 2021-2025 Symas Corporation |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of the Symas Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| #include <algorithm> |
| #include <cctype> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <ctime> |
| #include <set> |
| #include <stack> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <dirent.h> |
| #include <dlfcn.h> |
| #include <err.h> |
| #include <fcntl.h> |
| #include <fenv.h> |
| #include <math.h> // required for fpclassify(3), not in cmath |
| #include <setjmp.h> |
| #include <signal.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| #include <stdarg.h> |
| #if __has_include(<errno.h>) |
| # include <errno.h> // for program_invocation_short_name |
| #endif |
| |
| #include "config.h" |
| #include "libgcobol-fp.h" |
| |
| #include "ec.h" |
| #include "common-defs.h" |
| #include "io.h" |
| #include "gcobolio.h" |
| #include "libgcobol.h" |
| #include "gfileio.h" |
| #include "charmaps.h" |
| #include "valconv.h" |
| #include <sys/mman.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <execinfo.h> |
| #include "exceptl.h" |
| |
| /* BSD extension. */ |
| #if !defined(LOG_PERROR) |
| #define LOG_PERROR 0 |
| #endif |
| |
| #if !defined (HAVE_STRFROMF32) |
| # if __FLT_MANT_DIG__ == 24 && __FLT_MAX_EXP__ == 128 |
| static int |
| strfromf32 (char *s, size_t n, const char *f, float v) |
| { |
| return snprintf (s, n, f, (double) v); |
| } |
| # else |
| # error "It looks like float on this platform is not IEEE754" |
| # endif |
| #endif |
| |
| #if !defined (HAVE_STRFROMF64) |
| # if __DBL_MANT_DIG__ == 53 && __DBL_MAX_EXP__ == 1024 |
| static int |
| strfromf64 (char *s, size_t n, const char *f, double v) |
| { |
| return snprintf (s, n, f, v); |
| } |
| # else |
| # error "It looks like double on this platform is not IEEE754" |
| # endif |
| #endif |
| |
| // Enable Declarative tracing via "match_declarative" environment variable. |
| #if defined(MATCH_DECLARATIVE) || true |
| # undef MATCH_DECLARATIVE |
| # define MATCH_DECLARATIVE getenv("match_declarative") |
| #else |
| # define MATCH_DECLARATIVE (nullptr) |
| #endif |
| |
| // This couldn't be defined in symbols.h because it conflicts with a LEVEL66 |
| // in parse.h |
| #define LEVEL66 (66) |
| #define LEVEL88 (88) |
| |
| // These global variables are returned when the functions |
| // EXCEPTION-FILE |
| // EXCEPTION-LOCATION |
| // EXCEPTION-STATEMENT |
| // EXCEPTION-STATUS |
| // are called |
| |
| // These global values are established as the COBOL program executes |
| int __gg__exception_code = 0 ; |
| int __gg__exception_file_status = 0 ; |
| const char *__gg__exception_file_name = NULL ; |
| const char *__gg__exception_program_id = NULL ; |
| const char *__gg__exception_section = NULL ; |
| const char *__gg__exception_paragraph = NULL ; |
| const char *__gg__exception_source_file = NULL ; |
| int __gg__exception_line_number = 0 ; |
| const char *__gg__exception_statement = NULL ; |
| int __gg__default_compute_error = 0 ; |
| int __gg__rdigits = 0 ; |
| int __gg__nop = 0 ; |
| int __gg__main_called = 0 ; |
| |
| // During SORT operations, we don't want the end-of-file condition, which |
| // happens as a matter of course, from setting the EOF exception condition. |
| // Setting this variable to 'true' suppresses the error condition. |
| static bool sv_suppress_eof_ec = false; |
| |
| // What follows are arrays that are used by features like INSPECT, STRING, |
| // UNSTRING, and, particularly, arithmetic_operation. These features are |
| // characterized by having unknown, and essentially unlimited, numbers of |
| // variables. Consider, for example, ADD A B C D ... TO L M N O ... |
| |
| // Although originally implemented with malloc/free, that's terribly inefficient |
| // on its face; arithmetic is done frequently. The next step was to malloc |
| // buffers just once, and have them grow as needed, but that resulted in a lot |
| // of code being laid down, because it meant checking each buffer size at |
| // run-time, and laying down the code to be executed if the size was inadequate. |
| // |
| // The current solution is to make the pointers to the arrays of values global, |
| // and initialize them with space for MIN_FIELD_BLOCK_SIZE values. Thus, at |
| // compile time, we can ignore all tests for fewer than MIN_FIELD_BLOCK_SIZE |
| // (which is generally the case). Only when N is greater than the MIN do we |
| // have to check the current run-time size and, if necessary, expand the buffer |
| // with realloc. |
| size_t __gg__arithmetic_rounds_size = 0 ; |
| int * __gg__arithmetic_rounds = NULL ; |
| |
| size_t __gg__fourplet_flags_size = 0 ; |
| int * __gg__fourplet_flags = NULL ; |
| |
| static size_t treeplet_1_size = 0 ; |
| cblc_field_t ** __gg__treeplet_1f = NULL ; |
| size_t * __gg__treeplet_1o = NULL ; |
| size_t * __gg__treeplet_1s = NULL ; |
| |
| static size_t treeplet_2_size = 0 ; |
| cblc_field_t ** __gg__treeplet_2f = NULL ; |
| size_t * __gg__treeplet_2o = NULL ; |
| size_t * __gg__treeplet_2s = NULL ; |
| |
| static size_t treeplet_3_size = 0 ; |
| cblc_field_t ** __gg__treeplet_3f = NULL ; |
| size_t * __gg__treeplet_3o = NULL ; |
| size_t * __gg__treeplet_3s = NULL ; |
| |
| static size_t treeplet_4_size = 0 ; |
| cblc_field_t ** __gg__treeplet_4f = NULL ; |
| size_t * __gg__treeplet_4o = NULL ; |
| size_t * __gg__treeplet_4s = NULL ; |
| |
| |
| // This value is increased every time PROCEDURE DIVISION is processed. It is |
| // used to keep track of local variables. |
| size_t __gg__unique_prog_id = 0 ; |
| |
| // Whenever an exception status is set, a snapshot of the current statement |
| // location information are established in the "last_exception..." variables. |
| // This is in accordance with the ISO requirements of "14.6.13.1.1 General" that |
| // describe how a "last exception status" is maintained. |
| // other "location" information |
| static int last_exception_code; |
| static const char *last_exception_program_id; |
| static const char *last_exception_section; |
| static const char *last_exception_paragraph; |
| static const char *last_exception_source_file; |
| static int last_exception_line_number; |
| static const char *last_exception_statement; |
| // These variables are similar, and are established when an exception is |
| // raised for a file I-O operation. |
| static cblc_file_prior_op_t last_exception_file_operation; |
| static file_status_t last_exception_file_status; |
| static const char *last_exception_file_name; |
| |
| static int sv_from_raise_statement = 0; |
| |
| typedef void (*PFUNC)(); |
| static std::unordered_map<int, char ***> accessible_programs; |
| static std::unordered_map<int, PFUNC **> accessible_pointers; |
| |
| #define ARG_LIMIT 512 |
| char *__gg__call_parameter_signature = NULL; |
| int __gg__call_parameter_count = A_ZILLION; |
| size_t __gg__call_parameter_lengths[ARG_LIMIT]; |
| |
| // This is used for managing ENTRY statements in COBOL routines |
| void *__gg__entry_location = NULL; |
| |
| // This is the current value at the back of the PERFORM <PROC> stack of |
| // procedure signatures. Said another way: When the exit address at |
| // the end of a paragraph matches this value address, then it is time to pop |
| // the return address off of the stack. It's in this fashion that we implements |
| // nested PERFORM PROC statements. |
| void *__gg__exit_address = NULL; |
| |
| /* |
| * ec_status_t represents the runtime exception condition status for |
| * any statement. There are 4 states: |
| * 1. initial, all zeros |
| * 2. updated, copy global EC state for by Declarative and/or default |
| * 3. matched, Declarative found, isection nonzero |
| * 4. handled, where handled == type |
| * |
| * If the statement includes some kind of ON ERROR |
| * clause that covers it, the generated code does not raise an EC. |
| * |
| * The status is updated by __gg_match_exception if it runs, else |
| * __gg__check_fatal_exception. |
| * |
| * If a Declarative is matched, its section number is passed to handled_by(), |
| * which does two things: |
| * 1. sets isection to record the declarative |
| * 2. for a nonfatal EC, sets handled, indication no further action is needed |
| * |
| * A Declarative may use RESUME, which clears ec_status, which is a "handled" state. |
| * |
| * Default processing ensures return to initial state. |
| */ |
| class ec_status_t { |
| public: |
| struct file_status_t { |
| size_t ifile; |
| cblc_file_prior_op_t operation; |
| cbl_file_mode_t mode; |
| cblc_field_t *user_status; |
| const char * filename; |
| file_status_t() |
| : ifile(0) |
| , operation(file_op_none) |
| , mode(file_mode_none_e) |
| , user_status(nullptr) |
| , filename(nullptr) |
| {} |
| explicit file_status_t( const cblc_file_t *file ) |
| : ifile(file->symbol_table_index) |
| , operation(file->prior_op) |
| , mode(cbl_file_mode_t(file->mode_char)) |
| , user_status(file->user_status) |
| , filename(file->filename) |
| {} |
| const char * op_str() const { |
| switch( operation ) { |
| case file_op_none: return "none"; |
| case file_op_open: return "open"; |
| case file_op_close: return "close"; |
| case file_op_start: return "start"; |
| case file_op_read: return "read"; |
| case file_op_write: return "write"; |
| case file_op_rewrite: return "rewrite"; |
| case file_op_delete: return "delete"; |
| } |
| return "???"; |
| } |
| }; |
| private: |
| char msg[132]; |
| ec_type_t type, handled; |
| size_t isection; |
| cbl_enabled_exceptions_t enabled; |
| cbl_declaratives_t declaratives; |
| struct file_status_t file; |
| public: |
| int lineno; |
| const char *source_file; |
| cbl_name_t statement; // e.g., "ADD" |
| |
| ec_status_t() |
| : type(ec_none_e) |
| , handled(ec_none_e) |
| , isection(0) |
| , lineno(0) |
| , source_file(NULL) |
| { |
| msg[0] = statement[0] = '\0'; |
| } |
| |
| bool is_fatal() const; |
| ec_status_t& update(); |
| |
| bool is_enabled() const { return enabled.match(type); } |
| bool is_enabled( ec_type_t ec) const { return enabled.match(ec); } |
| ec_status_t& handled_by( size_t declarative_section ) { |
| isection = declarative_section; |
| // A fatal exception remains unhandled unless RESUME clears it. |
| if( ! is_fatal() ) { |
| handled = type; |
| } |
| return *this; |
| } |
| ec_status_t& clear() { |
| handled = type = ec_none_e; |
| isection = 0; |
| lineno = 0; |
| msg[0] = statement[0] = '\0'; |
| return *this; |
| } |
| bool unset() const { return isection == 0 && lineno == 0; } |
| |
| void reset_environment() const; |
| ec_status_t& copy_environment(); |
| |
| // Return the EC's type if it is *not* handled. |
| ec_type_t unhandled() const { |
| bool was_handled = ec_cmp(type, handled); |
| return was_handled? ec_none_e : type; |
| } |
| |
| bool done() const { return unhandled() == ec_none_e; } |
| |
| const file_status_t& file_status() const { return file; } |
| |
| const char * exception_location() { |
| snprintf(msg, sizeof(msg), "%s:%d: '%s'", source_file, lineno, statement); |
| return msg; |
| } |
| }; |
| |
| /* |
| * Capture the global EC status at the beginning of Declarative matching. While |
| * executing the Declarative, push the current status on a stack. When the |
| * Declarative returns, restore EC status from the stack. |
| * |
| * If the Declarative includes a RESUME statement, it clears the on-stack |
| * status, thus avoiding any default handling. |
| */ |
| static ec_status_t ec_status; |
| static std::stack<ec_status_t> ec_stack; |
| |
| static cbl_enabled_exceptions_t enabled_ECs; |
| static cbl_declaratives_t declaratives; |
| |
| static const ec_descr_t * |
| local_ec_type_descr( ec_type_t type ) { |
| auto p = std::find( __gg__exception_table, __gg__exception_table_end, type ); |
| if( p == __gg__exception_table_end ) |
| { |
| warnx("%s:%d: no such EC value %08x", __func__, __LINE__, type); |
| __gg__abort("Fell off the end of the __gg__exception_table"); |
| } |
| return p; |
| } |
| |
| cblc_file_t * __gg__file_stashed(); |
| |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wunused-function" |
| // Keep this debugging function around for when it is needed |
| static const char * |
| local_ec_type_str( ec_type_t type ) { |
| if( type == ec_none_e ) return "EC-NONE"; |
| auto p = local_ec_type_descr(type); |
| return p->name; |
| } |
| #pragma GCC diagnostic pop |
| |
| bool |
| ec_status_t::is_fatal() const { |
| auto descr = local_ec_type_descr(type); |
| return descr->disposition == ec_category_fatal_e; |
| } |
| |
| ec_status_t& |
| ec_status_t::update() { |
| handled = ec_none_e; |
| type = ec_type_t(__gg__exception_code); |
| source_file = __gg__exception_source_file; |
| lineno = __gg__exception_line_number; |
| if( __gg__exception_statement ) { |
| snprintf(statement, sizeof(statement), "%s", __gg__exception_statement); |
| } |
| cblc_file_t *stashed = __gg__file_stashed(); |
| this->file = stashed? file_status_t(stashed) : file_status_t(); |
| |
| if( type != ec_none_e && MATCH_DECLARATIVE ) { |
| warnx( "ec_status_t::update:%d: EC %s by %s (handled %s) " , __LINE__, |
| local_ec_type_str(type), |
| __gg__exception_statement? statement : "<none>", |
| local_ec_type_str(handled) ); |
| } |
| |
| this->enabled = ::enabled_ECs; |
| this->declaratives = ::declaratives; |
| |
| return *this; |
| } |
| |
| ec_status_t& |
| ec_status_t::copy_environment() { |
| this->enabled = ::enabled_ECs; |
| this->declaratives = ::declaratives; |
| return *this; |
| } |
| |
| void |
| ec_status_t::reset_environment() const { |
| ::enabled_ECs = enabled; |
| ::declaratives = declaratives; |
| } |
| |
| |
| // This is the default truncation mode |
| static cbl_truncation_mode truncation_mode = trunc_std_e; |
| |
| extern "C" |
| void |
| __gg__set_truncation_mode(cbl_truncation_mode trunc_mode) |
| { |
| truncation_mode = trunc_mode; |
| } |
| |
| struct program_state |
| { |
| // These are the run-time values of these characters. |
| |
| // They are always in source_code space; they get converted to native |
| // when they are used. |
| int rt_decimal_point; |
| int rt_decimal_separator; |
| int rt_quote_character; |
| int rt_low_value_character; |
| int rt_high_value_character; |
| char *rt_currency_signs[256]; |
| const unsigned short *rt_collation; // Points to a table of 256 values; |
| char *rt_program_name; |
| |
| program_state() |
| { |
| // IBM defaults to the \" QUOTE compiler option. quote_character must |
| // be set to \' when the APOST compiler option is in effect |
| |
| // rt_currency_signs provides for replacing a PICTURE currency "symbol" |
| // with a character string referred to in the language specification as |
| // a "sign". The string can be an arbitrary length, allowing the |
| // replacement of, as an example, the currency <symbol> "$" with the |
| // <sign> "USD" |
| |
| rt_decimal_point = ascii_period ; |
| rt_decimal_separator = ascii_comma ; |
| rt_quote_character = ascii_dquote ; // Change this with APOST |
| rt_low_value_character = DEGENERATE_LOW_VALUE ; |
| rt_high_value_character = DEGENERATE_HIGH_VALUE ; |
| |
| // Set all the currency_sign pointers to NULL: |
| |
| memset(rt_currency_signs, 0, sizeof(rt_currency_signs)); |
| |
| // The default collating sequence: |
| if( internal_is_ebcdic ) |
| { |
| rt_collation = __gg__cp1140_to_cp1252_values; |
| } |
| else |
| { |
| rt_collation = __gg__one_to_one_values; |
| } |
| rt_program_name = NULL; |
| } |
| |
| program_state(const program_state &ps) |
| { |
| rt_decimal_point = ps.rt_decimal_point ; |
| rt_decimal_separator = ps.rt_decimal_separator ; |
| rt_quote_character = ps.rt_quote_character ; |
| rt_low_value_character = ps.rt_low_value_character ; |
| // Note throughout the code that there is special processing for the |
| // high-value character. In EBCDIC 0xFF doesn't map to ASCII 0xFF, so |
| // we are forced to avoid converting EBCDIC 0xFF. |
| rt_high_value_character = ps.rt_high_value_character ; |
| rt_collation = ps.rt_collation ; |
| |
| for( int i=0; i<256; i++ ) |
| { |
| if( ps.rt_currency_signs[i] ) |
| { |
| rt_currency_signs[i] = strdup(ps.rt_currency_signs[i]); |
| } |
| else |
| { |
| rt_currency_signs[i] = NULL; |
| } |
| } |
| |
| rt_program_name = ps.rt_program_name ; |
| } |
| |
| ~program_state() |
| { |
| for(int symbol=0; symbol<256; symbol++) |
| { |
| if( rt_currency_signs[symbol] ) |
| { |
| free(rt_currency_signs[symbol]); |
| rt_currency_signs[symbol] = NULL; |
| } |
| } |
| } |
| }; |
| |
| static std::vector<program_state> program_states; |
| #define collated(a) (program_states.back().rt_collation[(unsigned int)(a&0xFF)]) |
| #define program_name (program_states.back().rt_program_name) |
| // #define decimal_point (program_states.back().rt_decimal_point) |
| // #define decimal_separator (program_states.back().rt_decimal_separator) |
| // #define quote_character (program_states.back().rt_quote_character) |
| // #define low_value_character (program_states.back().rt_low_value_character) |
| // #define high_value_character (program_states.back().rt_high_value_character) |
| // #define currency_signs(a) (program_states.back().rt_currency_signs[(a)]) |
| #define currency_signs(a) (__gg__currency_signs[(a)]) |
| |
| #ifdef DEBUG_MALLOC |
| void *malloc(size_t a) |
| { |
| void *retval = malloc(a); |
| fprintf(stderr, " --malloc(%p)-- ", retval); |
| return retval; |
| } |
| #endif |
| |
| void |
| __gg__abort(const char *msg) |
| { |
| fprintf(stderr, "%s: %s\n", program_name, msg); |
| abort(); |
| } |
| |
| void |
| __gg__mabort() |
| { |
| __gg__abort("Memory allocation error\n"); |
| } |
| |
| extern "C" |
| char |
| __gg__get_decimal_point() |
| { |
| return __gg__decimal_point; |
| } |
| |
| extern "C" |
| char |
| __gg__get_decimal_separator() |
| { |
| return __gg__decimal_separator; |
| } |
| |
| extern "C" |
| char * |
| __gg__get_default_currency_string() |
| { |
| return currency_signs(__gg__default_currency_sign); |
| } |
| |
| extern "C" |
| void |
| __gg__resize_int_p( size_t *size, |
| int **block, |
| size_t new_size) |
| { |
| if( new_size > *size ) |
| { |
| *size = new_size; |
| *block = static_cast<int *>(realloc(*block, new_size * sizeof(int))); |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__resize_treeplet(int ngroup, |
| size_t new_size) |
| { |
| switch( ngroup ) |
| { |
| case 1: |
| if( new_size > treeplet_1_size ) |
| { |
| treeplet_1_size = new_size; |
| __gg__treeplet_1f = static_cast<cblc_field_t **>(realloc(__gg__treeplet_1f, new_size * sizeof(cblc_field_t *))); |
| __gg__treeplet_1o = static_cast<size_t *>(realloc(__gg__treeplet_1o, new_size * sizeof(size_t))); |
| __gg__treeplet_1s = static_cast<size_t *>(realloc(__gg__treeplet_1s, new_size * sizeof(size_t))); |
| } |
| break; |
| case 2: |
| if( new_size > treeplet_2_size ) |
| { |
| treeplet_2_size = new_size; |
| __gg__treeplet_2f = static_cast<cblc_field_t **>(realloc(__gg__treeplet_2f, new_size * sizeof(cblc_field_t *))); |
| __gg__treeplet_2o = static_cast<size_t *>(realloc(__gg__treeplet_2o, new_size * sizeof(size_t))); |
| __gg__treeplet_2s = static_cast<size_t *>(realloc(__gg__treeplet_2s, new_size * sizeof(size_t))); |
| } |
| break; |
| case 3: |
| if( new_size > treeplet_3_size ) |
| { |
| treeplet_3_size = new_size; |
| __gg__treeplet_3f = static_cast<cblc_field_t **>(realloc(__gg__treeplet_3f, new_size * sizeof(cblc_field_t *))); |
| __gg__treeplet_3o = static_cast<size_t *>(realloc(__gg__treeplet_3o, new_size * sizeof(size_t))); |
| __gg__treeplet_3s = static_cast<size_t *>(realloc(__gg__treeplet_3s, new_size * sizeof(size_t))); |
| } |
| break; |
| case 4: |
| if( new_size > treeplet_4_size ) |
| { |
| treeplet_4_size = new_size; |
| __gg__treeplet_4f = static_cast<cblc_field_t **>(realloc(__gg__treeplet_4f, new_size * sizeof(cblc_field_t *))); |
| __gg__treeplet_4o = static_cast<size_t *>(realloc(__gg__treeplet_4o, new_size * sizeof(size_t))); |
| __gg__treeplet_4s = static_cast<size_t *>(realloc(__gg__treeplet_4s, new_size * sizeof(size_t))); |
| } |
| break; |
| } |
| } |
| |
| static void |
| initialize_program_state() |
| { |
| // This routine gets called exactly once for a COBOL executable |
| program_state initial_value = {}; |
| program_states.push_back(initial_value); |
| __gg__currency_signs = program_states.back().rt_currency_signs; |
| |
| // This is where we initialize the various tables that have |
| // MIN_FIELD_BLOCK_SIZE elements: |
| |
| __gg__resize_int_p(&__gg__arithmetic_rounds_size, |
| &__gg__arithmetic_rounds, |
| MIN_FIELD_BLOCK_SIZE ); |
| __gg__resize_int_p(&__gg__fourplet_flags_size, |
| &__gg__fourplet_flags, |
| MIN_FIELD_BLOCK_SIZE ); |
| __gg__resize_treeplet(1, MIN_FIELD_BLOCK_SIZE); |
| __gg__resize_treeplet(2, MIN_FIELD_BLOCK_SIZE); |
| __gg__resize_treeplet(3, MIN_FIELD_BLOCK_SIZE); |
| __gg__resize_treeplet(4, MIN_FIELD_BLOCK_SIZE); |
| } |
| |
| extern "C" |
| void |
| __gg__set_program_name(char *progname) |
| { |
| program_name = progname; |
| } |
| |
| extern "C" |
| void |
| __gg__push_program_state() |
| { |
| // Duplicate the state at the back of the stack |
| program_states.push_back(program_states.back()); |
| __gg__currency_signs = program_states.back().rt_currency_signs; |
| } |
| |
| extern "C" |
| void |
| __gg__pop_program_state() |
| { |
| program_states.pop_back(); |
| |
| // #define decimal_point (program_states.back().rt_decimal_point) |
| // #define decimal_separator (program_states.back().rt_decimal_separator) |
| // #define quote_character (program_states.back().rt_quote_character) |
| // #define low_value_character (program_states.back().rt_low_value_character) |
| // #define high_value_character (program_states.back().rt_high_value_character) |
| |
| __gg__decimal_point = program_states.back().rt_decimal_point ; |
| __gg__decimal_separator = program_states.back().rt_decimal_separator ; |
| __gg__quote_character = program_states.back().rt_quote_character ; |
| __gg__low_value_character = program_states.back().rt_low_value_character ; |
| __gg__high_value_character = program_states.back().rt_high_value_character ; |
| __gg__currency_signs = program_states.back().rt_currency_signs ; |
| |
| } |
| |
| static |
| int |
| cstrncmp( char const * const left_, |
| char const * const right_, |
| size_t count) |
| { |
| const char *left = left_; |
| const char *right = right_; |
| // This is the version of strncmp() that uses the current collation |
| |
| // It also is designed to handle strings with embedded NUL characters, so |
| // it treats NULs like any other characters. |
| int retval = 0; |
| while( count-- ) |
| { |
| unsigned char chl = *left++; |
| unsigned char chr = *right++; |
| retval = chl - chr; |
| if( retval ) |
| { |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__decimal_point_is_comma() |
| { |
| program_states.back().rt_decimal_point = ascii_comma ; |
| program_states.back().rt_decimal_separator = ascii_period ; |
| __gg__decimal_point = ascii_comma ; |
| __gg__decimal_separator = ascii_period ; |
| } |
| |
| extern "C" |
| void |
| __gg__init_program_state() |
| { |
| // This routine gets called at DATA DIVISION time. |
| |
| // We need to make sure that the program_states vector has at least one |
| // entry in it. This happens when we are the very first PROGRAM-ID called |
| // in this module. |
| if( program_states.empty() ) |
| { |
| initialize_program_state(); |
| } |
| } |
| |
| static int |
| var_is_refmod( const cblc_field_t *var ) |
| { |
| return (var->attr & refmod_e) != 0; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__power_of_ten(int n) |
| { |
| // 2** 64 = 1.8E19 |
| // 2**128 = 3.4E38 |
| __int128 retval = 1; |
| static const int MAX_POWER = 19 ; |
| static const __int128 pos[MAX_POWER+1] = |
| { |
| 1ULL, // 00 |
| 10ULL, // 01 |
| 100ULL, // 02 |
| 1000ULL, // 03 |
| 10000ULL, // 04 |
| 100000ULL, // 05 |
| 1000000ULL, // 06 |
| 10000000ULL, // 07 |
| 100000000ULL, // 08 |
| 1000000000ULL, // 09 |
| 10000000000ULL, // 10 |
| 100000000000ULL, // 11 |
| 1000000000000ULL, // 12 |
| 10000000000000ULL, // 13 |
| 100000000000000ULL, // 14 |
| 1000000000000000ULL, // 15 |
| 10000000000000000ULL, // 16 |
| 100000000000000000ULL, // 17 |
| 1000000000000000000ULL, // 18 |
| 10000000000000000000ULL, // 19 |
| }; |
| if( n < 0 || n>MAX_POWER*2) // The most we can handle is 10**38 |
| { |
| fprintf(stderr, |
| "Trying to raise 10 to %d as an int128, which we can't do.\n", |
| n); |
| fprintf(stderr, "The problem is in %s.\n", __func__); |
| abort(); |
| } |
| if( n <= MAX_POWER ) |
| { |
| // Up to 10**18 we do directly: |
| retval = pos[n]; |
| } |
| else |
| { |
| // 19 through 38: |
| retval = pos[n/2]; |
| retval *= retval; |
| if( n & 1 ) |
| { |
| retval *= 10; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__scale_by_power_of_ten_1(__int128 value, int N) |
| { |
| // This routine is called when the result of the scaling is not allowed to |
| // have non-zero rdigits. __gg__rdigits is set to 1 when the result is |
| // in the bad zone. The ultimate caller needs to examine __gg__rdigits to |
| // decide what to do about it. |
| |
| // This is a separate routine because of the performance hit caused by the |
| // value % pot operation, which is needed only when certain EC checking is |
| // turned on. |
| if( N > 0 ) |
| { |
| __gg__rdigits = 0; |
| value *= __gg__power_of_ten(N); |
| } |
| else if( N < 0) |
| { |
| // We throwing away the N rightmost digits. Use __gg__rdigits |
| // to let the calling chain know they were non-zero: |
| __int128 pot = __gg__power_of_ten(-N); |
| if( value % pot) |
| { |
| __gg__rdigits = 1; |
| } |
| else |
| { |
| __gg__rdigits = 0; |
| } |
| |
| value /= pot; |
| } |
| else |
| { |
| // N is zero |
| __gg__rdigits = 0; |
| } |
| return value; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__scale_by_power_of_ten_2(__int128 value, int N) |
| { |
| if( N > 0 ) |
| { |
| value *= __gg__power_of_ten(N); |
| } |
| else if( N < 0) |
| { |
| value /= __gg__power_of_ten(-N); |
| } |
| return value; |
| } |
| |
| extern "C" |
| bool |
| __gg__binary_to_string(char *result, int digits, __int128 value) |
| { |
| // The result is not terminated, because this routine is used |
| // to put information directly into cblc_field_t::data |
| // Our caller has to keep track of whether value was negative. |
| |
| // Note that this routine operates in the source code-set space; that is |
| // the result comes back with zero as an ASCII 0x30, not an EBCDIC 0xF0 |
| |
| if( value < 0 ) |
| { |
| value = -value; |
| } |
| result += digits-1 ; |
| while( digits-- ) |
| { |
| *result-- = value%10 + ascii_zero; |
| value /= 10; |
| } |
| // Should value be non-zero, it means we potentially have a size error |
| return value != 0; |
| } |
| |
| extern "C" |
| bool |
| __gg__binary_to_string_internal(char *result, int digits, __int128 value) |
| { |
| // The result is not terminated, because this routine is used |
| // to put information directly into cblc_field_t::data |
| // Our caller has to keep track of whether value was negative. |
| |
| // Note that this routine operates in the source code-set space; that is |
| // the result comes back with zero as an ASCII 0x30, not an EBCDIC 0xF0 |
| |
| if( value < 0 ) |
| { |
| value = -value; |
| } |
| result += digits-1 ; |
| while( digits-- ) |
| { |
| *result-- = (value%10) + internal_zero; |
| value /= 10; |
| } |
| // Should value be non-zero, it means we potentially have a size error |
| return value != 0; |
| } |
| |
| static bool |
| value_is_too_big(const cblc_field_t *var, |
| __int128 value, |
| int source_rdigits) |
| { |
| // This routine is in support of arithmetic ON SIZE ERROR. It returns |
| // TRUE if var hasn't enough bytes to hold the decimal representation |
| // of value: |
| bool retval = false; |
| |
| if( !(var->attr & intermediate_e) ) |
| { |
| if( value < 0 ) |
| { |
| value = -value; |
| } |
| if( var->digits ) |
| { |
| // I don't know how to describe this calculation. I came up with the |
| // equation by working a few examples. For instance, if value is 12345 and |
| // source_rdigits is two, then we are trying to cram 123.45 into 99v99999 |
| // and we have a size error. So, digits is 7, rdigits is 5 and source_rdigits |
| // 2. That means we compare 12345 with 10^(7 - 5 + 2), which is 12345 versus |
| // 10000, which is too big, which means we have a size error. |
| retval = |
| value >= __gg__power_of_ten( var->digits - var->rdigits + source_rdigits); |
| } |
| else |
| { |
| // var->digits is zero. We are dealing with a binary-style number that |
| // fills the whole of the value |
| if( !( var->type == FldNumericBin5 |
| || var->type == FldPointer |
| || var->type == FldIndex) ) |
| { |
| __gg__abort("value_is_too_big() was given a type it doesn't know about"); |
| } |
| if( var->capacity < 16 ) |
| { |
| __int128 max_possible = 1; |
| max_possible = max_possible << (var->capacity * 8); |
| retval = value >= max_possible; |
| } |
| } |
| } |
| |
| return retval; |
| } |
| |
| static void |
| binary_to_big_endian( unsigned char *dest, |
| int bytes, |
| __int128 value |
| ) |
| { |
| if( value < 0 ) |
| { |
| memset(dest, 0xFF, bytes); |
| } |
| else |
| { |
| memset(dest, 0x00, bytes); |
| } |
| |
| dest += bytes-1; |
| while( bytes-- ) |
| { |
| *dest-- = (unsigned char) value; |
| value >>= 8; |
| } |
| } |
| |
| static void |
| binary_to_little_endian( unsigned char *dest, |
| int bytes, |
| __int128 value |
| ) |
| { |
| if( value < 0 ) |
| { |
| memset(dest, 0xFF, bytes); |
| } |
| else |
| { |
| memset(dest, 0x00, bytes); |
| } |
| memcpy(dest, &value, bytes); |
| } |
| |
| static void |
| turn_sign_bit_on(unsigned char *location) |
| { |
| if( internal_is_ebcdic ) |
| { |
| *location &= ~NUMERIC_DISPLAY_SIGN_BIT; |
| } |
| else |
| { |
| *location |= NUMERIC_DISPLAY_SIGN_BIT; |
| } |
| } |
| |
| static void |
| turn_sign_bit_off(unsigned char *location) |
| { |
| if( internal_is_ebcdic ) |
| { |
| *location |= NUMERIC_DISPLAY_SIGN_BIT; |
| } |
| else |
| { |
| *location &= ~NUMERIC_DISPLAY_SIGN_BIT; |
| } |
| } |
| |
| static bool |
| is_sign_bit_on(char ch) |
| { |
| bool retval; |
| if( (unsigned char)ch == 0xFF || ch == 0x00 ) |
| { |
| // Don't let HIGH-VALUE or LOW_VALUE confuse sign detection |
| retval = false; |
| } |
| else |
| { |
| if( internal_is_ebcdic ) |
| { |
| retval = (ch & NUMERIC_DISPLAY_SIGN_BIT) == 0; |
| } |
| else |
| { |
| retval = (ch & NUMERIC_DISPLAY_SIGN_BIT) != 0; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__string_to_alpha_edited_ascii( char *dest, |
| const char *source, |
| int slength, |
| const char *picture) |
| { |
| char *dupe = static_cast<char *>(malloc(slength)); |
| massert(dupe); |
| memcpy(dupe, source, slength); |
| ascii_to_internal_str(dupe, slength); |
| __gg__string_to_alpha_edited(dest, dupe, slength, picture); |
| free(dupe); |
| } |
| |
| static __int128 |
| int128_to_int128_rounded( cbl_round_t rounded, |
| __int128 value, |
| __int128 factor, |
| __int128 remainder, |
| int *compute_error) |
| { |
| // value is signed, and is scaled to the target |
| GCOB_FP128 fpart = ((GCOB_FP128)remainder) / ((GCOB_FP128)factor); |
| __int128 retval = value; |
| |
| if(rounded == nearest_even_e |
| && fpart != GCOB_FP128_LITERAL (-0.5) |
| && fpart != GCOB_FP128_LITERAL (0.5)) |
| { |
| // "bankers rounding" has been requested. |
| // |
| // Since the fraction is not 0.5, this is an ordinary rounding |
| // problem |
| rounded = nearest_away_from_zero_e; |
| } |
| |
| switch(rounded) |
| { |
| case truncation_e: |
| break; |
| |
| case nearest_away_from_zero_e: |
| { |
| // This is ordinary rounding, like you learned in grade school |
| // 0.0 through 0.4 becomes 0 |
| // 0.5 through 0.9 becomes 1 |
| if( value < 0 ) |
| { |
| if( fpart <= GCOB_FP128_LITERAL(-0.5) ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( fpart >= GCOB_FP128_LITERAL(0.5) ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case away_from_zero_e: |
| { |
| // zero stays zero, otherwise head for the next number away from zero |
| if( value < 0 ) |
| { |
| if( fpart != 0 ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( fpart != 0 ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case nearest_toward_zero_e: |
| { |
| // 0.0 through 0.5 becomes 0 |
| // 0.6 through 0.9 becomes 1 |
| if( value < 0 ) |
| { |
| if( fpart < GCOB_FP128_LITERAL(-0.5) ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( fpart > GCOB_FP128_LITERAL(0.5) ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case toward_greater_e: |
| { |
| if( value > 0 ) |
| { |
| if( fpart != 0 ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case toward_lesser_e: |
| { |
| if( value < 0 ) |
| { |
| if(fpart != 0) |
| { |
| retval -= 1; |
| } |
| } |
| break; |
| } |
| |
| case nearest_even_e: |
| { |
| // This is "banker's rounding" |
| // 3.4 -> 3.0 |
| // 3.5 -> 4.0 |
| // 3.6 -> 4.0 |
| |
| // 4.4 -> 4.0 |
| // 4.5 -> 4.0 |
| // 4.6 -> 5.0 |
| |
| // We know that the fractional part is 0.5 or -0.5, and we know that |
| // we want 3 to become 4 and for 4 to stay 4. |
| |
| if( value < 0 ) |
| { |
| if( retval & 1 ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( retval & 1 ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case prohibited_e: |
| { |
| if( fpart != 0 ) |
| { |
| *compute_error |= compute_error_truncate; |
| } |
| |
| break; |
| } |
| |
| default: |
| abort(); |
| break; |
| } |
| return retval; |
| } |
| |
| static __int128 |
| f128_to_i128_rounded( cbl_round_t rounded, |
| GCOB_FP128 value, |
| int *compute_error) |
| { |
| // value is signed, and is scaled to the target |
| GCOB_FP128 ipart; |
| GCOB_FP128 fpart = FP128_FUNC(modf)(value, &ipart); |
| __int128 retval = (__int128)ipart; |
| |
| if(rounded == nearest_even_e |
| && fpart != GCOB_FP128_LITERAL (-0.5) |
| && fpart != GCOB_FP128_LITERAL (0.5)) |
| { |
| // "bankers rounding" has been requested. |
| // |
| // Since the fraction is not 0.5, this is an ordinary rounding |
| // problem |
| rounded = nearest_away_from_zero_e; |
| } |
| |
| switch(rounded) |
| { |
| case truncation_e: |
| break; |
| |
| case nearest_away_from_zero_e: |
| { |
| // This is ordinary rounding, like you learned in grade school |
| // 0.0 through 0.4 becomes 0 |
| // 0.5 through 0.9 becomes 1 |
| if( value < 0 ) |
| { |
| if( fpart <= GCOB_FP128_LITERAL (-0.5) ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( fpart >= GCOB_FP128_LITERAL (0.5) ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case away_from_zero_e: |
| { |
| // zero stays zero, otherwise head for the next number away from zero |
| if( value < 0 ) |
| { |
| if( fpart != 0 ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( fpart != 0 ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case nearest_toward_zero_e: |
| { |
| // 0.0 through 0.5 becomes 0 |
| // 0.6 through 0.9 becomes 1 |
| if( value < 0 ) |
| { |
| if( fpart < GCOB_FP128_LITERAL (-0.5) ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( fpart > GCOB_FP128_LITERAL (0.5) ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case toward_greater_e: |
| { |
| if( value > 0 ) |
| { |
| if( fpart != 0 ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case toward_lesser_e: |
| { |
| if( value < 0 ) |
| { |
| if(fpart != 0) |
| { |
| retval -= 1; |
| } |
| } |
| break; |
| } |
| |
| case nearest_even_e: |
| { |
| // This is "banker's rounding" |
| // 3.4 -> 3.0 |
| // 3.5 -> 4.0 |
| // 3.6 -> 4.0 |
| |
| // 4.4 -> 4.0 |
| // 4.5 -> 4.0 |
| // 4.6 -> 5.0 |
| |
| // We know that the fractional part is 0.5 or -0.5, and we know that |
| // we want 3 to become 4 and for 4 to stay 4. |
| |
| if( value < 0 ) |
| { |
| if( retval & 1 ) |
| { |
| retval -= 1; |
| } |
| } |
| else |
| { |
| if( retval & 1 ) |
| { |
| retval += 1; |
| } |
| } |
| break; |
| } |
| |
| case prohibited_e: |
| { |
| if( fpart != 0 ) |
| { |
| *compute_error |= compute_error_truncate; |
| } |
| |
| break; |
| } |
| |
| default: |
| abort(); |
| break; |
| } |
| return retval; |
| } |
| |
| static void |
| int128_to_field(cblc_field_t *var, |
| unsigned char *location, |
| size_t length, |
| __int128 value, |
| int source_rdigits, |
| enum cbl_round_t rounded, |
| int *compute_error) |
| { |
| // This routine takes a numerical value, and scales and converts it to the |
| // target field type. |
| |
| // It operates in the source codeset space, and converts the final result |
| // to the native codeset space |
| |
| switch( var->type ) |
| { |
| case FldFloat: |
| { |
| switch( var->capacity ) |
| { |
| case 4: |
| { |
| float tvalue = (float)value; |
| tvalue /= (float)__gg__power_of_ten(source_rdigits); |
| *PTRCAST(float, location) = tvalue; |
| break; |
| } |
| |
| case 8: |
| { |
| double tvalue = (double)value; |
| tvalue /= (double)__gg__power_of_ten(source_rdigits); |
| *PTRCAST(double, location) = tvalue; |
| break; |
| } |
| |
| case 16: |
| { |
| // It turns out we have a problem. The IEEE 754 quadruple-precision |
| // binary representation can handle up to 33 digits exactly, and can |
| // handle at most 36 digits. I decided to implement fixed-point |
| // values to 38 places (which is what an __int128 can hold), and as a |
| // result, at this point in the code we can be asking the compiler to |
| // turn a 38-digit __int128 into a _Float128. |
| |
| // This caused a problem that I noticed in COMPUTE var = (2/3)*3. |
| |
| // The default is truncation, and so the PIC 9V9999 result should be |
| // 1.9999. |
| |
| // At this point in the code, the 128-bit value was the |
| // 38-digit 19999999999999999999999999999999999998 |
| |
| // So, I then converted that to a _Float128, and the conversion |
| // routine properly did the best it could and returned exactly |
| // 2E37 |
| |
| // The problem: This rounded the number up from 1.9999...., and so |
| // the truncation resulted in 2.0000 when we wanted 1.9999 |
| |
| // The solution: Throw away digits on the right to make sure there |
| // are no more than 33 significant digits. |
| |
| bool isneg = value < 0; |
| if(isneg) |
| { |
| value = -value; |
| } |
| |
| static __int128 ten33 = __gg__power_of_ten(33); |
| |
| while( value >= ten33 ) |
| { |
| // Lop off the rightmost digits until the result has no more than |
| // 33 digits. |
| value /= 10; |
| source_rdigits -= 1; |
| } |
| |
| if(isneg) |
| { |
| value = -value; |
| } |
| GCOB_FP128 tvalue = (GCOB_FP128 )value; |
| tvalue /= (GCOB_FP128 )__gg__power_of_ten(source_rdigits); |
| // *(_Float128 *)location = tvalue; |
| // memcpy because *(_Float128 *) requires a 16-byte boundary. |
| memcpy(location, &tvalue, 16); |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| { |
| int target_rdigits = var->rdigits; |
| if( var->attr & intermediate_e && var->type == FldNumericBin5) |
| { |
| // The target is an intermediate, meaning that we want to |
| // Make sure our intermediate target has just enough digits and rdigits |
| // to hold the value we've been given: |
| target_rdigits = source_rdigits; |
| var->rdigits = target_rdigits; |
| var->digits = MAX_FIXED_POINT_DIGITS; |
| } |
| else if( var->attr & scaled_e ) |
| { |
| // Our target is scaled. No matter which way we are going, the result |
| // going into memory has no decimal places. |
| target_rdigits = 0; |
| |
| // We have some additional scaling of value to do to make things line up. |
| |
| if( var->rdigits >= 0 ) |
| { |
| // Our target is something like PPPPPP999, meaning that var->actual_length |
| // is 3, and var->rdigits is 6. |
| |
| // By rights, our caller should have given us something like 123 with |
| // source_rdigits of 9. So, we multiply by 10**9 to put the 123 just |
| // to the left of the decimal point, so that they line up with the |
| // target_rdigits of zero we are targeting: |
| source_rdigits -= var->digits + var->rdigits; |
| if(source_rdigits < 0) |
| { |
| // We overshot |
| value *= __gg__power_of_ten(-source_rdigits); |
| source_rdigits = 0; |
| } |
| } |
| else |
| { |
| // Our target is something like 999PPPPPP, so there is a ->digits |
| // of 3 and var->rdigits of -6. |
| |
| // If our caller gave us 123000000, we need to effectively divide |
| // it by 1000000 to line up the 123 with where we want it to go: |
| |
| source_rdigits += (-var->rdigits); |
| } |
| // Either way, we now have everything aligned for the remainder of the |
| // processing to work: |
| } |
| |
| // Convert the scale of value to match the scale of var |
| if( source_rdigits < target_rdigits ) |
| { |
| // The source (value), has fewer rdigits than the target (var) |
| |
| // Multiply value by ten until the source_rdigits matches the |
| // target_rdigits. No rounding will be necessary |
| value *= __gg__power_of_ten(target_rdigits - source_rdigits); |
| source_rdigits = target_rdigits; |
| } |
| |
| if( source_rdigits > target_rdigits ) |
| { |
| // The source(value) has more rdigits than the target (var) |
| |
| // Extract those extra digits; we'll need them for rounding: |
| __int128 factor = __gg__power_of_ten(source_rdigits - target_rdigits); |
| |
| __int128 remainder = value % factor; |
| value /= factor; |
| source_rdigits = target_rdigits; |
| |
| value = int128_to_int128_rounded( rounded, |
| value, |
| factor, |
| remainder, |
| compute_error); |
| } |
| |
| // The documentation for ROUNDED MODE PHOHIBITED says that if the value |
| // doesn't fit into the target, "...the content of the resultant |
| // identifier is unchanged" |
| |
| if( compute_error && *compute_error && rounded == prohibited_e ) |
| { |
| // This is the case where we are not supposed to do anything |
| } |
| else |
| { |
| // Value is now scaled to the target's target_rdigits |
| bool size_error = false; |
| |
| int is_negative = value < 0 ; |
| |
| if( !(var->attr & signable_e) && is_negative ) |
| { |
| if(false) |
| { |
| // I believe the COBOL spec allows for throwing INCOMPATIBLE-DATA |
| // errors. <sound effect: can being kicked down road> |
| printf( "runtime exception: assigning negative " |
| "value to unsigned variable %s\n", |
| var->name); |
| } |
| // Take the absolute value of value |
| value = -value; |
| is_negative = false; |
| } |
| |
| // And now we put value where it belongs |
| switch( var->type ) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| // This is sort of a Hail Mary play. We aren't supposed to do this |
| // conversion if rdigits is non-zero. But we shouldn't have gotten |
| // here if rdigits is non-zero. So, we'll just go with the flow. |
| |
| // Note that sending a signed value to an alphanumeric strips off |
| // any plus or minus signs. |
| size_error = __gg__binary_to_string_internal( |
| PTRCAST(char, location), |
| length, value); |
| break; |
| |
| case FldNumericDisplay: |
| if( var->attr & signable_e ) |
| { |
| // Things get exciting when a numeric-display value is signable |
| |
| if( var->attr & separate_e ) |
| { |
| // Whether positive or negative, a sign there will be: |
| char sign_ch = is_negative ? internal_minus : internal_plus ; |
| if( var->attr & leading_e ) |
| { |
| // The sign character goes into the first location |
| size_error = |
| __gg__binary_to_string_internal(PTRCAST(char, location+1), |
| length-1, value); |
| location[0] = sign_ch; |
| } |
| else |
| { |
| // The sign character goes into the last location |
| size_error = |
| __gg__binary_to_string_internal(PTRCAST(char, location), |
| length-1, value); |
| location[length-1] = sign_ch; |
| } |
| } |
| else |
| { |
| // The sign information is not separate, so we put it into |
| // the number |
| size_error = |
| __gg__binary_to_string_internal(PTRCAST(char, location), |
| length, value); |
| |
| if( size_error && is_negative ) |
| { |
| // If all of the digits are zero, then the result is zero, and |
| // we have to kill the is_negative flag: |
| is_negative = false; |
| for(size_t i=0; i<length; i++) |
| { |
| if( location[i] != internal_zero ) |
| { |
| is_negative = true; |
| break; |
| } |
| } |
| } |
| |
| if( is_negative ) |
| { |
| if( var->attr & leading_e ) |
| { |
| // The sign bit goes into the first digit: |
| turn_sign_bit_on(&location[0]); |
| } |
| else |
| { |
| // The sign bit goes into the last digit: |
| turn_sign_bit_on(&location[length-1]); |
| } |
| } |
| } |
| } |
| else |
| { |
| // It's a simple positive number |
| size_error = __gg__binary_to_string_internal( PTRCAST(char, |
| location), |
| length, value); |
| } |
| |
| break; |
| |
| case FldNumericEdited: |
| { |
| if( value == 0 && (var->attr & blank_zero_e) ) |
| { |
| memset(location, internal_space, length); |
| } |
| else |
| { |
| char ach[512]; |
| |
| // At this point, value is scaled to the target's rdigits |
| |
| size_error = __gg__binary_to_string(ach, var->digits, value); |
| ach[var->digits] = NULLCH; |
| |
| // Convert that string according to the PICTURE clause |
| size_error |= __gg__string_to_numeric_edited( |
| PTRCAST(char, location), |
| ach, |
| target_rdigits, |
| is_negative, |
| var->picture); |
| ascii_to_internal_str( PTRCAST(char, location), var->capacity); |
| } |
| |
| break; |
| } |
| |
| case FldNumericBinary: |
| binary_to_big_endian( location, |
| length, |
| value); |
| size_error = value_is_too_big(var, value, source_rdigits); |
| break; |
| |
| case FldNumericBin5: |
| case FldIndex: |
| case FldLiteralN: |
| case FldPointer: |
| // Weirdly, this might be a figurative constant, hopefully usually |
| // ZERO. Everything but HIGH-VALUE will end up zero. HIGH-VALUE |
| // will become one, but it is, apparently harmless. The HIGH-VALUE |
| // must get processed separately elsewhere. As the author, it would |
| // be nice if I knew -- but I don't. |
| binary_to_little_endian(location, |
| length, |
| value); |
| size_error = value_is_too_big(var, value, source_rdigits); |
| break; |
| |
| case FldAlphaEdited: |
| { |
| char ach[128]; |
| size_error = __gg__binary_to_string(ach, length, value); |
| ach[length] = NULLCH; |
| |
| // Convert that string according to the PICTURE clause |
| __gg__string_to_alpha_edited( |
| PTRCAST(char, location), |
| ach, |
| strlen(ach), |
| var->picture); |
| break; |
| } |
| |
| case FldPacked: |
| { |
| static const unsigned char bin2pd[100] = |
| { |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, |
| 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, |
| 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, |
| 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, |
| 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, |
| 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, |
| 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, |
| 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, |
| 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, |
| 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, |
| } ; |
| |
| // Convert the binary value to packed decimal. |
| |
| // Set the destination bytes to zero |
| memset(location, 0, length); |
| unsigned char sign_nybble = 0; |
| if( !(var->attr & packed_no_sign_e) ) |
| { |
| // This is COMP-3 packed decimal, so we need to make room to the |
| // right of the final decimal digit for the sign nybble: |
| value *= 10; |
| // Figure out what the sign nybble is going to be, and make the |
| // the value positive: |
| if(var->attr & signable_e) |
| { |
| if(value < 0) |
| { |
| sign_nybble = 0x0D; |
| value = -value; |
| } |
| else |
| { |
| sign_nybble = 0x0C; |
| } |
| } |
| else |
| { |
| sign_nybble = 0x0F; |
| if(value < 0) |
| { |
| value = -value; |
| } |
| } |
| } |
| // ploc points to the current rightmost byte of the location: |
| unsigned char *ploc = location + length -1 ; |
| |
| // Build the target from right to left, so that the result is |
| // big-endian: |
| while( value && ploc >= location ) |
| { |
| *ploc-- = bin2pd[value%100]; |
| value /= 100; |
| } |
| |
| // We can put the sign nybble into place at this point. Note that |
| // for COMP-6 numbers the sign_nybble value is zero, so the next |
| // operation is harmless. |
| location[length -1] |= sign_nybble; |
| |
| // If we still have value left, we have a size error |
| if( value ) |
| { |
| size_error = true; |
| } |
| else |
| { |
| if( ( sign_nybble && !(var->digits&1) ) |
| || ( !sign_nybble && (var->digits&1) ) ) |
| { |
| // This is either |
| // comp-3 with an even number of digits, or |
| // comp-6 with an odd number of digits. |
| // Either way, the first byte of the target has to have a high |
| // nybble of zero. If it's non-zero, then we have a size error: |
| if( location[0] & 0xF0 ) |
| { |
| size_error = true; |
| } |
| } |
| } |
| // And we're done. |
| break; |
| } |
| |
| default: |
| fprintf(stderr, "can't convert in %s() %s %d\n", |
| __func__, |
| var->name, |
| var->type); |
| abort(); |
| break; |
| } |
| if( compute_error ) |
| { |
| *compute_error |= size_error ? compute_error_truncate : 0; |
| } |
| } |
| } |
| break; |
| } |
| } |
| |
| static __int128 |
| edited_to_binary( char *ps_, |
| int length, |
| int *rdigits) |
| { |
| const unsigned char *ps = const_cast<const unsigned char *>(PTRCAST(unsigned char, ps_)); |
| // This routine is used for converting NumericEdited strings to |
| // binary. |
| |
| // Numeric edited strings can have all kinds of crap in them: spaces, |
| // slashes, dollar signs...you name it. It might have a minus sign at |
| // the beginning or end, or it might have CR or DB at the end. |
| |
| // We are going to look for a minus sign, D (or d) and use that to flag the |
| // result as negative. We are going to look for a decimal point and count up |
| // the numerical digits to the right of it. And we are going to pretend |
| // that nothing else matters. |
| |
| int hyphen = 0; |
| *rdigits = 0; |
| |
| // index into the ps string |
| int index = 0; |
| |
| // Create a delta_r for counting digits to the right of |
| // any decimal point. If and when we encounter a decimal point, |
| // we'll set this to one, otherwise it'll stay zero. |
| int delta_r = 0; |
| |
| __int128 result = 0; |
| |
| // We need to check the last two characters. If CR or DB, then the result |
| // is negative: |
| if( length >= 2) |
| { |
| if(((ps[length-2]&0xFF) == internal_D || (ps[length-2]&0xFF) == internal_d ) |
| &&((ps[length-1]&0xFF) == internal_B || (ps[length-1]&0xFF) == internal_b)) |
| { |
| hyphen = 1; |
| } |
| else if( ((ps[length-2]&0xFF) == internal_C |
| || (ps[length-2]&0xFF) == internal_c) |
| && ((ps[length-1]&0xFF) == internal_R |
| || (ps[length-1]&0xFF) == internal_r) ) |
| { |
| hyphen = 1; |
| } |
| } |
| |
| while( index < length ) |
| { |
| unsigned char ch = ps[index++] & 0xFF; |
| if( ch == ascii_to_internal(__gg__decimal_point) ) |
| { |
| delta_r = 1; |
| continue; |
| } |
| if( ch == internal_minus ) |
| { |
| hyphen = 1; |
| continue; |
| } |
| |
| if( internal_0 <= ch && ch <= internal_9 ) |
| { |
| result *= 10; |
| // In both EBCDIC and ASCII, this works: |
| result += ch & 0x0F ; |
| *rdigits += delta_r ; |
| continue; |
| } |
| } |
| |
| if( hyphen ) |
| { |
| result = -result; |
| } |
| return result; |
| } |
| |
| static |
| __int128 |
| big_endian_to_binary_signed( |
| const unsigned char *psource, |
| int capacity |
| ) |
| { |
| // This subroutine takes a big-endian value of "capacity" bytes and |
| // converts it to a signed INT128. The highest order bit of the big-endian |
| // value determines whether or not the highest-order bits of the INT128 |
| // return value are off or on. |
| |
| __int128 retval; |
| if( *psource >= 128 ) |
| { |
| retval = -1; |
| } |
| else |
| { |
| retval = 0; |
| } |
| |
| // move the bytes of psource into retval, flipping them end-to-end |
| unsigned char *dest = PTRCAST(unsigned char, &retval); |
| while(capacity > 0) |
| { |
| *dest++ = psource[--capacity]; |
| } |
| return retval; |
| } |
| |
| static |
| __int128 |
| little_endian_to_binary_signed( |
| const unsigned char *psource, |
| int capacity |
| ) |
| { |
| // This subroutine takes a little-endian value of "capacity" bytes and |
| // converts it to a signed INT128. The highest order bit of the little-endian |
| // value determines whether or not the highest-order bits of the INT128 |
| // return value are off or on. |
| |
| __int128 result; |
| |
| // Set all the bits of the result based on the sign of the source: |
| if( psource[capacity-1] >= 128 ) |
| { |
| result = -1; |
| } |
| else |
| { |
| result = 0; |
| } |
| |
| // Copy the low-order bytes into place: |
| memcpy(&result, psource, capacity); |
| return result; |
| } |
| |
| static |
| __int128 |
| little_endian_to_binary_unsigned( |
| const unsigned char *psource, |
| int capacity |
| ) |
| { |
| __int128 result = 0; |
| |
| // Copy the low-order bytes into place: |
| memcpy(&result, psource, capacity); |
| return result; |
| } |
| |
| static |
| __int128 |
| big_endian_to_binary_unsigned( |
| const unsigned char *psource, |
| int capacity |
| ) |
| { |
| // This subroutine takes an unsigned big-endian value of "capacity" bytes and |
| // converts it to an INT128. |
| |
| __int128 retval = 0 ; |
| |
| // move the bytes of psource into retval, flipping them end-to-end |
| unsigned char *dest = PTRCAST(unsigned char, &retval); |
| while(capacity > 0) |
| { |
| *dest++ = psource[--capacity]; |
| } |
| return retval; |
| } |
| |
| static |
| __int128 |
| get_binary_value_local( int *rdigits, |
| const cblc_field_t *resolved_var, |
| unsigned char *resolved_location, |
| size_t resolved_length) |
| { |
| __int128 retval = 0; |
| |
| unsigned char ch; |
| switch( resolved_var->type ) |
| { |
| #if 1 |
| case FldLiteralA : |
| fprintf(stderr, "%s(): is trying to handle a FldLiteralA\n", __func__); |
| abort(); |
| // // Read the data area as a dirty string: |
| // retval = __gg__dirty_to_binary_internal( (const char *)resolved_location, |
| // resolved_length, |
| // rdigits ); |
| break; |
| #endif |
| |
| case FldGroup : |
| case FldAlphanumeric : |
| // Read the data area as a dirty string: |
| retval = __gg__dirty_to_binary_internal( PTRCAST(const char, |
| resolved_location), |
| resolved_length, |
| rdigits ); |
| break; |
| |
| case FldNumericDisplay : |
| if( resolved_location[resolved_length-1] == DEGENERATE_HIGH_VALUE ) |
| { |
| // This is a degenerate case, which violates the language |
| // specification, but nonetheless seems to be a thing. By |
| // default, HIGH-VALUE is usually assumed to be 0xFF. This is |
| // not necessarily true; HIGH-VALUE can be changed by the |
| // SPECIAL-NAMES ALPHABET clause. Furthermore, by definition, |
| // HIGH-VALUE applies *only* to text literals. However, there |
| // seems to be code out in the universe that wants to be able |
| // to compare NumericDisplay values that have been set to |
| // HIGH-VALUE. Consider, for example, code that reads from |
| // a disk file which sets the input field to HIGH-VALUE upon |
| // an end-of-file condition. |
| |
| // This code detects that particular condition, and sets the |
| // resulting binary number to the maximum possible positive |
| // value. |
| |
| // Turn all the bits on |
| memset( &retval, 0xFF, sizeof(retval) ); |
| |
| // Make it positive by turning off the highest order bit: |
| (PTRCAST(unsigned char, &retval))[sizeof(retval)-1] = 0x3F; |
| *rdigits = resolved_var->rdigits; |
| } |
| else |
| { |
| // Pick up the sign byte, and force our value to be positive |
| unsigned char *sign_byte_location; |
| if( (resolved_var->attr & separate_e ) |
| && (resolved_var->attr & leading_e ) ) |
| { |
| sign_byte_location = resolved_location; |
| ch = *sign_byte_location; |
| *sign_byte_location = internal_plus; |
| } |
| else if( (resolved_var->attr & separate_e) |
| && !(resolved_var->attr & leading_e ) ) |
| { |
| sign_byte_location = resolved_location + resolved_length - 1; |
| ch = *sign_byte_location; |
| *sign_byte_location = internal_plus; |
| } |
| else if( (resolved_var->attr & leading_e) ) |
| { |
| sign_byte_location = resolved_location; |
| ch = *sign_byte_location; |
| turn_sign_bit_off(sign_byte_location); |
| } |
| else // if( !(resolved_var->attr & leading_e) ) |
| { |
| sign_byte_location = resolved_location + resolved_length - 1; |
| ch = *sign_byte_location; |
| turn_sign_bit_off(sign_byte_location); |
| } |
| |
| // We know where the decimal point is because of rdigits. Because |
| // we know that it a clean string of ASCII digits, we can use the |
| // dirty converter: |
| retval = __gg__dirty_to_binary_internal(PTRCAST(const char, |
| resolved_location), |
| resolved_length, |
| rdigits ); |
| *rdigits = resolved_var->rdigits; |
| |
| // Restore the sign byte |
| *sign_byte_location = ch; |
| |
| if( ch == internal_minus || is_sign_bit_on(ch) ) |
| { |
| retval = -retval; |
| } |
| } |
| break; |
| |
| case FldNumericEdited : |
| retval = edited_to_binary( PTRCAST(char, resolved_location), |
| resolved_length, |
| rdigits); |
| break; |
| |
| case FldNumericBinary : |
| if( resolved_var->attr & signable_e) |
| { |
| retval = big_endian_to_binary_signed( |
| PTRCAST(const unsigned char, resolved_location), |
| resolved_length); |
| } |
| else |
| { |
| retval = big_endian_to_binary_unsigned( |
| PTRCAST(const unsigned char, resolved_location), |
| resolved_length); |
| } |
| *rdigits = resolved_var->rdigits; |
| break; |
| |
| case FldLiteralN: |
| { |
| if( resolved_var->attr & signable_e) |
| { |
| retval = little_endian_to_binary_signed(resolved_var->data, |
| resolved_var->capacity); |
| } |
| else |
| { |
| retval = little_endian_to_binary_unsigned(resolved_var->data, |
| resolved_var->capacity); |
| } |
| *rdigits = resolved_var->rdigits; |
| break; |
| } |
| |
| case FldNumericBin5: |
| case FldIndex: |
| case FldPointer: |
| if( resolved_var->attr & signable_e) |
| { |
| retval = little_endian_to_binary_signed( |
| PTRCAST(const unsigned char, resolved_location), |
| resolved_length); |
| } |
| else |
| { |
| retval = little_endian_to_binary_unsigned( |
| PTRCAST(const unsigned char, resolved_location), |
| resolved_length); |
| } |
| *rdigits = resolved_var->rdigits; |
| break; |
| |
| case FldPacked: |
| { |
| static const unsigned char dp2bin[160] = |
| { |
| // This may not be the weirdest table I've ever created, but it is |
| // certainly a contender. Given the packed decimal byte 0x23, it |
| // returns the equivalent decimal value of 23. |
| 00, 01, 02, 03, 04, 05, 06, 07, 8, 9, 0, 0, 0, 0, 0, 0, // 0x00 |
| 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 0, 0, // 0x10 |
| 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 0, 0, 0, 0, 0, 0, // 0x20 |
| 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 0, 0, 0, 0, 0, 0, // 0x30 |
| 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0, // 0x40 |
| 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 0, 0, 0, 0, 0, 0, // 0x50 |
| 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0, 0, // 0x60 |
| 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, 0, 0, 0, 0, // 0x70 |
| 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 0, 0, 0, 0, 0, // 0x80 |
| 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0, 0, 0, 0, 0, 0, // 0x90 |
| }; |
| |
| if( resolved_var->attr & packed_no_sign_e ) |
| { |
| // This is packed decimal without a sign nybble |
| retval = 0; |
| for(size_t i=0; i<resolved_var->capacity; i++) |
| { |
| retval *= 100; |
| retval += dp2bin[resolved_location[i]]; |
| } |
| } |
| else |
| { |
| // This is packed decimal with a final sign nybble |
| retval = 0; |
| size_t imputed_length = (resolved_var->digits + 2)/2; |
| for(size_t i=0; i<imputed_length-1; i++) |
| { |
| retval *= 100; |
| retval += dp2bin[resolved_location[i]]; |
| } |
| retval *= 10; |
| retval += resolved_location[imputed_length-1]>>4; |
| if( (resolved_location[imputed_length-1]&0x0F) == 0x0D |
| || (resolved_location[imputed_length-1]&0x0F) == 0x0B ) |
| { |
| retval = -retval; |
| } |
| } |
| *rdigits = resolved_var->rdigits; |
| break; |
| } |
| } |
| |
| if( resolved_var->attr & scaled_e ) |
| { |
| // Here's where we handle a P-scaled number. |
| |
| if( resolved_var->rdigits >= 0) |
| { |
| // We might be dealing with a source with a PICTURE string of |
| // PPPPPP999, which means retval is a three-digit number |
| // and resolved_var->rdigits is +6. That means we need to divide retval |
| // by 10**9, and we need to make rdigits 9 |
| *rdigits = resolved_var->digits + resolved_var->rdigits; |
| } |
| else |
| { |
| // We have a source with a PIC string like 999PPPPPP, which is |
| // a capacity of 3 and a resolved_var->rdigits of -6. We need to multiply |
| // retval by +6, and make rdigits zero: |
| retval *= __gg__power_of_ten( -resolved_var->rdigits ); |
| *rdigits = 0; |
| } |
| } |
| |
| return retval; |
| } |
| |
| #pragma GCC diagnostic ignored "-Wformat-overflow" |
| |
| static time_t |
| cobol_time() |
| { |
| struct cbl_timespec tp; |
| __gg__clock_gettime(CLOCK_REALTIME, &tp); |
| return tp.tv_sec; |
| } |
| |
| extern "C" |
| char * |
| __gg__get_date_yymmdd() |
| { |
| char ach[32]; |
| |
| time_t t = cobol_time(); |
| const struct tm *local = localtime(&t); |
| |
| sprintf(ach, |
| "%2.2d%2.2d%2.2d", |
| local->tm_year % 100, |
| local->tm_mon+1 % 100, |
| local->tm_mday % 100 ); |
| |
| ascii_to_internal_str(ach, strlen(ach)); |
| return strdup(ach); |
| } |
| |
| extern "C" |
| char * |
| __gg__get_date_yyyymmdd() |
| { |
| char ach[32]; |
| |
| time_t t = cobol_time(); |
| const struct tm *local = localtime(&t); |
| |
| sprintf(ach, |
| "%4.4d%2.2d%2.2d", |
| local->tm_year + 1900, |
| local->tm_mon+1, |
| local->tm_mday); |
| |
| ascii_to_internal_str(ach, strlen(ach)); |
| return strdup(ach); |
| } |
| |
| extern "C" |
| char * |
| __gg__get_date_yyddd() |
| { |
| char ach[32]; |
| |
| time_t t = cobol_time(); |
| const struct tm *local = localtime(&t); |
| |
| sprintf(ach, |
| "%2.2d%3.3d", |
| local->tm_year % 100, |
| local->tm_yday+1); |
| |
| ascii_to_internal_str(ach, strlen(ach)); |
| return strdup(ach); |
| } |
| |
| extern "C" |
| char * |
| __gg__get_yyyyddd() |
| { |
| char ach[32]; |
| |
| time_t t = cobol_time(); |
| const struct tm *local = localtime(&t); |
| |
| sprintf(ach, |
| "%4.4d%3.3d", |
| local->tm_year + 1900, |
| local->tm_yday+1); |
| |
| ascii_to_internal_str(ach, strlen(ach)); |
| return strdup(ach); |
| } |
| |
| extern "C" |
| char * |
| __gg__get_date_dow() |
| { |
| char ach[32]; |
| |
| time_t t = cobol_time(); |
| const struct tm *local = localtime(&t); |
| |
| sprintf(ach, |
| "%1.1d", |
| local->tm_wday == 0 ? 7 : local->tm_wday); |
| |
| ascii_to_internal_str(ach, strlen(ach)); |
| return strdup(ach); |
| } |
| |
| static int |
| int_from_digits(const char * &p, int ndigits) |
| { |
| int retval = 0; |
| while( p && ndigits ) |
| { |
| char ch = *p++; |
| if( isdigit(ch) ) |
| { |
| retval *= 10; |
| retval += (ch & 0xF); |
| ndigits -= 1; |
| } |
| } |
| return retval; |
| } |
| |
| uint64_t |
| get_time_nanoseconds() |
| { |
| // This code was unabashedly stolen from gcc/timevar.cc. |
| // It returns the Unix epoch with nine decimal places. |
| |
| uint64_t retval = 0; |
| |
| #ifdef HAVE_CLOCK_GETTIME |
| struct timespec ts; |
| clock_gettime (CLOCK_REALTIME, &ts); |
| retval = ts.tv_sec * 1000000000 + ts.tv_nsec; |
| return retval; |
| #endif |
| #ifdef HAVE_GETTIMEOFDAY |
| struct timeval tv; |
| gettimeofday (&tv, NULL); |
| retval = tv.tv_sec * 1000000000 + tv.tv_usec * 1000; |
| return retval; |
| #endif |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__clock_gettime(clockid_t clk_id, struct cbl_timespec *tp) |
| { |
| const char *p = getenv("GCOBOL_CURRENT_DATE"); |
| |
| if( p ) |
| { |
| time_t t; |
| time (&t); |
| struct tm tm; |
| |
| memset(&tm, 0, sizeof(tm)); |
| localtime_r(&t, &tm); // This sets tm_isdst for the local time |
| |
| tm.tm_year = int_from_digits(p, 4) - 1900; |
| tm.tm_mon = int_from_digits(p, 2) - 1; |
| tm.tm_mday = int_from_digits(p, 2); |
| tm.tm_hour = int_from_digits(p, 2); |
| tm.tm_min = int_from_digits(p, 2); |
| tm.tm_sec = int_from_digits(p, 2); |
| |
| tm.tm_isdst = 0; |
| tp->tv_sec = mktime(&tm); |
| tp->tv_nsec = 0; |
| if( tm.tm_isdst ) |
| { |
| tp->tv_sec -= 3600; |
| } |
| } |
| else |
| { |
| timespec tm; |
| clock_gettime(clk_id, &tm); |
| uint64_t ns = get_time_nanoseconds(); |
| tp->tv_sec = ns/1000000000; |
| tp->tv_nsec = ns%1000000000; |
| } |
| } |
| |
| extern "C" |
| char * |
| __gg__get_date_hhmmssff() |
| { |
| char ach[32]; |
| |
| struct cbl_timespec tv; |
| __gg__clock_gettime(CLOCK_REALTIME, &tv); |
| |
| struct tm tm; |
| localtime_r(&tv.tv_sec, &tm); |
| |
| // // This routine returns local time: |
| // int day_frac = (tv.tv_sec - timezone) % 86400; |
| |
| // int hour = (day_frac / 3600); |
| // int minute = (day_frac%3600) / 60; |
| // int second = (day_frac % 60); |
| int hundredths = tv.tv_nsec/10000000; |
| |
| sprintf(ach, |
| "%2.2d%2.2d%2.2d%2.2d", |
| tm.tm_hour, |
| tm.tm_min, |
| tm.tm_sec, |
| hundredths); |
| |
| ascii_to_internal_str(ach, strlen(ach)); |
| return strdup(ach); |
| } |
| |
| extern "C" |
| int |
| __gg__setop_compare( |
| const char *candidate, |
| int capacity, |
| char *domain) |
| { |
| // This routine is called to compare the characters of 'candidate' |
| // against the list of character pairs in 'domain' |
| |
| int retval = 0; |
| int l; |
| int h; |
| char *d; |
| |
| for(int i=0; i<capacity; i++) |
| { |
| int ch = (*candidate++ & 0xFF); |
| d = domain; |
| while(*d) |
| { |
| retval = 0; |
| // We are decoding hexadecimal numbers, either in pairs, |
| // or singletons: "20/30 " or "20 ". The final one is |
| // terminated with '\0' |
| |
| // See the comments in genapi.cc::get_class_condition_string |
| // to see how this string was encoded. |
| |
| l = (int)strtoll(d, reinterpret_cast<char **>(&d), 16); |
| if( l < 0 ) |
| { |
| l = -l; |
| } |
| h = l; |
| if( *d == '/' ) |
| { |
| d += 1; |
| h = (int)strtoll(d, reinterpret_cast<char **>(&d), 16); |
| if( h < 0 ) |
| { |
| h = -h; |
| } |
| } |
| else if( *d == ' ' ) |
| { |
| d += 1; |
| } |
| if( ch >= l && ch <= h ) |
| { |
| // This character is acceptable |
| retval = 1; |
| break; |
| } |
| } |
| |
| // We checked the entire list of pairs for the candidate character |
| if( retval == 0 ) |
| { |
| // This candidate character failed, so we don't need to check |
| // the rest of the candidates |
| break; |
| } |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__dirty_to_binary_source(const char *dirty, |
| int length, |
| int *rdigits) |
| { |
| // This routine is used for converting uncontrolled strings to a |
| // a 128-bit signed binary number. |
| |
| // The string can start with a plus or minus |
| // It can contain a single embedded dot |
| // The rest of the characters have to be [0-9] |
| // Any other character, including a second dot, ends processing. |
| |
| // So, a "1ABC" will yield 1; "ABC" will yield 0. |
| |
| // It takes pointers to "s_hyphen" and "s_rdigits" so that it can |
| // report what it saw. |
| |
| // It returns the binary result. So, 1031.2 returns 10312 and s_rdigits=1 |
| |
| // The binary number, if signed, is returned as a negative number. |
| |
| __int128 retval = 0; |
| |
| int hyphen = 0; |
| *rdigits = 0; |
| |
| // Create a delta_r for counting digits to the right of |
| // any decimal point. If and when we encounter a decimal separator, |
| // we'll set this to one, otherwise it'll stay zero. |
| int delta_r = 0; |
| |
| // We now loop over the remaining input characters: |
| while( length-- >0 ) |
| { |
| char ch = *dirty++; |
| |
| if( ch == ascii_minus ) |
| { |
| hyphen = 1; |
| continue; |
| } |
| if( ch == ascii_plus ) |
| { |
| continue; |
| } |
| if( ch == __gg__decimal_point && delta_r == 0 ) |
| { |
| // This is the first decimal point we've seen, so we |
| // can start counting rdigits: |
| delta_r = 1; |
| continue; |
| } |
| if( ch < ascii_0 || ch > ascii_9 ) |
| { |
| // When we hit something that isn't a digit, then we are done |
| break; |
| } |
| retval *= 10; |
| retval += ch - ascii_0; |
| *rdigits += delta_r; |
| } |
| if( !retval ) |
| { |
| // Because the result is zero, there can't be a minus sign |
| hyphen = 0; |
| } |
| if( hyphen ) |
| { |
| // We saw a minus sign, so negate the result |
| retval = -retval; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__dirty_to_binary_internal( const char *dirty, |
| int length, |
| int *rdigits) |
| { |
| // This routine is used for converting uncontrolled strings to a |
| // a 128-bit signed binary number. |
| |
| // The string can start with a plus or minus |
| // It can contain a single embedded dot |
| // The rest of the characters have to be [0-9] |
| // Any other character, including a second dot, ends processing. |
| |
| // So, a "1ABC" will yield 1; "ABC" will yield 0. |
| |
| // It also can handle 12345E-2 notation. |
| |
| // It returns the binary result. So, 1031.2 returns 10312 and rdigits=1 |
| |
| // The binary number, if signed, is returned as a negative number. |
| |
| // We are limiting the number of digits in the number to MAX_FIXED_POINT_DIGITS |
| |
| __int128 retval = 0; |
| |
| int digit_count = 0; |
| int hyphen = 0; |
| *rdigits = 0; |
| |
| // Create a delta_r for counting digits to the right of |
| // any decimal point. If and when we encounter a decimal separator, |
| // we'll set this to one, otherwise it'll stay zero. |
| int delta_r = 0; |
| |
| // We now loop over the remaining input characters: |
| unsigned char ch = '\0'; |
| |
| if(length-- > 0) |
| { |
| ch = *dirty++; |
| if( ch == internal_minus ) |
| { |
| hyphen = 1; |
| } |
| else if( ch == internal_plus ) |
| { |
| // A plus sign is okay |
| } |
| else if( ch == ascii_to_internal(__gg__decimal_point) ) |
| { |
| delta_r = 1; |
| } |
| else if( ch >= internal_0 && ch <= internal_9 ) |
| { |
| retval = ch - internal_0 ; |
| if( retval ) |
| { |
| digit_count += 1; |
| } |
| } |
| else |
| { |
| // Because didn't start with minus, plus, a decimal_place or a digit, |
| // this isn't a number |
| length = 0; |
| ch = '\0'; |
| } |
| } |
| |
| while( length-- > 0 ) |
| { |
| ch = *dirty++; |
| if( ch == ascii_to_internal(__gg__decimal_point) && delta_r == 0 ) |
| { |
| // This is the first decimal point we've seen, so we |
| // can start counting rdigits: |
| delta_r = 1; |
| continue; |
| } |
| if( ch < internal_0 || ch > internal_9 ) |
| { |
| // When we hit something that isn't a digit, then we are done |
| break; |
| } |
| if( digit_count < MAX_FIXED_POINT_DIGITS ) |
| { |
| retval *= 10; |
| retval += ch - internal_0 ; |
| *rdigits += delta_r; |
| if( retval ) |
| { |
| digit_count += 1; |
| } |
| } |
| } |
| |
| // Let's check for an exponent: |
| if( ch == internal_E || ch == internal_e ) |
| { |
| int exponent = 0; |
| int exponent_sign = 1; |
| if( length > 0 ) |
| { |
| ch = *dirty; |
| if( ch == internal_plus) |
| { |
| length -= 1; |
| dirty += 1; |
| } |
| else if (ch == internal_minus) |
| { |
| exponent_sign = -1; |
| length -= 1; |
| dirty += 1; |
| } |
| } |
| while(length-- > 0) |
| { |
| ch = *dirty++; |
| if( ch < internal_0 || ch > internal_9 ) |
| { |
| // When we hit something that isn't a digit, then we are done |
| break; |
| } |
| exponent *= 10; |
| exponent += ch - internal_0 ; |
| } |
| exponent *= exponent_sign; |
| // We need to adjust the retval and the rdigits based on the exponent. |
| if( exponent < 0) |
| { |
| *rdigits += -exponent; |
| } |
| else if(exponent > 0) |
| { |
| if( exponent <= *rdigits ) |
| { |
| *rdigits -= exponent; |
| } |
| else |
| { |
| // Exponent is > rdigits |
| retval *= __gg__power_of_ten(exponent - *rdigits); |
| *rdigits = 0; |
| } |
| } |
| } |
| |
| if( !retval ) |
| { |
| // Because the result is zero, there can't be a minus sign |
| hyphen = 0; |
| } |
| if( hyphen ) |
| { |
| // We saw a minus sign, so negate the result |
| retval = -retval; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| GCOB_FP128 |
| __gg__dirty_to_float( const char *dirty, |
| int length) |
| { |
| // This routine is used for converting uncontrolled strings to a |
| // a _Float128 |
| |
| // The string can start with a plus or minus |
| // It can contain a single embedded dot |
| // The rest of the characters have to be [0-9] |
| // Any other character, including a second dot, ends processing. |
| |
| // So, a "1ABC" will yield 1; "ABC" will yield 0. |
| |
| // It also can handle 12345E-2 notation. |
| |
| GCOB_FP128 retval = 0; |
| |
| int rdigits = 0; |
| int hyphen = 0; |
| |
| // Create a delta_r for counting digits to the right of |
| // any decimal point. If and when we encounter a decimal separator, |
| // we'll set this to one, otherwise it'll stay zero. |
| int delta_r = 0; |
| |
| // We now loop over the remaining input characters: |
| char ch = '\0'; |
| |
| if(length-- > 0) |
| { |
| ch = *dirty++; |
| if( ch == internal_minus ) |
| { |
| hyphen = 1; |
| } |
| else if( ch == internal_plus ) |
| { |
| // A plus sign is okay |
| } |
| else if( ch == ascii_to_internal(__gg__decimal_point) ) |
| { |
| delta_r = 1; |
| } |
| else if( ch >= internal_0 && ch <= internal_9 ) |
| { |
| retval = ch - internal_0 ; |
| } |
| else |
| { |
| // Because didn't start with minus, plus, a decimal_place or a digit, |
| // this isn't a number. Set length to zero to prevent additional |
| // processing |
| length = 0; |
| ch = '\0'; |
| } |
| } |
| |
| while( length-- > 0 ) |
| { |
| ch = *dirty++; |
| if( ch == ascii_to_internal(__gg__decimal_point) && delta_r == 0 ) |
| { |
| // This is the first decimal point we've seen, so we |
| // can start counting rdigits: |
| delta_r = 1; |
| continue; |
| } |
| if( ch < internal_0 || ch > internal_9 ) |
| { |
| // When we hit something that isn't a digit, then we are done |
| break; |
| } |
| retval *= 10; |
| retval += ch - internal_0 ; |
| rdigits += delta_r; |
| } |
| |
| // Let's check for an exponent: |
| int exponent = 0; |
| if( ch == internal_E || ch == internal_e ) |
| { |
| int exponent_sign = 1; |
| if( length > 0 ) |
| { |
| ch = *dirty; |
| if( ch == internal_plus) |
| { |
| length -= 1; |
| dirty += 1; |
| } |
| else if (ch == internal_minus) |
| { |
| exponent_sign = -1; |
| length -= 1; |
| dirty += 1; |
| } |
| } |
| while(length-- > 0) |
| { |
| ch = *dirty++; |
| if( ch < internal_0 || ch > internal_9 ) |
| { |
| // When we hit something that isn't a digit, then we are done |
| break; |
| } |
| exponent *= 10; |
| exponent += ch - internal_0 ; |
| } |
| exponent *= exponent_sign; |
| } |
| |
| // We need to adjust the retval based on rdigits and exponent. |
| // Notice that 123.45E2 properly comes out to be 12345 |
| if( exponent - rdigits >= 0 ) |
| { |
| retval *= __gg__power_of_ten(exponent - rdigits); |
| } |
| else |
| { |
| retval /= __gg__power_of_ten(rdigits - exponent); |
| } |
| |
| if( !retval ) |
| { |
| // Because the result is zero, there can't be a minus sign |
| hyphen = 0; |
| } |
| if( hyphen ) |
| { |
| // We saw a minus sign, so negate the result |
| retval = -retval; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__get_integer_binary_value(cblc_field_t *var) |
| { |
| // This routine is called when a rounded integer is needed |
| |
| __int128 retval; |
| |
| int rdigits; |
| |
| retval = __gg__binary_value_from_field(&rdigits, var); |
| |
| while( rdigits-- > 1) |
| { |
| retval /= 10; |
| } |
| |
| if( rdigits-- == 1) |
| { |
| if( retval < 0 ) |
| { |
| retval -= 5; |
| } |
| else |
| { |
| retval += 5; |
| } |
| retval /= 10; |
| } |
| |
| return retval; |
| } |
| |
| static |
| void psz_to_internal(char *psz) |
| { |
| char *p = psz; |
| while( *p ) |
| { |
| *p = ascii_to_internal(*p); |
| p += 1; |
| } |
| } |
| |
| static int |
| get_scaled_rdigits(const cblc_field_t *field) |
| { |
| int retval; |
| if( !(field->attr & scaled_e) ) |
| { |
| // The value is not P-scaled, so we just use the unchanged rdigits value |
| retval = field->rdigits; |
| } |
| else |
| { |
| if( field->rdigits < 0 ) |
| { |
| // The PIC string was something like 999PPPP, which means an rdigits value |
| // of -4. We return zero; somebody else will have the job of multiplying |
| // the three significant digits by 10^4 to get the magnitude correct. |
| retval = 0; |
| } |
| else |
| { |
| // The PIC string was something like PPPP999, which means an rdigits value |
| // of +4. We return an rdigits value of 4 + 3 = 7, which will mean that |
| // the three significant digits will be scaled to 0.0000999 |
| retval = field->digits + field->rdigits; |
| } |
| } |
| return retval; |
| } |
| |
| static char * |
| format_for_display_internal(char **dest, |
| size_t *dest_size, |
| cblc_field_t *var, |
| unsigned char *actual_location, |
| int actual_length, |
| int address_of) |
| { |
| // dest and dest_size represent a malloced buffer of dest_size. |
| |
| // This routine will put the formatted result into dest if it fits, and |
| // realloc dest if it doesn't. The new_size goes into the dest_size |
| // reference. Used properly, the caller's buffer just keeps getting bigger |
| // as necessary, cutting down on the number of reallocations needed. |
| |
| int source_rdigits = var->rdigits; |
| |
| if( var->attr & scaled_e ) |
| { |
| source_rdigits = 0; |
| } |
| |
| // Special case, when var->address_of is on |
| |
| if( address_of ) |
| { |
| // Assume that DISPLAY OF ADDRESS OF should be what's expected: |
| |
| __gg__realloc_if_necessary(dest, dest_size, 2*sizeof(void *) + 1); |
| |
| sprintf( *dest, |
| "0x%*.*lx", |
| (int)(2*sizeof(void *)), |
| (int)(2*sizeof(void *)), |
| (unsigned long)actual_location); |
| ascii_to_internal_str(*dest, strlen(*dest)); |
| |
| goto done; |
| } |
| |
| switch( var->type ) |
| { |
| case FldLiteralA: |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldNumericEdited: |
| case FldAlphaEdited: |
| __gg__realloc_if_necessary(dest, dest_size, actual_length+1); |
| if( actual_location ) |
| { |
| memcpy(*dest, actual_location, actual_length); |
| } |
| else |
| { |
| fprintf(stderr, "attempting to display a NULL pointer in %s\n", var->name); |
| abort(); |
| //memset(*dest, internal_query, actual_length); |
| //memcpy(*dest, actual_location, actual_length); |
| } |
| (*dest)[actual_length] = NULLCH; |
| break; |
| |
| case FldNumericDisplay: |
| { |
| // We are going to make use of fact that a NumericDisplay's data is |
| // almost already in the format we need. We have to add a decimal point, |
| // if necessary, in the right place, and we need to tack on leading or |
| // trailing zeroes for PPP999 and 999PPP scaled-e variables. |
| |
| if( var_is_refmod(var) ) |
| { |
| __gg__realloc_if_necessary(dest, dest_size, actual_length+1); |
| memcpy((*dest), actual_location, actual_length); |
| (*dest)[actual_length] = NULLCH; |
| break; |
| } |
| |
| const unsigned char *running_location = actual_location; |
| |
| // We need the counts of digits to the left and right of the decimal point |
| int rdigits = get_scaled_rdigits(var); |
| int ldigits = var->digits - rdigits; |
| |
| // Calculate the minimum allocated size we need, keeping in mind that |
| // ldigits can be negative when working with a PPP999 number |
| int nsize = std::max(ldigits,0) + rdigits+1; |
| if( ldigits < 0 ) |
| { |
| // We are dealing with a scaled_e number |
| rdigits += ldigits; |
| } |
| |
| if( rdigits ) |
| { |
| // We need room for the inside decimal point |
| nsize += 1; |
| } |
| if( var->attr & signable_e ) |
| { |
| // We need room for a leading or trailing sign in the output |
| nsize += 1; |
| } |
| |
| // nsize is now the actual number of bytes we need in the destination |
| __gg__realloc_if_necessary(dest, dest_size, nsize); |
| |
| if( actual_location ) |
| { |
| int index = 0; // This is the running index into our output destination |
| if( var->attr & signable_e ) |
| { |
| if( var->attr & separate_e ) |
| { |
| // We are dealing with a sign character maintained separately in |
| // the data. |
| if( var->attr & leading_e ) |
| { |
| // The first character is the sign character |
| (*dest)[index++] = *running_location++; |
| } |
| } |
| else |
| { |
| // The sign character is not separate. It's in either the first |
| // or last byte of the data: |
| size_t sign_location = var->attr & leading_e ? 0 : actual_length-1 ; |
| if( is_sign_bit_on( actual_location[sign_location]) ) |
| { |
| (*dest)[index++] = internal_minus; |
| } |
| else |
| { |
| (*dest)[index++] = internal_plus; |
| } |
| } |
| } |
| |
| {//xxx |
| // copy over the characters to the left of the decimal point: |
| for(int i=0; i<ldigits; i++ ) |
| { |
| char ch = *running_location++; |
| |
| // The default HIGH-VALUE of 0xFF runs afoul of the |
| // NumericDisplay sign bit 0f 0x40 when running in |
| // ASCII mode. The following test handles that problem |
| // when HIGH-VALUE is still 0xFF. That HIGH-VALUE can |
| // be changed by the SPECIAL-NAMES ALPHABET clause. But |
| |
| // I have decided that the onus of that problem is on |
| // the user. |
| if( (*dest)[index-1] != (char)DEGENERATE_HIGH_VALUE ) |
| { |
| turn_sign_bit_off( PTRCAST(unsigned char, &ch)); |
| } |
| (*dest)[index++] = ch; |
| } |
| if( rdigits ) |
| { |
| // Lay down a decimal point |
| (*dest)[index++] = ascii_to_internal(__gg__decimal_point); |
| |
| if( ldigits < 0 ) |
| { |
| // This is a scaled_e value, and we need that many zeroes: |
| for( int i=0; i<-ldigits; i++ ) |
| { |
| (*dest)[index++] = internal_zero; |
| } |
| } |
| |
| // And the digits to the right |
| for(int i=0; i<rdigits; i++ ) |
| { |
| char ch = *running_location++; |
| if( (*dest)[index-1] != (char)DEGENERATE_HIGH_VALUE ) |
| { |
| turn_sign_bit_off(PTRCAST(unsigned char, &ch)); |
| } |
| (*dest)[index++] = ch; |
| } |
| } |
| } |
| // At this point, for a 999PPP number, we need to tack on the zeroes |
| if( var->rdigits < 0 ) |
| { |
| for(int i=0; i < -(var->rdigits); i++) |
| { |
| (*dest)[index++] = internal_zero; |
| } |
| } |
| |
| if( var->attr & signable_e |
| && var->attr & separate_e |
| && !(var->attr & leading_e) ) |
| { |
| (*dest)[index++] = actual_location[actual_length-1]; |
| } |
| |
| (*dest)[index++] = NULLCH; |
| } |
| else |
| { |
| fprintf(stderr, "attempting to display a NULL pointer in %s\n", var->name); |
| abort(); |
| // memset(*dest, internal_query, nsize-1); |
| // (*dest)[nsize] = NULLCH; |
| } |
| } |
| break; |
| |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| { |
| int dummy; |
| int digits; |
| __int128 value = get_binary_value_local(&dummy, |
| var, |
| actual_location, |
| actual_length); |
| // Perhaps weirdly, for a p-scaled value with negative rdigits, |
| // value has been scaled by 10**(-rdigits), because for most purposes, |
| // get_binary_value_local should be the actual value. But for display, |
| // we need to bring it back down by that factor, because down below we |
| // will be adding zeroes to the right. This is a tug-of-war between |
| // USAGE DISPLAY and other USAGES: |
| |
| if( (var->attr & scaled_e) && var->rdigits < 0 ) |
| { |
| value = __gg__scale_by_power_of_ten_2(value, var->rdigits); |
| } |
| |
| if( var->digits ) |
| { |
| digits = var->digits; |
| } |
| else |
| { |
| // USAGE BINARY comes through without a digits value. Force it based on |
| // the capacity: |
| switch( var->capacity ) |
| { |
| case 1: |
| digits = 3; |
| break; |
| case 2: |
| digits = 5; |
| break; |
| case 4: |
| digits = 10; |
| break; |
| case 8: |
| digits = 19; |
| break; |
| case 16: |
| digits = 38; |
| break; |
| default: |
| warnx("%s(): %s has capacity %ld\n", |
| __func__, |
| var->name, |
| var->capacity); |
| abort(); |
| break; |
| } |
| } |
| |
| char ach[128]; |
| __gg__binary_to_string_internal(ach, digits, value); |
| |
| // And copy the code from up above: |
| int nsize = digits+1; |
| int index = 0; |
| if( source_rdigits ) |
| { |
| // We need room for the inside decimal point |
| nsize += 1; |
| } |
| if( var->attr & signable_e ) |
| { |
| // We need room for the leading sign |
| nsize += 1; |
| } |
| __gg__realloc_if_necessary(dest, dest_size, nsize); |
| |
| if( var->attr & signable_e ) |
| { |
| if( value < 0 ) |
| { |
| (*dest)[index++] = internal_minus; |
| } |
| else |
| { |
| (*dest)[index++] = internal_plus; |
| } |
| } |
| // copy over the characters to the left of the decimal point: |
| memcpy((*dest)+index, ach, digits - source_rdigits); |
| index += digits - source_rdigits; |
| if( source_rdigits ) |
| { |
| (*dest)[index++] = ascii_to_internal(__gg__decimal_point); |
| memcpy((*dest)+index, ach+(digits-source_rdigits), source_rdigits); |
| index += source_rdigits; |
| } |
| (*dest)[index++] = NULLCH ; |
| } |
| break; |
| |
| case FldIndex: |
| { |
| // The display of a FldIndex doesn't need to provide clues about its |
| // length, so don't bother with leading zeroes. |
| int dummy; |
| __int128 value = get_binary_value_local(&dummy, |
| var, |
| actual_location, |
| actual_length); |
| char ach[64]; |
| sprintf(ach, "%lu", (unsigned long)value); |
| __gg__realloc_if_necessary(dest, dest_size, strlen(ach)+1); |
| strcpy(*dest, ach); |
| } |
| break; |
| |
| case FldClass: |
| { |
| if( var->level != LEVEL88 ) |
| { |
| size_t retsize = MINIMUM_ALLOCATION_SIZE; |
| memset(*dest, 0, retsize); |
| strcpy(*dest, "<CLASS>"); |
| } |
| else |
| { |
| // This is a LEVEL 88 variable |
| size_t retsize = MINIMUM_ALLOCATION_SIZE; |
| memset(*dest, 0, retsize); |
| strcpy(*dest, "<LEVEL88>"); |
| } |
| |
| break; |
| } |
| |
| case FldPointer: |
| { |
| int digits; |
| __int128 value = get_binary_value_local( &digits, |
| var, |
| actual_location, |
| actual_length); |
| __gg__realloc_if_necessary(dest, dest_size, 2*sizeof(void *) + 3); |
| |
| sprintf( *dest, |
| "0x%*.*lx", |
| (int)(2*sizeof(void *)), |
| (int)(2*sizeof(void *)), |
| (unsigned long)value); |
| ascii_to_internal_str(*dest, strlen(*dest)); |
| break; |
| } |
| |
| case FldFloat: |
| { |
| switch(var->capacity) |
| { |
| case 4: |
| { |
| // We will convert based on the fact that for float32, any seven-digit |
| // number converts to float32 and then back again unchanged. |
| |
| // We will also format numbers so that we produce 0.01 and 1E-3 on the low |
| // side, and 9999999 and then 1E+7 on the high side |
| // 10,000,000 = 1E7 |
| char ach[64]; |
| _Float32 floatval = *PTRCAST(_Float32, actual_location); |
| strfromf32(ach, sizeof(ach), "%.9E", floatval); |
| char *p = strchr(ach, 'E'); |
| if( !p ) |
| { |
| // Probably INF -INF NAN or -NAN, so ach has our result |
| } |
| else |
| { |
| p += 1; |
| int exp = atoi(p); |
| if( exp >= 6 || exp <= -5 ) |
| { |
| // We are going to stick with the E notation, so ach has our result |
| } |
| else |
| { |
| // We are going to produce our number in such a way that we specify |
| // seven signicant digits, no matter where the decimal point lands. |
| // Note that exp is in the range of 6 to -2 |
| |
| int precision = 9 - exp; |
| sprintf(ach, "%.*f", precision, (double)floatval ); |
| } |
| __gg__remove_trailing_zeroes(ach); |
| __gg__realloc_if_necessary(dest, dest_size, strlen(ach)+1); |
| } |
| psz_to_internal(ach); |
| strcpy(*dest, ach); |
| break; |
| } |
| |
| case 8: |
| { |
| // We will convert based on the fact that for float32, any 15-digit |
| // number converts to float64 and then back again unchanged. |
| |
| // We will also format numbers so that we produce 0.01 and 1E-3 on the low |
| // side, and 9999999 and then 1E+15 on the high side |
| char ach[64]; |
| _Float64 floatval = *PTRCAST(_Float64, actual_location); |
| strfromf64(ach, sizeof(ach), "%.17E", floatval); |
| char *p = strchr(ach, 'E'); |
| if( !p ) |
| { |
| // Probably INF -INF NAN or -NAN, so ach has our result |
| } |
| else |
| { |
| p += 1; |
| int exp = atoi(p); |
| if( exp >= 6 || exp <= -5 ) |
| { |
| // We are going to stick with the E notation, so ach has our result |
| } |
| else |
| { |
| // We are going to produce our number in such a way that we specify |
| // seven signicant digits, no matter where the decimal point lands. |
| // Note that exp is in the range of 6 to -2 |
| |
| int precision = 17 - exp; |
| |
| sprintf(ach, "%.*f", precision, (double)floatval ); |
| } |
| __gg__remove_trailing_zeroes(ach); |
| __gg__realloc_if_necessary(dest, dest_size, strlen(ach)+1); |
| } |
| psz_to_internal(ach); |
| strcpy(*dest, ach); |
| break; |
| } |
| |
| case 16: |
| { |
| // We will convert based on the fact that for float32, any 15-digit |
| // number converts to float64 and then back again unchanged. |
| |
| // We will also format numbers so that we produce 0.01 and 1E-3 on the low |
| // side, and 9999999 and then 1E+15 on the high side |
| char ach[128]; |
| // We can't use *(_Float64 *)actual_location; |
| // That uses the SSE registers, which won't work if the source isn't |
| // on a 16-bit boundary. |
| GCOB_FP128 floatval; |
| memcpy(&floatval, actual_location, 16); |
| strfromfp128(ach, sizeof(ach), "%.36" FP128_FMT "E", floatval); |
| char *p = strchr(ach, 'E'); |
| if( !p ) |
| { |
| // Probably INF -INF NAN or -NAN, so ach has our result |
| } |
| else |
| { |
| p += 1; |
| int exp = atoi(p); |
| if( exp >= 6 || exp <= -5 ) |
| { |
| // We are going to stick with the E notation, so ach has our result |
| } |
| else |
| { |
| // We are going to produce our number in such a way that we specify |
| // seven signicant digits, no matter where the decimal point lands. |
| // Note that exp is in the range of 6 to -2 |
| |
| int precision = 36 - exp; |
| char achFormat[24]; |
| sprintf(achFormat, "%%.%d" FP128_FMT "f", precision); |
| strfromfp128(ach, sizeof(ach), achFormat, floatval); |
| } |
| __gg__remove_trailing_zeroes(ach); |
| __gg__realloc_if_necessary(dest, dest_size, strlen(ach)+1); |
| } |
| |
| psz_to_internal(ach); |
| strcpy(*dest, ach); |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| fprintf(stderr, |
| "Unknown conversion %d in format_for_display_internal\n", |
| var->type ); |
| abort(); |
| break; |
| } |
| |
| if( var->attr & scaled_e && var->type != FldNumericDisplay ) |
| { |
| static size_t buffer_size = MINIMUM_ALLOCATION_SIZE; |
| static char *buffer = static_cast<char *>(malloc(buffer_size)); |
| massert(buffer); |
| if( var->rdigits > 0) |
| { |
| // We have something like 123 or +123. We need to insert a decimal |
| // point and a rdigits zeroes to make it +.000000123 |
| |
| size_t new_length = strlen(*dest) + var->rdigits + 1 + 1; |
| __gg__realloc_if_necessary(&buffer, &buffer_size, new_length); |
| |
| |
| memset(buffer, internal_0, new_length); |
| char *p = buffer; |
| char *s = *dest; |
| if( ((*dest)[0]&0xFF) < internal_0 |
| || ((*dest)[0]&0xFF) > internal_9 ) |
| { |
| *p++ = (*dest)[0]; |
| s += 1; |
| } |
| *p++ = ascii_to_internal(__gg__decimal_point); |
| p += var->rdigits; // Skip over the zeroes |
| strcpy(p, s); |
| |
| __gg__realloc_if_necessary(dest, dest_size, new_length); |
| strcpy(*dest, buffer); |
| } |
| else // var->rdigits < 0 |
| { |
| // We have something like 123 or +123. All we need to do is |
| // add zeroes to the end: |
| size_t new_length = strlen(*dest) + -var->rdigits + 1; |
| __gg__realloc_if_necessary(&buffer, &buffer_size, new_length); |
| |
| memset(buffer, internal_0, new_length); |
| buffer[new_length-1] = NULLCH; |
| memcpy(buffer, *dest, strlen(*dest)); |
| |
| __gg__realloc_if_necessary(dest, dest_size, new_length); |
| strcpy(*dest, buffer); |
| } |
| } |
| |
| if( var->type == FldNumericBin5 && (var->attr & intermediate_e) ) |
| { |
| // Because this is a intermediate Bin5, let's strip off leading zeroes. |
| // |
| // Because we don't know what we are dealing with, we created a 38-digit |
| // number with a variable number of rdigits. So, we usually have a boatload |
| // of leading zeroes. I find that display offensive, so let's fix it: |
| unsigned char *p1 = (unsigned char *)(*dest); |
| if( *p1 == internal_plus || *p1 == internal_minus ) |
| { |
| p1 += 1; |
| } |
| unsigned char *p2 = p1; |
| while( p2[0] == internal_zero && p2[1] != '\0' ) |
| { |
| p2 += 1; |
| } |
| strcpy(PTRCAST(char, p1), PTRCAST(char, p2)); |
| } |
| |
| done: |
| return *dest; |
| } |
| |
| static char * |
| format_for_display_local( char **dest, |
| size_t *dest_size, |
| cblc_field_t *var, |
| size_t var_offset, |
| size_t var_size, |
| int var_flags) |
| { |
| if(var) |
| { |
| // At this point, format the entire length. It's up to our caller to |
| // trim it further, because this routine is used by both receivers and |
| // senders |
| format_for_display_internal(dest, |
| dest_size, |
| var, |
| var->data + var_offset, |
| var_size, |
| var_flags & REFER_T_ADDRESS_OF); |
| } |
| else |
| { |
| **dest = '\0'; |
| } |
| return *dest; |
| } |
| |
| static int |
| compare_88( const char *list, |
| const char *list_e, |
| bool fig_const, |
| cblc_field_t * /*conditional*/, |
| unsigned char *conditional_location, |
| int conditional_length) |
| { |
| int list_len = (int)(list_e-list); |
| int test_len; |
| char *test; |
| if( fig_const ) |
| { |
| // We are working with a figurative constant |
| |
| test = static_cast<char *>(malloc(conditional_length)); |
| massert(test); |
| test_len = conditional_length; |
| // This is where we handle the zero-length strings that |
| // nonetheless can magically be expanded into figurative |
| // constants: |
| |
| int ch = internal_space; |
| // Check for the strings starting with 0xFF whose second character |
| // indicates a figurative constant: |
| if( list[0] == ascii_Z ) |
| { |
| ch = internal_zero; |
| } |
| else if( list[0] == ascii_H ) |
| { |
| if( __gg__high_value_character == DEGENERATE_HIGH_VALUE ) |
| { |
| ch = __gg__high_value_character; |
| } |
| else |
| { |
| ch = ascii_to_internal(__gg__high_value_character); |
| } |
| } |
| else if( list[0] == ascii_Q ) |
| { |
| ch = ascii_to_internal(__gg__quote_character); |
| } |
| else if( list[0] == ascii_L ) |
| { |
| ch = ascii_to_internal(__gg__low_value_character); |
| } |
| memset( test, ch, conditional_length ); |
| } |
| else if( list_len < conditional_length ) |
| { |
| // 'list' is too short; we have to right-fill with spaces: |
| test = static_cast<char *>(malloc(conditional_length)); |
| massert(test); |
| test_len = conditional_length; |
| memset(test, internal_space, conditional_length); |
| memcpy(test, list, list_len); |
| } |
| else |
| { |
| test = static_cast<char *>(malloc(list_len)); |
| massert(test); |
| test_len = list_len; |
| memcpy(test, list, list_len); |
| } |
| |
| int cmpval; |
| |
| if( test[0] == NULLCH && conditional_location[0] == 0) |
| { |
| cmpval = 0; |
| } |
| else |
| { |
| cmpval = cstrncmp (test, |
| PTRCAST(char, conditional_location), |
| conditional_length); |
| if( cmpval == 0 && (int)strlen(test) != conditional_length ) |
| { |
| // When strncmp returns 0, the actual smaller string is the |
| // the shorter of the two: |
| cmpval = test_len - conditional_length; |
| } |
| } |
| |
| free(test); |
| |
| if( cmpval < 0 ) |
| { |
| cmpval = -1; |
| } |
| else if(cmpval > 0) |
| { |
| cmpval = +1; |
| } |
| return cmpval; |
| } |
| |
| static GCOB_FP128 |
| get_float128( const cblc_field_t *field, |
| unsigned char *location ) |
| { |
| GCOB_FP128 retval=0; |
| if(field->type == FldFloat ) |
| { |
| switch( field->capacity ) |
| { |
| case 4: |
| retval = *PTRCAST(_Float32 , location); |
| break; |
| case 8: |
| retval = *PTRCAST(_Float64 , location); |
| break; |
| case 16: |
| // retval = *(_Float128 *)location; doesn't work, because the SSE |
| // registers need the source on a 16-byte boundary, and we can't |
| // guarantee that. |
| memcpy(&retval, location, 16); |
| break; |
| } |
| } |
| else if( field->type == FldLiteralN ) |
| { |
| if( __gg__decimal_point == '.' ) |
| { |
| retval = strtofp128(field->initial, NULL); |
| } |
| else |
| { |
| // We need to replace any commas with periods |
| static size_t size = 128; |
| static char *buffer = static_cast<char *>(malloc(size)); |
| while( strlen(field->initial)+1 > size ) |
| { |
| size *= 2; |
| buffer = static_cast<char *>(malloc(size)); |
| } |
| massert(buffer); |
| strcpy(buffer, field->initial); |
| char *p = strchr(buffer, ','); |
| if(p) |
| { |
| *p = '.'; |
| } |
| retval = strtofp128(buffer, NULL); |
| } |
| } |
| else |
| { |
| fprintf(stderr, "What's all this then?\n"); |
| abort(); |
| } |
| return retval; |
| } |
| |
| static |
| int |
| compare_field_class(cblc_field_t *conditional, |
| unsigned char *conditional_location, |
| int conditional_length, |
| cblc_field_t *list) |
| { |
| int retval = 1; // Zero means equal |
| __int128 value; |
| int rdigits; |
| |
| // list->initial points to a superstring: a double-null terminated |
| // string containing pairs of strings. We are looking for equality. |
| |
| switch( conditional->type ) |
| { |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldIndex: |
| { |
| value = get_binary_value_local (&rdigits, |
| conditional, |
| conditional_location, |
| conditional_length); |
| const char *walker = list->initial; |
| while(*walker) |
| { |
| char left_flag; |
| size_t left_len; |
| char * left; |
| |
| char right_flag; |
| size_t right_len; |
| char * right; |
| |
| char *pend; |
| left_len = strtoull(walker, &pend, 10); |
| left_flag = *pend; |
| left = pend+1; |
| |
| right = left + left_len; |
| right_len = strtoull(right, &pend, 10); |
| right_flag = *pend; |
| right = pend+1; |
| |
| walker = right + right_len; |
| |
| int left_rdigits; |
| int right_rdigits; |
| |
| __int128 left_value; |
| if( left_flag == 'F' && left[0] == 'Z' ) |
| { |
| left_value = 0; |
| left_rdigits = 0; |
| } |
| else |
| { |
| left_value = __gg__dirty_to_binary_internal( |
| left, |
| left_len, |
| &left_rdigits); |
| } |
| |
| __int128 right_value; |
| if( right_flag == 'F' && right[0] == 'Z' ) |
| { |
| right_value = 0; |
| right_rdigits = 0; |
| } |
| else |
| { |
| right_value = __gg__dirty_to_binary_internal( |
| right, |
| right_len, |
| &right_rdigits); |
| } |
| |
| // Normalize all three numbers to the same rdigits |
| int max = std::max(rdigits, left_rdigits); |
| max = std::max(max, right_rdigits); |
| |
| if( max > rdigits ) |
| { |
| value *= __gg__power_of_ten(max - rdigits); |
| } |
| if( max > left_rdigits ) |
| { |
| left_value *= __gg__power_of_ten(max - left_rdigits); |
| } |
| if( max > right_rdigits ) |
| { |
| right_value *= __gg__power_of_ten(max - right_rdigits); |
| } |
| if( left_value <= value && value <= right_value ) |
| { |
| retval = 0; |
| break; |
| } |
| } |
| break; |
| } |
| |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldLiteralA: |
| { |
| char *walker = list->initial; |
| while(*walker) |
| { |
| bool fig1; |
| bool fig2; |
| char *first; |
| char *last; |
| char *first_e; |
| char *last_e; |
| size_t first_len; |
| size_t last_len; |
| |
| char *pend; |
| |
| first = walker; |
| first_len = strtoull(first, &pend, 10); |
| fig1 = *pend == 'F'; |
| first = pend+1; |
| first_e = first + first_len; |
| |
| last = first_e; |
| |
| last_len = strtoull(last, &pend, 10); |
| fig2 = *pend == 'F'; |
| last = pend+1; |
| last_e = last + last_len; |
| |
| walker = last_e; |
| |
| int compare_result; |
| |
| compare_result = compare_88(first, |
| first_e, |
| fig1, |
| conditional, |
| conditional_location, |
| conditional_length); |
| if( compare_result > 0 ) |
| { |
| // First is > conditional, so this is no good |
| continue; |
| } |
| compare_result = compare_88(last, |
| last_e, |
| fig2, |
| conditional, |
| conditional_location, |
| conditional_length); |
| if( compare_result < 0 ) |
| { |
| // Last is < conditional, so this is no good |
| continue; |
| } |
| |
| // conditional is inclusively between first and last |
| retval = 0; |
| break; |
| } |
| break; |
| } |
| |
| case FldFloat: |
| { |
| GCOB_FP128 fp128 = get_float128(conditional, conditional_location) ; |
| const char *walker = list->initial; |
| while(*walker) |
| { |
| char left_flag; |
| size_t left_len; |
| char * left; |
| |
| char right_flag; |
| size_t right_len; |
| char * right; |
| |
| char *pend; |
| left_len = strtoull(walker, &pend, 10); |
| left_flag = *pend; |
| left = pend+1; |
| |
| right = left + left_len; |
| right_len = strtoull(right, &pend, 10); |
| right_flag = *pend; |
| right = pend+1; |
| |
| walker = right + right_len; |
| |
| GCOB_FP128 left_value; |
| if( left_flag == 'F' && left[0] == 'Z' ) |
| { |
| left_value = 0; |
| } |
| else |
| { |
| left_value = __gg__dirty_to_float(left, |
| left_len); |
| } |
| |
| GCOB_FP128 right_value; |
| if( right_flag == 'F' && right[0] == 'Z' ) |
| { |
| right_value = 0; |
| } |
| else |
| { |
| right_value = __gg__dirty_to_float( right, |
| right_len); |
| } |
| |
| if( left_value <= fp128 && fp128 <= right_value ) |
| { |
| retval = 0; |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| printf( "%s(): doesn't know what to do with %s\n", |
| __func__, |
| conditional->name); |
| abort(); |
| } |
| |
| return retval; |
| } |
| |
| static |
| bool |
| local_is_numeric(int type, bool address_of) |
| { |
| bool retval; |
| if( address_of ) |
| { |
| retval = true; |
| } |
| else |
| { |
| switch(type) |
| { |
| case FldNumericDisplay: |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldIndex: |
| case FldPointer: |
| case FldLiteralN: |
| case FldFloat: |
| retval = true; |
| break; |
| default: |
| retval = false; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| static |
| bool |
| local_is_alpha(int type, bool address_of) |
| { |
| bool retval; |
| if( address_of ) |
| { |
| retval = false; |
| } |
| else |
| { |
| switch(type) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldAlphaEdited: |
| case FldNumericEdited: |
| case FldLiteralA: |
| retval = true; |
| break; |
| default: |
| retval = false; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| static |
| int |
| compare_strings(const char *left_string, |
| size_t left_length, |
| bool left_all, |
| const char *right_string, |
| size_t right_length, |
| bool right_all) |
| { |
| int retval = 0; |
| size_t i = 0; |
| |
| if( right_all && right_length > left_length ) |
| { |
| // In the rubber-bandy ALL situation, and the ALL is longer than the |
| // fixed side, we just compare the characters of the fixed side: |
| right_length = left_length; |
| } |
| |
| if( left_all && left_length > right_length ) |
| { |
| left_length = right_length; |
| } |
| |
| while( !retval && i<left_length && i<right_length ) |
| { |
| retval = collated((unsigned char)left_string[i]) |
| - collated((unsigned char)right_string[i]); |
| i += 1; |
| } |
| |
| // We need to space-extend the shorter value. That's because |
| // "Bob" is equal to "Bob " |
| if( !right_all ) |
| { |
| while( !retval && i<left_length ) |
| { |
| retval = collated((unsigned char)left_string[i]) |
| - collated(internal_space); |
| i += 1; |
| } |
| } |
| else |
| { |
| // In an ALL situation where the ALL is shorter than the fixed side, we |
| // wrap around the ALL characters |
| while( !retval && i<left_length ) |
| { |
| retval = collated((unsigned char)left_string[i]) |
| - collated((unsigned char)right_string[i%right_length]); |
| i += 1; |
| } |
| } |
| |
| if( !left_all ) |
| { |
| while( !retval && i<right_length ) |
| { |
| retval = collated(internal_space) |
| - collated((unsigned char)right_string[i]); |
| i += 1; |
| } |
| } |
| else |
| { |
| if( left_length > right_length ) |
| { |
| left_length = right_length; |
| } |
| while( !retval && i<right_length ) |
| { |
| retval = collated((unsigned char)left_string[i%left_length]) |
| - collated((unsigned char)right_string[i]); |
| i += 1; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__compare_2(cblc_field_t *left_side, |
| unsigned char *left_location, |
| size_t left_length, |
| int left_attr, |
| int left_flags, |
| cblc_field_t *right_side, |
| unsigned char *right_location, |
| size_t right_length, |
| int right_attr, |
| int right_flags, |
| int second_time_through) |
| { |
| // First order of business: If right_side is a FldClass, pass that off |
| // to the speciality squad: |
| |
| if( right_side->type == FldClass ) |
| { |
| return compare_field_class( left_side, |
| left_location, |
| left_length, |
| right_side); |
| } |
| |
| // Serene in our conviction that the left_side isn't a FldClass, we |
| // move on. |
| |
| // Extract the individual flags from the flag words: |
| bool left_all = !!(left_flags & REFER_T_MOVE_ALL ); |
| bool left_address_of = !!(left_flags & REFER_T_ADDRESS_OF); |
| bool right_all = !!(right_flags & REFER_T_MOVE_ALL ); |
| bool right_address_of = !!(right_flags & REFER_T_ADDRESS_OF); |
| //bool left_refmod = !!(left_flags & REFER_T_REFMOD ); |
| bool right_refmod = !!(right_flags & REFER_T_REFMOD ); |
| |
| // Figure out if we have any figurative constants |
| cbl_figconst_t left_figconst = (cbl_figconst_t)(left_attr & FIGCONST_MASK); |
| cbl_figconst_t right_figconst = (cbl_figconst_t)(right_attr & FIGCONST_MASK); |
| |
| unsigned int fig_left = 0; |
| unsigned int fig_right = 0; |
| |
| switch(left_figconst) |
| { |
| case normal_value_e : |
| fig_left = 0; |
| break; |
| case low_value_e : |
| fig_left = ascii_to_internal(__gg__low_value_character); |
| break; |
| case zero_value_e : |
| fig_left = internal_zero; |
| break; |
| case space_value_e : |
| fig_left = internal_space; |
| break; |
| case quote_value_e : |
| fig_left = ascii_to_internal(__gg__quote_character); |
| break; |
| case high_value_e : |
| if( __gg__high_value_character == DEGENERATE_HIGH_VALUE ) |
| { |
| fig_left = __gg__high_value_character; |
| } |
| else |
| { |
| fig_left = ascii_to_internal(__gg__high_value_character); |
| } |
| break; |
| case null_value_e: |
| break; |
| } |
| switch(right_figconst) |
| { |
| case normal_value_e : |
| fig_right = 0; |
| break; |
| case low_value_e : |
| fig_right = ascii_to_internal(__gg__low_value_character); |
| break; |
| case zero_value_e : |
| fig_right = internal_zero; |
| break; |
| case space_value_e : |
| fig_right = internal_space; |
| break; |
| case quote_value_e : |
| fig_right = ascii_to_internal(__gg__quote_character); |
| break; |
| case high_value_e : |
| if( __gg__high_value_character == DEGENERATE_HIGH_VALUE ) |
| { |
| fig_right = __gg__high_value_character; |
| } |
| else |
| { |
| fig_right = ascii_to_internal(__gg__high_value_character); |
| } |
| break; |
| case null_value_e: |
| break; |
| } |
| |
| // We have four high-level conditions to consider: |
| int retval = 0; |
| bool compare = false; |
| |
| if( left_figconst && right_figconst ) |
| { |
| // We are comparing two figurative constants |
| retval = collated(fig_left) - collated(fig_right); |
| compare = true; |
| goto fixup_retval; |
| } |
| if( left_figconst && !right_figconst ) |
| { |
| // Go directly to fixup_retval. Because 'compare' is false, we'll |
| // end up trying again with the variables swapped: |
| goto fixup_retval; |
| } |
| if( !left_figconst && right_figconst ) |
| { |
| // We are comparing the left side to a figurative constant: |
| switch( right_figconst ) |
| { |
| default: |
| fprintf(stderr, |
| "%s() %s:%d -- Unknown figurative constant %d\n", |
| __func__, __FILE__, __LINE__, |
| (int)right_figconst); |
| abort(); |
| break; |
| |
| case null_value_e: |
| break; |
| |
| case low_value_e: |
| case high_value_e: |
| case quote_value_e: |
| case space_value_e: |
| retval = 0; |
| for(size_t i=0; i<left_length; i++) |
| { |
| retval = collated((unsigned int)left_location[i]) |
| - collated(fig_right); |
| if( retval ) |
| { |
| break; |
| } |
| } |
| |
| compare = true; |
| goto fixup_retval; |
| break; |
| |
| case zero_value_e: |
| { |
| switch( left_side->type ) |
| { |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldNumericDisplay: |
| case FldLiteralN: |
| case FldIndex: |
| case FldPointer: |
| // ZEROES is a chameleon. When compared to a numeric, it is |
| // the number zero: |
| { |
| int rdigits; |
| __int128 value; |
| |
| if( left_side) |
| |
| value = get_binary_value_local( &rdigits, |
| left_side, |
| left_location, |
| left_length); |
| retval = 0; |
| retval = value < 0 ? -1 : retval; |
| retval = value > 0 ? 1 : retval; |
| compare = true; |
| break; |
| } |
| |
| case FldFloat: |
| { |
| GCOB_FP128 value = __gg__float128_from_location(left_side, |
| left_location); |
| retval = 0; |
| retval = value < 0 ? -1 : retval; |
| retval = value > 0 ? 1 : retval; |
| compare = true; |
| break; |
| } |
| |
| default: |
| // We are comparing a alphanumeric string to ZEROES |
| retval = 0; |
| for(size_t i=0; i<left_length; i++) |
| { |
| retval = collated((unsigned int)left_location[i]) |
| - collated(fig_right); |
| if( retval ) |
| { |
| break; |
| } |
| } |
| compare = true; |
| break; |
| } |
| goto fixup_retval; |
| } |
| } |
| } |
| else |
| { |
| // Neither left_side nor right_side is a figurative constant. |
| |
| // Our strategy here is to compare two alphanumerics, two numerics, |
| // or an alphanumeric to a numeric. We'll handle a numeric to an |
| // alphanumeric on a second-time-through. |
| |
| if( local_is_alpha(left_side->type, left_address_of) |
| && local_is_alpha(right_side->type, right_address_of) ) |
| { |
| retval = compare_strings( reinterpret_cast<char *>(left_location), |
| left_length, |
| left_all, |
| reinterpret_cast<char *>(right_location), |
| right_length, |
| right_all ); |
| |
| compare = true; |
| goto fixup_retval; |
| } |
| |
| if( local_is_numeric(left_side->type, left_address_of) |
| && local_is_numeric(right_side->type, right_address_of) ) |
| { |
| if( left_side->type == FldFloat && right_side->type == FldFloat ) |
| { |
| // One or the other of the numerics is a FldFloat |
| GCOB_FP128 left_value = __gg__float128_from_location(left_side, left_location); |
| GCOB_FP128 right_value = __gg__float128_from_location(right_side, right_location); |
| retval = 0; |
| retval = left_value < right_value ? -1 : retval; |
| retval = left_value > right_value ? 1 : retval; |
| compare = true; |
| goto fixup_retval; |
| } |
| |
| if( left_side->type == FldFloat ) |
| { |
| // The left side is a FldFloat; the other is another type of numeric: |
| int rdecimals; |
| GCOB_FP128 left_value; |
| GCOB_FP128 right_value; |
| |
| if( right_side->type == FldLiteralN) |
| { |
| // In order to do the comparision, we need the value from the |
| // literal to be the same flavor as the left side: |
| // We need to replace any commas with periods |
| static size_t size = 128; |
| static char *buffer = static_cast<char *>(malloc(size)); |
| while( strlen(right_side->initial)+1 > size ) |
| { |
| size *= 2; |
| buffer = static_cast<char *>(malloc(size)); |
| } |
| massert(buffer); |
| strcpy(buffer, right_side->initial); |
| if( __gg__decimal_point == ',' ) |
| { |
| // We need to replace any commas with periods |
| char *p = strchr(buffer, ','); |
| if(p) |
| { |
| *p = '.'; |
| } |
| } |
| |
| // buffer[] now contains the string we want to convert |
| |
| switch(left_side->capacity) |
| { |
| case 4: |
| { |
| _Float32 left_value4 = *PTRCAST(_Float32, left_location); |
| _Float32 right_value4 = strtof(buffer, NULL); |
| retval = 0; |
| retval = left_value4 < right_value4 ? -1 : retval; |
| retval = left_value4 > right_value4 ? 1 : retval; |
| break; |
| } |
| case 8: |
| { |
| _Float64 left_value8 = *PTRCAST(_Float64, left_location); |
| _Float64 right_value8 = strtod(buffer, NULL); |
| retval = 0; |
| retval = left_value8 < right_value8 ? -1 : retval; |
| retval = left_value8 > right_value8 ? 1 : retval; |
| break; |
| } |
| case 16: |
| { |
| //_Float128 left_value = *(_Float128 *)left_location; |
| GCOB_FP128 left_value16; |
| memcpy(&left_value16, left_location, 16); |
| GCOB_FP128 right_value16 = strtofp128(buffer, NULL); |
| retval = 0; |
| retval = left_value16 < right_value16 ? -1 : retval; |
| retval = left_value16 > right_value16 ? 1 : retval; |
| break; |
| } |
| } |
| } |
| else |
| { |
| left_value = __gg__float128_from_location(left_side, left_location); |
| |
| right_value = get_binary_value_local( &rdecimals, |
| right_side, |
| right_location, |
| right_length); |
| right_value /= __gg__power_of_ten(rdecimals); |
| retval = 0; |
| retval = left_value < right_value ? -1 : retval; |
| retval = left_value > right_value ? 1 : retval; |
| } |
| |
| compare = true; |
| goto fixup_retval; |
| } |
| |
| if( left_side->type != FldFloat && right_side->type != FldFloat) |
| { |
| // We are comparing a numeric to a numeric, neither are floats |
| int ldecimals; |
| int rdecimals; |
| __int128 left_value; |
| __int128 right_value; |
| |
| if( left_address_of ) |
| { |
| left_value = (__int128)left_location; |
| ldecimals = 0; |
| } |
| else |
| { |
| left_value = get_binary_value_local( &ldecimals, |
| left_side, |
| left_location, |
| left_length); |
| } |
| if( right_address_of ) |
| { |
| right_value = (__int128)right_location; |
| rdecimals = 0; |
| } |
| else |
| { |
| right_value = get_binary_value_local( &rdecimals, |
| right_side, |
| right_location, |
| right_length); |
| } |
| |
| // We need to align the decimal points: |
| if(rdecimals > ldecimals) |
| { |
| left_value *= __gg__power_of_ten(rdecimals-ldecimals); |
| } |
| else if( ldecimals > rdecimals ) |
| { |
| right_value *= __gg__power_of_ten(ldecimals-rdecimals); |
| } |
| |
| retval = 0; |
| retval = left_value < right_value ? -1 : retval; |
| retval = left_value > right_value ? 1 : retval; |
| compare = true; |
| goto fixup_retval; |
| } |
| } |
| |
| if( local_is_alpha(left_side->type, left_address_of) |
| && local_is_numeric(right_side->type, right_address_of) ) |
| { |
| // We are comparing an alphanumeric to a numeric. |
| |
| // The right side is numeric. Sometimes people write code where they |
| // take the refmod of a numeric displays. If somebody did that here, |
| // just do a complete straight-up character by character comparison: |
| |
| if( right_refmod ) |
| { |
| retval = compare_strings( reinterpret_cast<char *>(left_location), |
| left_length, |
| left_all, |
| reinterpret_cast<char *>(right_location), |
| right_length, |
| right_all); |
| compare = true; |
| goto fixup_retval; |
| } |
| |
| |
| // The trick here is to convert the numeric to its display form, |
| // and compare that to the alphanumeric. For example, when comparing |
| // a VAL5 PIC X(3) VALUE 5 to literals, |
| // |
| // VAL5 EQUAL 5 is TRUE |
| // VAL5 EQUAL 005 is TRUE |
| // VAL5 EQUAL "5" is FALSE |
| // VAL5 EQUAL "005" is TRUE |
| if( left_side->type == FldLiteralA ) |
| { |
| left_location = reinterpret_cast<unsigned char *>(left_side->data); |
| left_length = left_side->capacity; |
| } |
| |
| static size_t right_string_size = MINIMUM_ALLOCATION_SIZE; |
| static char *right_string |
| = static_cast<char *>(malloc(right_string_size)); |
| |
| right_string = format_for_display_internal( |
| &right_string, |
| &right_string_size, |
| right_side, |
| right_location, |
| right_length, |
| 0); |
| |
| // There is a tricky aspect to comparing an alphanumeric to |
| // a string. In short, we have to strip off any leading plus sign |
| |
| // And, according to the NIST tests, the same is true for minus signs. |
| // Apparently, when comparing a number to an alphanumeric, it is |
| // considered a "pseudo-move", and the rule for moving a negative |
| // number to an alphanumeric is that negative signs get stripped off |
| |
| if( *left_location == internal_plus || *left_location == internal_minus ) |
| { |
| left_location += 1; |
| left_length -= 1; |
| } |
| |
| const char *right_fixed; |
| if( *right_string == internal_plus || *right_string == internal_minus ) |
| { |
| right_fixed = right_string + 1; |
| } |
| else |
| { |
| right_fixed = right_string; |
| } |
| |
| retval = compare_strings( reinterpret_cast<char *>(left_location), |
| left_length, |
| left_all, |
| right_fixed, |
| strlen(right_fixed), |
| right_all); |
| compare = true; |
| goto fixup_retval; |
| } |
| } |
| |
| fixup_retval: |
| |
| if( !compare && !second_time_through) |
| { |
| // This is the first time through, and we couldn't do the comparison. |
| // Maybe we have to reverse the inputs: |
| retval = __gg__compare_2( right_side, |
| right_location, |
| right_length, |
| right_attr, |
| right_flags, |
| left_side, |
| left_location, |
| left_length, |
| left_attr, |
| left_flags, |
| 1); |
| // And reverse the sense of the return value: |
| compare = true; |
| retval = -retval; |
| } |
| |
| if( !compare && second_time_through ) |
| { |
| // Nope. We still couldn't do the comparison |
| fprintf(stderr, "###### %10s in %s:%d\n", __func__, __FILE__, __LINE__ ); |
| fprintf(stderr, "###### We don't know how to compare types %d and %d\n", |
| left_side->type, |
| right_side->type); |
| __gg__abort("__gg__compare_2() couldn't do the comparison"); |
| } |
| |
| // Normalize negative and positive to just -1 and +1 |
| if( retval < 0 ) |
| { |
| retval = -1; |
| } |
| else if( retval > 0) |
| { |
| retval = 1; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__compare(struct cblc_field_t *left, |
| size_t left_offset, |
| size_t left_length, |
| size_t left_flags, |
| struct cblc_field_t *right, |
| size_t right_offset, |
| size_t right_length, |
| size_t right_flags, |
| int second_time_through ) |
| { |
| int retval; |
| left_length = left_length ? left_length : left->capacity; |
| right_length = right_length ? right_length : right->capacity; |
| retval = __gg__compare_2( left, |
| left->data + left_offset, |
| left_length, |
| left->attr, |
| left_flags, |
| right, |
| right->data + right_offset, |
| right_length, |
| right->attr, |
| right_flags, |
| second_time_through); |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__double_to_target(cblc_field_t *tgt, double tgt_value, cbl_round_t rounded) |
| { |
| int tgt_rdigits = 0; |
| |
| if( tgt->attr & intermediate_e ) |
| { |
| // We are calculating an intermediate result. We want to keep as |
| // much of the fractional part as we can. We are using a double, |
| // which can hold almost 16 digits. So, keep multiplying non-zero |
| // value by 10 until it's that big. Limit the number of |
| // multiplies, in case we were given a ridiculously tiny number to |
| // begin with: |
| const double digits = 1E15; |
| |
| if( tgt_value ) |
| { |
| while( tgt_value > -digits && tgt_value < digits && tgt_rdigits < 15 ) |
| { |
| tgt_value *= 10.0; |
| tgt_rdigits += 1; |
| } |
| } |
| // Alter the run-time target's rdigits |
| tgt->rdigits = tgt_rdigits; |
| } |
| else |
| { |
| // We want a specific number of rdigits. We will multiply by |
| // 10^(tgt->rdigits + 1), to allow for the possibility of rounding: |
| tgt_rdigits = tgt->rdigits+1; |
| for(int i=0; i<tgt_rdigits; i++) |
| { |
| tgt_value *= 10.0; |
| } |
| } |
| |
| __gg__int128_to_field(tgt, |
| tgt_value, |
| tgt_rdigits, |
| rounded, |
| NULL); |
| } |
| |
| struct for_sort_table |
| { |
| size_t nkeys; |
| cblc_field_t **keys; |
| size_t *ascending; |
| unsigned char *contents; |
| size_t *offsets; |
| size_t base; |
| }; |
| |
| static for_sort_table sorter; |
| |
| static int |
| compare_two_records(unsigned char *range1, unsigned char *range2) |
| { |
| int retval = 0; |
| |
| for(size_t i=0; i<sorter.nkeys; i++) |
| { |
| // Pick up the basic information about the current key: |
| cblc_field_t field1; |
| cblc_field_t field2; |
| |
| memcpy(&field1, sorter.keys[i], sizeof(cblc_field_t)); |
| memcpy(&field2, sorter.keys[i], sizeof(cblc_field_t)); |
| |
| // Establish the locations inside the contents buffer |
| field1.data = range1 |
| + field1.offset |
| - sorter.base; |
| field2.data = range2 |
| + field2.offset |
| - sorter.base; |
| |
| // We handle descending by swapping the data sources: |
| if( !sorter.ascending[i] ) |
| { |
| std::swap(field1.data, field2.data); |
| } |
| |
| retval = __gg__compare( &field1, |
| 0, |
| field1.capacity, |
| 0, |
| &field2, |
| 0, |
| field2.capacity, |
| 0, |
| 0 ); |
| if( retval != 0 ) |
| { |
| break; |
| } |
| } |
| |
| return retval; |
| } |
| |
| static int |
| compare_for_sort_table(const size_t e1, const size_t e2) |
| { |
| int retval = 0; |
| // Pick up the offsets to the two records: |
| size_t offset1 = e1; |
| size_t offset2 = e2; |
| |
| offset1 += sizeof(size_t); |
| |
| offset2 += sizeof(size_t); |
| |
| retval = compare_two_records(sorter.contents + offset1, |
| sorter.contents + offset2); |
| |
| if(retval == 0) |
| { |
| // Create a stable sort by using the original offset as a way of breaking |
| // ties: |
| retval = e1 - e2; |
| } |
| |
| return retval < 0; |
| } |
| |
| static void |
| sort_contents(unsigned char *contents, |
| std::vector<size_t> &offsets, |
| size_t key_base, |
| size_t nkeys, |
| cblc_field_t **keys, |
| size_t *ascending, |
| int /*duplicates*/) |
| { |
| sorter.contents = contents; |
| sorter.offsets = offsets.data(); |
| sorter.base = key_base, |
| sorter.nkeys = nkeys; |
| sorter.keys = keys; |
| sorter.ascending = ascending; |
| |
| std::sort(offsets.begin(), offsets.end(), compare_for_sort_table); |
| } |
| |
| extern "C" |
| void |
| __gg__sort_table( const cblc_field_t *table, |
| size_t table_o, |
| size_t depending_on, |
| size_t nkeys, |
| cblc_field_t **keys, |
| size_t *ascending, |
| int duplicates ) |
| { |
| size_t buffer_size = 128; |
| unsigned char *contents = static_cast<unsigned char *>(malloc(buffer_size)); |
| size_t offset = 0; |
| std::vector<size_t>offsets; |
| size_t record_size = table->capacity; |
| unsigned char *next_record = table->data + table_o; |
| |
| // Convert the table to our normalized form |
| for(size_t i=0; i<depending_on; i++) |
| { |
| while( offset + sizeof(size_t) + record_size > buffer_size ) |
| { |
| buffer_size *= 2; |
| contents = static_cast<unsigned char *>(realloc(contents, buffer_size)); |
| } |
| offsets.push_back(offset); |
| memcpy(contents+offset, &record_size, sizeof(size_t)); |
| offset += sizeof(size_t); |
| memcpy(contents+offset, next_record, record_size); |
| offset += record_size; |
| next_record += record_size; |
| } |
| |
| // Sort it |
| sort_contents(contents, |
| offsets, |
| table->offset, |
| nkeys, |
| keys, |
| ascending, |
| duplicates); |
| |
| // Put the sorted contents back |
| next_record = table->data + table_o; |
| for(size_t i=0; i<depending_on; i++) |
| { |
| offset = offsets[i]; |
| offset += sizeof(size_t); |
| memcpy(next_record, contents+offset, record_size); |
| next_record += record_size; |
| } |
| |
| free(contents); |
| } |
| |
| static char * |
| as_initial(char *initial) |
| { |
| if( (size_t)initial & ~0xF ) |
| { |
| return initial; |
| } |
| else |
| { |
| static char empty_string[] = ""; |
| return empty_string; |
| } |
| } |
| |
| static const int DEFAULT_BYTE_MASK = 0x00000000FF; |
| static const int NSUBSCRIPT_MASK = 0x0000000F00; |
| static const int NSUBSCRIPT_SHIFT = 8; |
| static const int DEFAULTBYTE_BIT = 0x0000001000; |
| static const int EXPLICIT_BIT = 0x0000002000; |
| static const int REDEFINED_BIT = 0x0000004000; |
| static const int JUST_ONCE_BIT = 0x0000008000; |
| |
| static |
| void |
| init_var_both(cblc_field_t *var, |
| unsigned char *qual_data, |
| int flag_bits) |
| { |
| //fprintf(stderr, "CALLED WITH %s 0x%x\n", var->name, flag_bits); |
| |
| if( flag_bits & JUST_ONCE_BIT && var->attr & initialized_e ) |
| { |
| return; |
| } |
| |
| bool is_redefined = !!(flag_bits & REDEFINED_BIT); |
| bool explicitly = !!(flag_bits & EXPLICIT_BIT); |
| bool defaultbyte_in_play = !!(flag_bits & DEFAULTBYTE_BIT); |
| char defaultbyte = flag_bits & DEFAULT_BYTE_MASK; |
| unsigned int nsubscripts = (flag_bits & NSUBSCRIPT_MASK) >> NSUBSCRIPT_SHIFT; |
| |
| if( var->data == NULL |
| && var->attr & (intermediate_e) |
| && var->type != FldLiteralA |
| && var->type != FldLiteralN ) |
| { |
| //fprintf(stderr, "ABORTING on %2.2d %s %d\n", var->level, var->name, var->type); |
| //abort(); |
| var->data = static_cast<unsigned char *>(malloc(var->capacity)); |
| } |
| |
| // Set the "initialized" bit, which is tested in parser_symbol_add to make |
| // sure this code gets executed only once. |
| //fprintf(stderr, "__gg__initialize_variable %s setting initialize_e\n", var->name); |
| var->attr |= initialized_e; |
| |
| // We need to make sure that the program_states vector has at least one |
| // entry in it. This happens when we are the very first PROGRAM-ID called |
| // in this module. |
| |
| // When there is no DATA DIVISION, program_states will be empty the first time |
| // we arrive here. |
| if( program_states.empty() ) |
| { |
| initialize_program_state(); |
| } |
| |
| char *local_initial = as_initial(var->initial); |
| |
| if( var->level == LEVEL88 ) |
| { |
| // We need to convert the options to the internal native codeset |
| |
| size_t buffer_size = 4; |
| char *buffer = static_cast<char *>(malloc(buffer_size)); |
| |
| size_t index = 0; |
| |
| const cblc_field_t *parent = var->parent; |
| switch(parent->type) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| { |
| char *walker = local_initial; |
| while(*walker) |
| { |
| static size_t first_size = MINIMUM_ALLOCATION_SIZE; |
| static char *first = static_cast<char *>(malloc(first_size)); |
| static size_t last_size = MINIMUM_ALLOCATION_SIZE; |
| static char *last = static_cast<char *>(malloc(last_size)); |
| if( (*walker & 0xFF) == 0xFF ) |
| { |
| strcpy(first, walker); |
| } |
| else |
| { |
| raw_to_internal(&first, &first_size, walker, strlen(walker)); |
| } |
| walker += strlen(first) + 1; |
| |
| if( (*walker & 0xFF) == 0xFF ) |
| { |
| strcpy(last, walker); |
| } |
| else |
| { |
| raw_to_internal(&last, &last_size, walker, strlen(walker)); |
| } |
| walker += strlen(last) + 1; |
| while(index + strlen(first) + strlen(last) + 3 > buffer_size) |
| { |
| buffer_size *= 2; |
| buffer = static_cast<char *>(realloc(buffer, buffer_size)); |
| } |
| strcpy(buffer+index, first); |
| index += strlen(first) + 1; |
| strcpy(buffer+index, last); |
| index += strlen(last) + 1; |
| } |
| buffer[index++] = 0; |
| break; |
| } |
| } |
| if( index > 0 ) |
| { |
| buffer = static_cast<char *>(realloc(buffer, index)); |
| local_initial = buffer; |
| } |
| } |
| |
| // Next order of business: When the variable was allocated in |
| // parser_symbol_add(), only LEVEL 01 variables had memory allocated. All |
| // child variables were given NULL data pointers. It is at this point that |
| // we apply offsets: |
| |
| cblc_field_t *parent = var->parent; |
| if( !var->data ) |
| { |
| |
| // We don't have any data. If our immediate parent does have data, then |
| // we can calculate our data+offset from his data+offset: |
| |
| if( parent && parent->data ) |
| { |
| var->data = parent->data - parent->offset + var->offset; |
| } |
| } |
| |
| if( !var->data ) |
| { |
| // This can happen with BASED variables before they are allocated |
| return; |
| } |
| |
| if( !(var->attr & based_e) && (var->attr & external_e) ) |
| { |
| // These types can't be initialized |
| return; |
| } |
| |
| // There are times, for example, when we are table with OCCURS, that we |
| // look like a variable with no initial, and we might be tempted to set our |
| // memory to the default. But if a parent has been initialized, we must not |
| // touch our memory: |
| bool a_parent_initialized = false; |
| if( !explicitly ) |
| { |
| while(parent) |
| { |
| if( strlen(as_initial(parent->initial)) ) |
| { |
| a_parent_initialized = true; |
| } |
| if( parent->level == LEVEL01 |
| || parent->level == LEVEL77) |
| { |
| break; |
| } |
| parent = parent->parent; // I can't help it. This just tickles me. |
| } |
| } |
| |
| if( is_redefined || a_parent_initialized || var->level == LEVEL66 || var->level == LEVEL88) |
| { |
| // Don't initialize variables that have the REDEFINES clause. Many things |
| // in COBOL programs don't work if you do, in particular the initialization |
| // of tables. |
| |
| // Likewise, don't initialize variables with an OCCURS clause. To do so |
| // means that we will likely clobber the values in the flat data item we |
| // effectively redefine. |
| return; |
| } |
| |
| // This is a little brutish, but it is nonetheless simple, effective, and |
| // not at all costly. The numeric-edited variable type can have a |
| // BLANK WHEN ZERO clause, which causes the storage to be set to spaces |
| // when receiving a value of zero. But according to the ISO/IEC 1989:2014 |
| // specification, section 13.18.63.3 sentence 8, initialization is not |
| // affected by any BLANK WHEN ZERO clause. |
| |
| // So, I am going to rather ham-handedly turn that bit off here, and |
| // restore it when initialization is done. |
| |
| // Save this for later |
| int save_the_attribute = var->attr; |
| |
| // Turn off the bit in question |
| var->attr &= ~blank_zero_e; |
| |
| size_t capacity = var->capacity ; |
| |
| size_t number_of_dimensions = 0; |
| size_t limits[MAXIMUM_TABLE_DIMENSIONS]; |
| size_t capacities[MAXIMUM_TABLE_DIMENSIONS]; |
| size_t dimension[MAXIMUM_TABLE_DIMENSIONS+1]; |
| |
| bool there_can_be_more = nsubscripts == 0; |
| if( nsubscripts == 0 ) |
| { |
| cblc_field_t *family_tree = var; |
| while(family_tree && number_of_dimensions < MAXIMUM_TABLE_DIMENSIONS) |
| { |
| if( family_tree->occurs_upper ) |
| { |
| limits[number_of_dimensions] = family_tree->occurs_upper; |
| capacities[number_of_dimensions] = family_tree->capacity; |
| dimension[number_of_dimensions] = 0; |
| number_of_dimensions += 1; |
| } |
| |
| family_tree = family_tree->parent; |
| } |
| |
| switch( var->type ) |
| { |
| case FldIndex: |
| case FldGroup: |
| case FldClass: |
| there_can_be_more = false; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // We need to save the location in case we start changing the location |
| // to handle initializing table elements: |
| unsigned char *save_the_location = var->data; |
| bool there_is_more = false; |
| unsigned char *outer_location; |
| if( nsubscripts ) |
| { |
| outer_location = qual_data; |
| } |
| else |
| { |
| outer_location = var->data; |
| } |
| do |
| { |
| var->data = outer_location; |
| switch( var->type ) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldAlphaEdited: |
| case FldNumericEdited: |
| case FldLiteralA: |
| { |
| // Any initialization values were converted to single-byte-coding in the |
| // right codeset during parser_symbol_add() |
| if( var->initial ) |
| { |
| memcpy(outer_location, var->initial, var->capacity); |
| } |
| else |
| { |
| if( !defaultbyte_in_play ) |
| { |
| memset( outer_location, |
| internal_space, |
| capacity ); |
| } |
| else |
| { |
| memset( outer_location, |
| defaultbyte, |
| capacity ); |
| } |
| } |
| break; |
| } |
| |
| case FldNumericDisplay: |
| { |
| // Any initialization values were converted to single-byte-coding in the |
| // right codeset during parser_symbol_add() |
| if( var->initial ) |
| { |
| memcpy(outer_location, var->initial, var->capacity); |
| } |
| else |
| { |
| if( !defaultbyte_in_play ) |
| { |
| memset( outer_location, |
| internal_zero, |
| capacity ); |
| if( (var->attr & signable_e) && (var->attr & separate_e) ) |
| { |
| if( var->attr & leading_e ) |
| { |
| outer_location[0] = internal_plus; |
| } |
| else |
| { |
| outer_location[var->capacity-1] = internal_plus; |
| } |
| } |
| } |
| else |
| { |
| memset( outer_location, |
| defaultbyte, |
| capacity ); |
| } |
| } |
| break; |
| } |
| |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldIndex: |
| case FldFloat: |
| { |
| // During parser_symbol_add(), the original text initial value was turned |
| // into the appropriate binary value. |
| if( var->initial ) |
| { |
| memcpy(outer_location, var->initial, var->capacity); |
| } |
| else |
| { |
| if( !defaultbyte_in_play ) |
| { |
| memset( outer_location, |
| 0, |
| capacity ); |
| } |
| else |
| { |
| memset( outer_location, |
| defaultbyte, |
| capacity ); |
| } |
| } |
| break; |
| } |
| |
| case FldLiteralN: |
| break; |
| |
| case FldClass: |
| // Do nothing for class |
| break; |
| |
| case FldPointer: |
| memset(var->data, 0, var->capacity); |
| break; |
| |
| default: |
| fprintf(stderr, "###### %10s in %s:%d\n", __func__, __FILE__, __LINE__ ); |
| fprintf(stderr, "###### You got yourself a new CVT_type %d for variable %s (%s)\n", |
| var->type, |
| var->name, |
| local_initial); |
| __gg__abort("Unknown variable type"); |
| } |
| |
| char *location = reinterpret_cast<char *>(save_the_location); |
| |
| there_is_more = false; |
| size_t i=0; |
| // Roll up through the dimensions like an odometer. |
| for(i=0; i<number_of_dimensions; i++) |
| { |
| if( ++dimension[i] < limits[i] ) |
| { |
| break; |
| } |
| dimension[i] = 0; |
| } |
| if( i < number_of_dimensions ) |
| { |
| there_is_more = there_can_be_more; |
| } |
| if( there_is_more ) |
| { |
| // Augment location by the size of each dimension: |
| for(i=0; i<number_of_dimensions; i++) |
| { |
| location += dimension[i] * capacities[i]; |
| } |
| } |
| |
| outer_location = reinterpret_cast<unsigned char *>(location); |
| } while(there_is_more); |
| |
| var->data = save_the_location; |
| |
| // See the comment up above about suppressing and restoring |
| // BLANK WHEN ZERO during initialization. |
| var->attr |= (save_the_attribute&blank_zero_e); |
| } |
| |
| extern "C" |
| void |
| __gg__initialize_variable(cblc_field_t *var, |
| size_t offset, |
| int flag_bits) |
| { |
| init_var_both( var, |
| var->data + offset, |
| flag_bits); |
| } |
| |
| extern "C" |
| void |
| __gg__initialize_variable_clean(cblc_field_t *var, int flag_bits) |
| { |
| // if( var->type == FldLiteralA ) |
| // { |
| // fprintf(stderr, "BAZINGA!\n"); |
| // } |
| |
| init_var_both( var, |
| var->data, |
| flag_bits); |
| } |
| |
| static void |
| alpha_to_alpha_move_from_location(cblc_field_t *field, |
| size_t dest_offset, |
| size_t dest_length, |
| const char * source_location, |
| size_t source_length, |
| bool move_all) |
| { |
| // This is a helper function, called when it is known that both source |
| // and dest are alphanumeric |
| dest_length = dest_length ? dest_length : field->capacity; |
| |
| char *to = reinterpret_cast<char *>(field->data + dest_offset); |
| const char *from = source_location; |
| size_t count = std::min(dest_length, source_length); |
| |
| if( source_length >= dest_length ) |
| { |
| // We have more source characters than places to put them |
| if( field->attr & rjust_e ) |
| { |
| // Destination is right-justified, so we |
| // discard the leading source characters: |
| memmove(to, |
| from + (source_length - count), |
| count); |
| } |
| else |
| { |
| // Destination is right-justified, so we |
| // discard the trailing source characters: |
| memmove(to, |
| from, |
| count); |
| } |
| } |
| else |
| { |
| // We have too few source characters to fill the destination. |
| if( field->attr & rjust_e ) |
| { |
| // The destination is right-justified |
| if( move_all ) |
| { |
| // This does "BOBBO" |
| size_t isource = 0; |
| for(size_t i=0; i<dest_length; i++) |
| { |
| to[i] = from[isource++]; |
| if( isource >= source_length ) |
| { |
| isource = 0; |
| } |
| } |
| } |
| else |
| { |
| // The destination is right-justified, and the source is an |
| // ordinary string too short to fill it. So, we space-fill |
| // the leading characters. |
| // We do the move first, in case this is an overlapping move |
| // involving characters that will be space-filled |
| memmove(to + (dest_length-count), |
| from, |
| count); |
| memset(to, internal_space, dest_length-count); |
| } |
| } |
| else |
| { |
| // The source is smaller than the destination |
| // The destination is left-justified |
| if( move_all ) |
| { |
| // and the source is move_all. We will repeat the input until |
| // it fills the output, starting from the left side. |
| size_t isource = 0; |
| for(size_t i=0; i<dest_length; i++) |
| { |
| to[i] = from[isource++]; |
| if( isource >= source_length ) |
| { |
| isource = 0; |
| } |
| } |
| } |
| else |
| { |
| // The destination is right-justified, and the source is an |
| // ordinary string too short to fill it. So, we space-fill |
| // the trailing characters. |
| // We do the move first, in case this is an overlapping move |
| // involving characters that will be space-filled |
| memmove(to, |
| from, |
| count); |
| memset( to + count, |
| internal_space, |
| dest_length-count); |
| } |
| } |
| } |
| } |
| |
| static void |
| alpha_to_alpha_move(cblc_field_t *dest, |
| size_t dest_offset, |
| size_t dest_size, |
| const cblc_field_t *source, |
| size_t source_offset, |
| size_t source_size, |
| bool source_move_all) |
| { |
| alpha_to_alpha_move_from_location( dest, |
| dest_offset, |
| dest_size, |
| reinterpret_cast<char *>(source->data + source_offset), |
| source_size, |
| source_move_all); |
| } |
| |
| extern "C" |
| void |
| __gg__psz_to_alpha_move( cblc_field_t *field, |
| size_t offset, |
| size_t length, |
| const char *source, |
| size_t source_length ) |
| { |
| alpha_to_alpha_move_from_location( field, |
| offset, |
| length, |
| source, |
| source_length, |
| false); |
| } |
| |
| extern "C" |
| int |
| __gg__move( cblc_field_t *fdest, |
| size_t dest_offset, |
| size_t dest_size, |
| cblc_field_t *fsource, |
| size_t source_offset, |
| size_t source_size, |
| int source_flags, |
| cbl_round_t rounded ) |
| { |
| int size_error = 0; // This is the return value |
| |
| __int128 value; |
| int rdigits; |
| |
| cbl_figconst_t source_figconst = |
| (cbl_figconst_t)(fsource->attr & FIGCONST_MASK); |
| cbl_field_type_t dest_type = (cbl_field_type_t)fdest->type; |
| cbl_field_type_t source_type = (cbl_field_type_t)fsource->type; |
| |
| if( var_is_refmod(fdest) ) |
| { |
| // one or both are refmods, |
| dest_type = FldAlphanumeric; |
| if( source_figconst == normal_value_e ) |
| { |
| source_type = FldAlphanumeric; |
| } |
| } |
| |
| if( ( source_figconst == low_value_e |
| || source_figconst == space_value_e |
| || source_figconst == quote_value_e |
| || source_figconst == high_value_e ) |
| && |
| ( fdest->type == FldNumericBinary |
| || fdest->type == FldPacked |
| || fdest->type == FldNumericBin5 |
| || fdest->type == FldNumericDisplay |
| || fdest->type == FldFloat ) |
| ) |
| { |
| // Regardless of what you see below, as time went on it became clear that |
| // high-value and low-value required special processing in order to cope |
| // with code. Or, at least, to cope with legacy tests. |
| |
| // The ISO 2014 specification has this to say about the moving of figurative |
| // constants to numerics: |
| |
| // 14.9.24.3, paragraph 7) |
| |
| /* NOTE: MOVE of the figurative constant QUOTE or QUOTES to a numeric data |
| * item is an obsolete feature and is to be removed from the next edition |
| * of standard COBOL. MOVE of figurative constants that are not numeric, |
| * other than QUOTE or QUOTES, to a numeric item is an archaic feature of |
| * standard COBOL and its use should be avoided |
| */ |
| |
| int special_char = 0; // quiets cppcheck |
| if( source_figconst == low_value_e ) |
| { |
| special_char = ascii_to_internal(__gg__low_value_character); |
| } |
| else if( source_figconst == high_value_e ) |
| { |
| special_char = ascii_to_internal(__gg__high_value_character); |
| } |
| else if( source_figconst == quote_value_e ) |
| { |
| special_char = ascii_to_internal(__gg__quote_character); |
| } |
| else if( source_figconst == space_value_e ) |
| { |
| special_char = ascii_to_internal(ascii_space); |
| } |
| memset( fdest->data + dest_offset, |
| special_char, |
| dest_size); |
| } |
| else |
| { |
| size_t min_length; |
| bool moved = true; |
| switch( dest_type ) |
| { |
| case FldGroup: |
| switch( source_type ) |
| { |
| // For all other types, we just do a straight byte-for-byte move |
| case FldAlphanumeric: |
| case FldNumericEdited: |
| case FldAlphaEdited: |
| case FldNumericDisplay: |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldGroup: |
| // This is a little bold, but non-alphabetics will never |
| // have the rjust_e or MOVE_ALL bits on, so it's safe |
| // enough. |
| alpha_to_alpha_move(fdest, |
| dest_offset, |
| dest_size, |
| fsource, |
| source_offset, |
| source_size, |
| !!(source_flags & REFER_T_MOVE_ALL)); |
| break; |
| |
| default: |
| moved = false; |
| break; |
| } |
| |
| break; |
| |
| case FldAlphanumeric: |
| { |
| switch( source_type ) |
| { |
| case FldGroup: |
| alpha_to_alpha_move(fdest, |
| dest_offset, |
| dest_size, |
| fsource, |
| source_offset, |
| source_size, |
| !!(source_flags & REFER_T_MOVE_ALL)); |
| break; |
| |
| case FldAlphanumeric: |
| case FldNumericEdited: |
| case FldAlphaEdited: |
| // This is an ordinary alpha-to-alpha move: |
| alpha_to_alpha_move(fdest, |
| dest_offset, |
| dest_size, |
| fsource, |
| source_offset, |
| source_size, |
| !!(source_flags & REFER_T_MOVE_ALL)); |
| break; |
| |
| case FldNumericDisplay: |
| // We are moving a FldNumericDisplay to an alphanumeric: |
| if( fsource->rdigits > 0 ) |
| { |
| fprintf(stderr, "%s() %s:%d -- It isn't legal to move a" |
| " non-integer NumericDisplay to an" |
| " alphanumeric\n", |
| __func__, __FILE__, __LINE__); |
| fprintf( stderr, |
| "%s to %s\n", |
| fsource->name, |
| fdest->name); |
| abort(); |
| } |
| else |
| { |
| // We are moving a integer NumericDisplay to an |
| // alphanumeric. We ignore any sign bit, and just |
| // move the characters: |
| |
| size_t source_digits |
| = fsource->digits |
| + ( fsource->rdigits < 0 |
| ? -fsource->rdigits : 0) ; |
| |
| // Pick up the absolute value of the source |
| value = __gg__binary_value_from_qualified_field(&rdigits, |
| fsource, |
| source_offset, |
| source_size); |
| |
| char ach[128]; |
| |
| // Convert it to the full complement of digits available |
| // from the source...but no more |
| __gg__binary_to_string_internal(ach, source_digits, value); |
| |
| if( !(fdest->attr & rjust_e) ) |
| { |
| min_length = std::min( source_digits, |
| dest_size); |
| memmove(fdest->data + dest_offset, ach, min_length); |
| if( min_length < dest_size ) |
| { |
| // min_length is smaller than dest_length, so we |
| // have to space-fill the excess bytes in the |
| // destination: |
| memset( fdest->data + dest_offset + min_length, |
| internal_space, |
| dest_size - min_length ); |
| } |
| } |
| else |
| { |
| // Destination is right-justified, so things are |
| // slightly more complex |
| if( source_digits >= dest_size ) |
| { |
| // We need to truncate the source data on the |
| // left: |
| memmove( |
| fdest->data + dest_offset, |
| ach + (source_digits - dest_size), |
| dest_size ); |
| } |
| else |
| { |
| // We need to move the shorty source string to |
| // the right side of the destination, and space-fill |
| // the prefix: |
| memmove(fdest->data + dest_offset + (dest_size - source_digits), |
| ach, |
| source_digits ); |
| memset( fdest->data + dest_offset, |
| internal_space, |
| dest_size - source_digits); |
| } |
| } |
| } |
| break; |
| |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldLiteralN: |
| // We are moving a binary number to an alphanumeric: |
| if( fsource->rdigits > 0 ) |
| { |
| fprintf(stderr, "%s() %s:%d -- It isn't legal to move a" |
| " non-integer binary number to an" |
| " alphanumeric\n", |
| __func__, __FILE__, __LINE__); |
| fprintf(stderr, "%s to %s\n", fsource->name, fdest->name); |
| abort(); |
| } |
| else |
| { |
| char ach[128]; |
| |
| // Turn the integer source into a value: |
| value = __gg__binary_value_from_qualified_field(&rdigits, |
| fsource, |
| source_offset, |
| source_size); |
| |
| source_size = fsource->digits; |
| |
| // Turn the integer value into a string: |
| __gg__binary_to_string_internal(ach, |
| source_size, |
| value); |
| |
| char *pach = ach; |
| |
| // When source is a temporary variable, it was set to |
| // a large number of digits, which will give the wrong |
| // result. So, we will make like the US Marine Corp, |
| // and improvise, adapt, and overcome. |
| |
| // Specifically, we'll move pach to point to the first |
| // character that isn't zero. |
| |
| if( fsource->attr & intermediate_e ) |
| { |
| while(source_size > 1) // This ensures we leave one '0' |
| { |
| if( *(pach+1) == '\0' ) |
| { |
| break; |
| } |
| if( ((*pach)&0xFF) != internal_zero ) |
| { |
| break; |
| } |
| pach += 1; |
| source_size -= 1; |
| } |
| } |
| |
| if( !(fdest->attr & rjust_e) ) |
| { |
| min_length = std::min( source_size, |
| dest_size); |
| memmove(fdest->data+dest_offset, pach, min_length); |
| if( min_length < dest_size ) |
| { |
| // min_length is smaller than dest_length, so we have to |
| // space-fill the excess bytes in the destination: |
| memset( fdest->data+dest_offset + min_length, |
| internal_space, |
| dest_size - min_length ); |
| } |
| } |
| else |
| { |
| // Destination is right-justified, so things are slightly more complex |
| if( source_size >= dest_size ) |
| { |
| // We need to truncate the source data on the left: |
| memmove(fdest->data+dest_offset, |
| pach + (source_size - dest_size), |
| dest_size ); |
| } |
| else |
| { |
| // We need to move the shorty source string to the |
| // right side of the destination, and space-fill the prefix: |
| memmove(fdest->data+dest_offset + (dest_size - source_size), |
| pach, |
| source_size ); |
| memset(fdest->data+dest_offset, internal_space, (dest_size - source_size)); |
| } |
| } |
| } |
| break; |
| |
| case FldIndex: |
| { |
| char ach[128]; |
| |
| // Turn the integer source into a value: |
| value = __gg__binary_value_from_qualified_field(&rdigits, |
| fsource, |
| source_offset, |
| source_size); |
| sprintf(ach, "%lu", (unsigned long)value); |
| |
| char *pach = ach; |
| |
| if( !(fdest->attr & rjust_e) ) |
| { |
| min_length = std::min( source_size, |
| dest_size); |
| memmove(fdest->data+dest_offset, pach, min_length); |
| if( min_length < dest_size ) |
| { |
| // min_length is smaller than dest_length, so we have to |
| // space-fill the excess bytes in the destination: |
| memset( fdest->data+dest_offset + min_length, |
| internal_space, |
| dest_size - min_length ); |
| } |
| } |
| else |
| { |
| // Destination is right-justified, so things are slightly more complex |
| if( source_size >= dest_size ) |
| { |
| // We need to truncate the source data on the left: |
| memmove(fdest->data+dest_offset, |
| pach + (source_size - dest_size), |
| dest_size ); |
| } |
| else |
| { |
| // We need to move the shorty source string to the |
| // right side of the destination, and space-fill the prefix: |
| memmove(fdest->data+dest_offset + (dest_size - source_size), |
| pach, |
| source_size ); |
| memset(fdest->data+dest_offset, internal_space, (dest_size - source_size)); |
| } |
| } |
| } |
| break; |
| |
| default: |
| moved = false; |
| break; |
| } |
| break; |
| } |
| |
| case FldNumericBinary: |
| { |
| switch( source_type ) |
| { |
| case FldGroup: |
| min_length = std::min(source_size, dest_size); |
| memmove(fdest->data+dest_offset, fsource->data+source_offset, min_length); |
| if( min_length < dest_size ) |
| { |
| // min_length is smaller than dest_length, so we have to |
| // space-fill the excess bytes in the destination: |
| memset( fdest->data+dest_offset + min_length, |
| internal_space, |
| dest_size - min_length ); |
| } |
| fdest->attr &= ~FIGCONST_MASK; |
| break; |
| |
| case FldAlphanumeric: |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldIndex: |
| case FldLiteralN: |
| { |
| // We are moving a number to a number: |
| value = __gg__binary_value_from_qualified_field(&rdigits, |
| fsource, |
| source_offset, |
| source_size); |
| |
| if( truncation_mode == trunc_std_e ) |
| { |
| // We need to adjust the value to have the rdigits of the |
| // the destination: |
| |
| int scaler = rdigits - fdest->rdigits; |
| if( scaler > 0 ) |
| { |
| value /= __gg__power_of_ten(scaler); |
| rdigits -= scaler; |
| } |
| else if( scaler < 0 ) |
| { |
| value *= __gg__power_of_ten(-scaler); |
| rdigits -= scaler; |
| } |
| if( value < 0 ) |
| { |
| value = -value; |
| value %= __gg__power_of_ten(fdest->digits); |
| value = -value; |
| } |
| else |
| { |
| value %= __gg__power_of_ten(fdest->digits); |
| } |
| } |
| |
| __gg__int128_to_qualified_field( |
| fdest, |
| dest_offset, |
| dest_size, |
| value, |
| rdigits, |
| rounded, |
| &size_error ); |
| break; |
| } |
| |
| case FldFloat: |
| { |
| rdigits = get_scaled_rdigits(fdest); |
| bool negative = false; |
| __int128 value128 = 0; |
| switch(fsource->capacity) |
| { |
| case 4: |
| { |
| _Float32 val = *PTRCAST(_Float32, fsource->data+source_offset); |
| if(val < 0) |
| { |
| negative = true; |
| val = -val; |
| } |
| val *= static_cast<_Float32>(__gg__power_of_ten(rdigits)); |
| value128 = (__int128)val; |
| break; |
| } |
| case 8: |
| { |
| _Float64 val = *PTRCAST(_Float64, fsource->data+source_offset); |
| if(val < 0) |
| { |
| negative = true; |
| val = -val; |
| } |
| val *= (_Float32)__gg__power_of_ten(rdigits); |
| value128 = (__int128)val; |
| break; |
| } |
| case 16: |
| { |
| //_Float128 val = *(_Float128 *)(fsource->data+source_offset); |
| GCOB_FP128 val; |
| memcpy(&val, fsource->data+source_offset, 16); |
| if(val < 0) |
| { |
| negative = true; |
| val = -val; |
| } |
| val *= (_Float32)__gg__power_of_ten(rdigits); |
| value128 = (__int128)val; |
| break; |
| } |
| } |
| if( negative ) |
| { |
| value128 = -value128; |
| } |
| __gg__int128_to_qualified_field( |
| fdest, |
| dest_offset, |
| dest_size, |
| value128, |
| rdigits, |
| rounded, |
| &size_error ); |
| break; |
| } |
| |
| default: |
| { |
| moved = false; |
| break; |
| } |
| } |
| break; |
| } |
| |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldNumericBin5: |
| case FldPacked: |
| case FldIndex: |
| // Bin5 and Index are treated with no truncation, as if they were |
| // trunc_bin_e. The other types aren't subject to truncation. |
| switch( source_type ) |
| { |
| case FldGroup: |
| min_length = std::min(source_size, dest_size); |
| memmove(fdest->data+dest_offset, fsource->data+source_offset, min_length); |
| if( min_length < dest_size ) |
| { |
| // min_length is smaller than dest_length, so we have to |
| // space-fill the excess bytes in the destination: |
| memset( fdest->data+dest_offset + min_length, |
| internal_space, |
| dest_size - min_length ); |
| } |
| break; |
| |
| case FldAlphanumeric: |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldIndex: |
| case FldLiteralN: |
| { |
| // We are moving a number to a number: |
| value = __gg__binary_value_from_qualified_field(&rdigits, |
| fsource, |
| source_offset, |
| source_size); |
| __gg__int128_to_qualified_field( |
| fdest, |
| dest_offset, |
| dest_size, |
| value, |
| rdigits, |
| rounded, |
| &size_error ); |
| break; |
| } |
| |
| case FldFloat: |
| { |
| // We are converted a floating-point value fixed-point |
| |
| rdigits = get_scaled_rdigits(fdest); |
| GCOB_FP128 fp128=0; |
| switch(fsource->capacity) |
| { |
| case 4: |
| { |
| fp128 = *reinterpret_cast<_Float32 *>(fsource->data+source_offset); |
| break; |
| } |
| case 8: |
| { |
| fp128 = *reinterpret_cast<_Float64 *>(fsource->data+source_offset); |
| break; |
| } |
| case 16: |
| { |
| // value = *(_Float128 *)(fsource->data+source_offset); |
| memcpy(&fp128, fsource->data+source_offset, 16); |
| break; |
| } |
| } |
| __gg__float128_to_qualified_field( |
| fdest, |
| dest_offset, |
| fp128, |
| rounded, |
| &size_error); |
| break; |
| } |
| |
| default: |
| moved = false; |
| break; |
| } |
| break; |
| |
| case FldAlphaEdited: |
| { |
| switch( source_type ) |
| { |
| case FldGroup: |
| min_length = std::min(source_size, dest_size); |
| memmove(fdest->data+dest_offset, fsource->data+source_offset, min_length); |
| if( min_length < dest_size ) |
| { |
| // min_length is smaller than dest_length, so we have to |
| // space-fill the excess bytes in the destination: |
| memset( fdest->data+dest_offset + min_length, |
| internal_space, |
| dest_size - min_length ); |
| } |
| break; |
| |
| case FldNumericDisplay: |
| { |
| int source_digits = fsource->digits + (fsource->rdigits<0 ? -fsource->rdigits : 0) ; |
| |
| // Pick up the absolute value of the source |
| value = __gg__binary_value_from_qualified_field(&rdigits, |
| fsource, |
| source_offset, |
| source_size); |
| char ach[64]; |
| |
| // Convert it to the full complement of digits available |
| // from the source...but no more |
| __gg__binary_to_string(ach, source_digits, value); |
| |
| // Binary to string returns ASCII characters: |
| for(int i=0; i<source_digits; i++) |
| { |
| ach[i] = ascii_to_internal(ach[i]); |
| } |
| |
| // And move them into place: |
| __gg__string_to_alpha_edited( reinterpret_cast<char *>(fdest->data+dest_offset), |
| ach, |
| source_digits, |
| fdest->picture); |
| break; |
| } |
| |
| default: |
| { |
| static size_t display_string_size = MINIMUM_ALLOCATION_SIZE; |
| static char *display_string = static_cast<char *>(malloc(display_string_size)); |
| |
| size_t display_string_length = dest_size; |
| __gg__realloc_if_necessary( &display_string, |
| &display_string_size, |
| display_string_length); |
| |
| if( source_figconst == low_value_e ) |
| { |
| memset(display_string, ascii_to_internal(__gg__low_value_character), dest_size); |
| } |
| else if( source_figconst == zero_value_e ) |
| { |
| memset(display_string, internal_zero, dest_size); |
| } |
| else if( source_figconst == space_value_e ) |
| { |
| memset(display_string, internal_space, dest_size); |
| } |
| else if( source_figconst == quote_value_e ) |
| { |
| memset(display_string, ascii_to_internal(__gg__quote_character), dest_size); |
| } |
| else if( source_figconst == high_value_e ) |
| { |
| memset(display_string, ascii_to_internal(__gg__high_value_character), dest_size); |
| } |
| else |
| { |
| display_string = format_for_display_internal( |
| &display_string, |
| &display_string_size, |
| fsource, |
| reinterpret_cast<unsigned char *>(fsource->data+source_offset), |
| source_size, |
| source_flags && REFER_T_ADDRESS_OF); |
| display_string_length = strlen(display_string); |
| } |
| __gg__string_to_alpha_edited( reinterpret_cast<char *>(fdest->data+dest_offset), |
| display_string, |
| display_string_length, |
| fdest->picture); |
| break; |
| } |
| } |
| break; |
| } |
| |
| case FldFloat: |
| { |
| switch( source_type ) |
| { |
| case FldAlphanumeric: |
| { |
| char ach[256]; |
| size_t len = std::min(source_size, sizeof(ach)-1); |
| memcpy(ach, fsource->data+source_offset, len); |
| ach[len] = '\0'; |
| __gg__internal_to_console_in_place(ach, len); |
| switch( fdest->capacity ) |
| { |
| case 4: |
| { |
| *PTRCAST(float, fdest->data+dest_offset) = strtod(ach, NULL); |
| break; |
| } |
| case 8: |
| { |
| *PTRCAST(double, fdest->data+dest_offset) = strtod(ach, NULL); |
| break; |
| } |
| case 16: |
| { |
| //*(_Float128 *)(fdest->data+dest_offset) = strtofp128(ach, NULL); |
| GCOB_FP128 t = strtofp128(ach, NULL); |
| memcpy(fdest->data+dest_offset, &t, 16); |
| break; |
| } |
| } |
| break; |
| } |
| default: |
| moved = false; |
| break; |
| } |
| break; |
| } |
| |
| default: |
| moved = false; |
| break; |
| } |
| if( !moved ) |
| { |
| fprintf(stderr, "%s() %s:%d -- We were unable to do a move from " |
| "type %d to %d\n", |
| __func__, __FILE__, __LINE__, |
| fsource->type, fdest->type); |
| abort(); |
| } |
| } |
| return size_error; |
| } |
| |
| extern "C" |
| int |
| __gg__move_literala(cblc_field_t *field, |
| size_t field_offset, |
| size_t field_size, |
| cbl_round_t rounded_, |
| const char *str, |
| size_t strlen ) |
| { |
| cbl_round_t rounded = static_cast<cbl_round_t>(rounded_ & ~REFER_ALL_BIT); |
| bool move_all = !!(rounded_ & REFER_ALL_BIT); |
| |
| int size_error = 0; // This is the return value |
| |
| bool moved = true; |
| |
| __int128 value; |
| int rdigits; |
| |
| cbl_field_type_t dest_type = (cbl_field_type_t)field->type; |
| if( var_is_refmod(field) ) |
| { |
| dest_type = FldAlphanumeric; |
| } |
| |
| switch( dest_type ) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| { |
| alpha_to_alpha_move_from_location(field, field_offset, field_size, str, strlen, move_all); |
| break; |
| } |
| |
| case FldNumericBinary: |
| { |
| value = __gg__dirty_to_binary_internal( str, |
| strlen, |
| &rdigits ); |
| if( truncation_mode == trunc_std_e ) |
| { |
| // We need to adjust the value to have the rdigits of the |
| // the destination: |
| |
| int scaler = rdigits - field->rdigits; |
| if( scaler > 0 ) |
| { |
| value /= __gg__power_of_ten(scaler); |
| rdigits -= scaler; |
| } |
| else if( scaler < 0 ) |
| { |
| value *= __gg__power_of_ten(-scaler); |
| rdigits -= scaler; |
| } |
| |
| if( value < 0 ) |
| { |
| value = -value; |
| value %= __gg__power_of_ten(field->digits); |
| value = -value; |
| } |
| else |
| { |
| value %= __gg__power_of_ten(field->digits); |
| } |
| } |
| __gg__int128_to_qualified_field( |
| field, |
| field_offset, |
| field_size, |
| value, |
| rdigits, |
| rounded, |
| &size_error ); |
| break; |
| } |
| |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldNumericBin5: |
| case FldPacked: |
| case FldIndex: |
| // Bin5 and Index are treated with no truncation, as if they were |
| // trunc_bin_e. The other types aren't subject to truncation. |
| // We are moving a number to a number: |
| value = __gg__dirty_to_binary_internal( str, |
| strlen, |
| &rdigits ); |
| __gg__int128_to_qualified_field( |
| field, |
| field_offset, |
| field_size, |
| value, |
| rdigits, |
| rounded, |
| &size_error ); |
| break; |
| |
| case FldAlphaEdited: |
| { |
| static size_t display_string_size = MINIMUM_ALLOCATION_SIZE; |
| static char *display_string = static_cast<char *>(malloc(display_string_size)); |
| |
| __gg__realloc_if_necessary( &display_string, |
| &display_string_size, |
| field_size); |
| |
| memset(display_string, internal_space, display_string_size); |
| size_t len = std::min(display_string_size, strlen); |
| memcpy(display_string, str, len); |
| __gg__string_to_alpha_edited( reinterpret_cast<char *>(field->data+field_offset), |
| display_string, |
| field_size, |
| field->picture); |
| break; |
| } |
| |
| case FldFloat: |
| { |
| char ach[256]; |
| size_t len = std::min(strlen, sizeof(ach)-1); |
| memcpy(ach, str, len); |
| ach[len] = '\0'; |
| switch( field->capacity ) |
| { |
| case 4: |
| { |
| *PTRCAST(float, field->data+field_offset) = strtod(ach, NULL); |
| break; |
| } |
| case 8: |
| { |
| *PTRCAST(double, field->data+field_offset) = strtod(ach, NULL); |
| break; |
| } |
| case 16: |
| { |
| GCOB_FP128 t = strtofp128(ach, NULL); |
| memcpy(field->data+field_offset, &t, 16); |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| moved = false; |
| break; |
| } |
| |
| if( !moved ) |
| { |
| fprintf(stderr, "%s() %s:%d -- We were unable to do a move to " |
| "type %d\n", |
| __func__, __FILE__, __LINE__, |
| field->type); |
| abort(); |
| } |
| |
| return size_error; |
| } |
| |
| extern "C" |
| void |
| __gg__file_sort_ff_input( cblc_file_t *workfile, |
| cblc_file_t *input) |
| { |
| // The name means "file-file input" |
| |
| // We are going to read records from input and write them to workfile. These |
| // files are already open. |
| |
| sv_suppress_eof_ec = true; |
| for(;;) |
| { |
| // Read the data from the input file into its record_area |
| __gg__file_read(input, |
| -1); |
| if( input->io_status >= FhNotOkay ) |
| { |
| break; |
| } |
| // We have the data we need. Transmit it to workfile. |
| int before_advancing = 0; |
| if( workfile->org == file_line_sequential_e ) |
| { |
| // we need a newline at the end of each line |
| before_advancing = 1; |
| } |
| else if( workfile->org == file_sequential_e ) |
| { |
| // We don't want any vertical movement |
| before_advancing = -1; |
| } |
| |
| size_t bytes_to_write = std::min( workfile->record_area_max, |
| input->record_area_max ); |
| |
| __gg__file_write( workfile, |
| input->default_record->data, |
| bytes_to_write, |
| 0, |
| before_advancing, |
| 0); // non-random |
| } |
| sv_suppress_eof_ec = false; |
| } |
| |
| extern "C" |
| void |
| __gg__file_sort_ff_output( cblc_file_t *output, |
| cblc_file_t *workfile) |
| { |
| // The name means "file-file output" |
| |
| // We read records from workfile and write them to the output file |
| |
| // Make sure workfile is positioned at the beginning |
| __gg__file_reopen(workfile, 'r'); |
| |
| sv_suppress_eof_ec = true; |
| for(;;) |
| { |
| __gg__file_read( workfile, |
| -1); |
| if( workfile->io_status >= FhNotOkay ) |
| { |
| break; |
| } |
| int advancing = -1; // Default to no vertical movement |
| if( output->org == file_line_sequential_e ) |
| { |
| advancing = 1; |
| } |
| |
| __gg__file_write( output, |
| workfile->default_record->data, |
| workfile->record_area_max, |
| 0, |
| advancing, |
| 0); // 1 would be is_random |
| } |
| sv_suppress_eof_ec = false; |
| } |
| |
| extern "C" |
| void |
| __gg__sort_workfile(cblc_file_t *workfile, |
| size_t nkeys, |
| cblc_field_t **keys, |
| size_t *ascending, |
| int duplicates) |
| { |
| // We are going to read the records of workfile into memory. We keep offsets |
| // into the memory buffer, and then we'll sort those offsets according to the |
| // things they point to. |
| |
| // The workfile is open and positioned at zero when we arrive here. |
| |
| // Read the file into memory |
| size_t buffer_size = 128; |
| unsigned char *contents = static_cast<unsigned char *>(malloc(buffer_size)); |
| size_t offset = 0; |
| std::vector<size_t>offsets; |
| size_t bytes_read; |
| size_t bytes_to_write; |
| |
| sv_suppress_eof_ec = true; |
| for(;;) |
| { |
| __gg__file_read(workfile, |
| -1); |
| if( workfile->record_length ) |
| { |
| int rdigits; |
| bytes_read = (size_t) __gg__binary_value_from_field( |
| &rdigits, |
| workfile->record_length); |
| } |
| else |
| { |
| bytes_read = workfile->record_area_max; |
| } |
| if( workfile->io_status >= FhNotOkay ) |
| { |
| break; |
| } |
| |
| while( offset + sizeof(size_t) + bytes_read > buffer_size ) |
| { |
| buffer_size *= 2; |
| contents = static_cast<unsigned char *>(realloc(contents, buffer_size)); |
| } |
| offsets.push_back(offset); |
| |
| // Copy over the record size: |
| memcpy(contents+offset, &bytes_read, sizeof(size_t)); |
| offset += sizeof(size_t); |
| |
| // And the contents of the record |
| memcpy(contents+offset, workfile->default_record->data, bytes_read); |
| offset += bytes_read; |
| } |
| sv_suppress_eof_ec = false; |
| |
| sort_contents(contents, |
| offsets, |
| 0, |
| nkeys, |
| keys, |
| ascending, |
| duplicates); |
| |
| // We now put the sorted data back out onto the disk: |
| fclose(workfile->file_pointer); |
| __gg__file_reopen(workfile, 'w'); |
| |
| for(size_t i=0; i<offsets.size(); i++) |
| { |
| offset = offsets[i]; |
| memcpy(&bytes_to_write, contents+offset, sizeof(size_t)); |
| offset += sizeof(size_t); |
| int advancing = -1; |
| if( workfile->org == file_line_sequential_e ) |
| { |
| advancing = 1; |
| } |
| if( workfile->record_area_min != workfile->record_area_max |
| && workfile->record_length ) |
| { |
| __gg__int128_to_field(workfile->record_length, |
| bytes_to_write, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| __gg__file_write( workfile, |
| contents+offset, |
| bytes_to_write, |
| 0, |
| advancing, |
| 0); // 1 would be is_random |
| } |
| free(contents); |
| } |
| |
| extern "C" |
| void |
| __gg__merge_files( cblc_file_t *workfile, |
| size_t nkeys, |
| cblc_field_t **keys, |
| size_t *ascending, |
| size_t ninputs, |
| cblc_file_t **inputs) |
| { |
| // Merge takes in N files that are already sorted. It looks at the N records |
| // at the top of the N files, and figures out who the winner is, and puts each |
| // winner into workfile. If it notices that any of the files are not in the |
| // order specified by the keys it raises the EC-SORT-MERGE-SEQUENCE exception. |
| |
| // Is everybody ready? |
| |
| // Then we will begin. |
| |
| sorter.nkeys = nkeys; |
| sorter.keys = keys; |
| sorter.ascending = ascending; |
| |
| // We need to prime the pump by reading one record from everybody |
| size_t the_biggest = 0; |
| for(size_t i=0; i<ninputs; i++) |
| { |
| the_biggest = std::max(the_biggest, inputs[i]->record_area_max); |
| |
| __gg__file_read(inputs[i], |
| -1); |
| if( inputs[i]->io_status >= FhNotOkay ) |
| { |
| inputs[i] = NULL; |
| } |
| } |
| |
| // For each input, either there is a good record in its record area, or else |
| // inputs[i] for that file is NULL |
| |
| if( !the_biggest ) |
| { |
| return; |
| } |
| |
| unsigned char *prior_winner = static_cast<unsigned char *>(malloc(the_biggest)); |
| massert(prior_winner); |
| *prior_winner = '\0'; |
| |
| for(;;) |
| { |
| int winner = -1; |
| for(int i=0; i<(int)ninputs; i++ ) |
| { |
| if( !inputs[i] ) |
| { |
| // This input has been exhausted |
| continue; |
| } |
| if( winner == -1 ) |
| { |
| // Establish the first file as the current winner |
| winner = i; |
| continue; |
| } |
| // We now compare inputs[i] to the current winner |
| int ncompare = compare_two_records( inputs[i]->default_record->data, |
| inputs[winner]->default_record->data); |
| if( ncompare < 0 ) |
| { |
| // We have a new winner |
| winner = i; |
| } |
| } |
| // We have scanned all the inputs, looking for the smallest of them. |
| if( winner == -1 ) |
| { |
| // We have exhausted all of the inputs, which means we are done. |
| break; |
| } |
| if( *prior_winner ) |
| { |
| // We need to compare the current winner to the prior winner |
| int ncompare = compare_two_records( prior_winner, |
| inputs[winner]->default_record->data); |
| if( ncompare > 0 ) |
| { |
| // The prior winner is bigger than the current winner, which means that |
| // the input files were not in order. This is a run-time error. |
| |
| exception_raise(ec_sort_merge_sequence_e); |
| abort(); |
| } |
| } |
| // Establish winner as the prior_winner |
| memcpy( prior_winner, |
| inputs[winner]->default_record->data, |
| inputs[winner]->record_area_max); |
| // And send it to the workfile |
| |
| int before_advancing = -1; // No vertical movement... |
| if( workfile->org == file_line_sequential_e ) |
| { |
| // we need a newline at the end of each line sequential line |
| before_advancing = 1; |
| } |
| |
| __gg__file_write( workfile, |
| inputs[winner]->default_record->data, |
| inputs[winner]->record_area_max, |
| 0, |
| before_advancing, |
| 0); // 1 means is_random |
| |
| |
| // And now we need to replace the winner: |
| |
| __gg__file_read(inputs[winner], |
| -1); |
| if( inputs[winner]->io_status >= FhNotOkay ) |
| { |
| inputs[winner] = NULL; |
| } |
| } |
| |
| free(prior_winner); |
| } |
| |
| static const char * |
| funky_find( const char *piece, |
| const char *piece_end, |
| const char *whole, |
| const char *whole_end ) |
| { |
| const char *retval = NULL; |
| |
| size_t length_of_piece = piece_end - piece; |
| if(length_of_piece == 0) |
| { |
| __gg__abort("funky_find() length_of_piece shouldn't be zero"); |
| } |
| |
| whole_end -= length_of_piece; |
| |
| while( whole <= whole_end ) |
| { |
| if( memcmp( piece, whole, length_of_piece) == 0 ) |
| { |
| retval = whole; |
| break; |
| } |
| whole += 1; |
| } |
| return retval; |
| } |
| |
| static const char * |
| funky_find_backward(const char *piece, |
| const char *piece_end, |
| const char *whole, |
| const char *whole_end ) |
| { |
| const char *retval = NULL; |
| |
| size_t length_of_piece = piece_end - piece; |
| if(length_of_piece == 0) |
| { |
| __gg__abort("funky_find_backward() length_of_piece shouldn't be zero"); |
| } |
| |
| whole_end -= length_of_piece; |
| |
| while( whole <= whole_end ) |
| { |
| if( memcmp( piece, whole_end, length_of_piece) == 0 ) |
| { |
| retval = whole_end; |
| break; |
| } |
| whole_end -= 1; |
| } |
| return retval; |
| } |
| |
| typedef struct normalized_operand |
| { |
| // These are the characters of the string. When the field is NumericDisplay |
| // any leading or trailing +/- characters are removed, and any embedded |
| // NUMERIC_DISPLAY_SIGN_BIT bits are removed. |
| std::string the_characters; |
| size_t offset; // Usually zero. One when there is a leading sign. |
| size_t length; // Usually the same as the original. But it is one less |
| // // than the original when there is a trailing sign. |
| } normalized_operand; |
| |
| typedef struct comparand |
| { |
| size_t id_2_index; |
| cbl_inspect_bound_t operation; |
| normalized_operand identifier_3; // The thing to be found |
| normalized_operand identifier_5; // The replacement, for FORMAT 2 |
| const char *alpha; // The start location within normalized_id_1 |
| const char *omega; // The end+1 location within normalized_id_1 |
| size_t leading_count; |
| bool leading; |
| bool first; |
| } comparand; |
| |
| typedef struct id_2_result |
| { |
| cblc_field_t *id2; |
| size_t id2_o; |
| size_t id2_s; |
| size_t result; |
| } id_2_result; |
| |
| static normalized_operand |
| normalize_id( const cblc_field_t *refer, |
| size_t refer_o, |
| size_t refer_s |
| ) |
| { |
| normalized_operand retval; |
| |
| if( refer ) |
| { |
| const unsigned char *data = refer->data + refer_o; |
| cbl_figconst_t figconst |
| = (cbl_figconst_t)(refer->attr & FIGCONST_MASK); |
| |
| retval.offset = 0; |
| retval.length = refer_s; |
| |
| if( refer->type == FldNumericDisplay ) |
| { |
| // The value is NumericDisplay. |
| if( refer->attr & separate_e ) |
| { |
| // Because the sign is a separate plus or minus, the length |
| // gets reduced by one: |
| retval.length = refer_s - 1; |
| if( refer->attr & leading_e ) |
| { |
| // Because the sign character is LEADING, we increase the |
| // offset by one |
| retval.offset = 1; |
| } |
| } |
| for( size_t i=retval.offset; i<retval.length; i++ ) |
| { |
| // Because we are dealing with a NumericDisplay that might have |
| // the NUMERIC_DISPLAY_SIGN_BIT turned on, we need to mask it off |
| unsigned char ch = data[i]; |
| turn_sign_bit_off(&ch); |
| retval.the_characters += ch; |
| } |
| } |
| else |
| { |
| // We are set up to create the_characters; |
| if( figconst == normal_value_e ) |
| { |
| for( size_t i=retval.offset; i<retval.length; i++ ) |
| { |
| retval.the_characters += data[i]; |
| } |
| } |
| else |
| { |
| char ch=0; |
| switch( figconst ) |
| { |
| case low_value_e : |
| ch = ascii_to_internal(__gg__low_value_character); |
| break; |
| case zero_value_e : |
| ch = internal_zero; |
| break; |
| case space_value_e : |
| ch = internal_space; |
| break; |
| case quote_value_e : |
| ch = ascii_to_internal(__gg__quote_character); |
| break; |
| case high_value_e : |
| if( __gg__high_value_character == DEGENERATE_HIGH_VALUE ) |
| { |
| ch = __gg__high_value_character; |
| } |
| else |
| { |
| ch = ascii_to_internal(__gg__high_value_character); |
| } |
| break; |
| case normal_value_e: |
| // We can't get here |
| break; |
| case null_value_e: |
| break; |
| } |
| for( size_t i=retval.offset; i<retval.length; i++ ) |
| { |
| retval.the_characters += ch; |
| } |
| } |
| } |
| } |
| else |
| { |
| // THere is no field, so leave the_characters empty. |
| retval.offset = 0; |
| retval.length = 0; |
| } |
| return retval; |
| } |
| |
| static void |
| match_lengths( normalized_operand &id_target, |
| const normalized_operand &id_source) |
| { |
| char ch = id_target.the_characters[0]; |
| id_target.the_characters.clear(); |
| for(size_t i=0; i<id_source.length; i++) |
| { |
| id_target.the_characters += ch; |
| } |
| id_target.length = id_source.length; |
| } |
| |
| static void |
| the_alpha_and_omega(const normalized_operand &id_before, |
| const normalized_operand &id_after, |
| const char * &alpha, |
| const char * &omega) |
| { |
| /* The 2023 ISO description of the AFTER and BEFORE phrases of the INSPECT |
| statement is, in a word, garbled. |
| |
| IBM's COBOL for Linux 1.2 is a little better, but still a bit confusing |
| because the description for AFTER neglects to specifically state that |
| the scan starts one character to the right of the *first* occurrence of |
| the AFTER value. |
| |
| Micro Focus 9.2.5 has the advantage of being ungarbled, succinct, and |
| unambiguous. |
| |
| The BEFORE phrase modifies the character position to use as the rightmost |
| position in source for the corresponding comparison operation. Comparisons |
| in source occur only to the left of the first occurrence of delimiter. If |
| delimiter is not present in source, then the comparison proceeds as if |
| there were no BEFORE phrase. |
| |
| The AFTER phrase modifies the character position to use as the leftmost |
| position in source for the corresponding comparison operation. Comparisons |
| in source occur only to the right of the first occurrence of delimiter. |
| This character position is the one immediately to the right of the |
| rightmost character of the delimiter found. If delimiter is not found in |
| source, the INSPECT statement has no effect (no tallying or replacement |
| occurs). |
| |
| "xyzxyzAFTERxyzxyzxyzxyzBEFORExyzxyzAFTERxyzxyz" |
| ^ ^ |
| | | |
| | |-- omega |
| ----------------alpha |
| */ |
| |
| if( id_before.length ) |
| { |
| // This is the BEFORE delimiter. We look for the first occurrence of that |
| // delimiter starting at the left of id_1 |
| |
| const char *start = id_before.the_characters.c_str(); |
| const char *end = start + id_before.length; |
| const char *found = funky_find(start, end, alpha, omega); |
| if( found ) |
| { |
| // We found id_before within alpha/omega, so reduce omega |
| // to the found location. |
| omega = found; |
| // If not found, we just leave omega alone. |
| } |
| } |
| |
| if( id_after.length ) |
| { |
| // This is the AFTER delimiter. We look for the first occurrence of that |
| // delimiter in id_1 |
| |
| const char *start = id_after.the_characters.c_str(); |
| const char *end = start + id_after.length; |
| const char *found = funky_find(start, end, alpha, omega); |
| if( found ) |
| { |
| // We found id_after in the alpha/omega segment. We update alpha |
| // be the character after the id_after substring. |
| alpha = found + (end-start); |
| } |
| else |
| { |
| // We didn't find the id_after string, so we set the alpha to be |
| // omega. That means that no tally or replace operation will take |
| // because no characters will qualify. |
| alpha = omega; |
| } |
| } |
| } |
| |
| static void |
| the_alpha_and_omega_backward( const normalized_operand &id_before, |
| const normalized_operand &id_after, |
| const char * &alpha, |
| const char * &omega) |
| { |
| /* Not unlike the_alpha_and_omega(), but for handling BACKWARD. |
| |
| "xyzxyzBEFORExyzxyzAFTERxyzxyzxyzxyzBEFORExyzxyzAFTERxyzxyz" |
| ^ ^ |
| | | |
| | -- omega |
| |--------alpha |
| */ |
| |
| const char *id_1 = alpha; |
| const char *id_1_end = omega; |
| |
| if( id_before.length ) |
| { |
| // This is the BEFORE delimiter. We look for the first occurrence of it |
| // from the right end of id_1 |
| |
| const char *start = id_before.the_characters.c_str(); |
| const char *end = start + id_before.length; |
| const char *found = funky_find_backward(start, end, id_1, id_1_end); |
| if( found ) |
| { |
| // We found id_before within id_1, so change alpha to the character just |
| // to the right of BEFORE. Otherwise, we will leave alpha alone, so that |
| // it stays at the beginning of id_1 |
| alpha = found + id_before.length; |
| } |
| } |
| |
| if( id_after.length ) |
| { |
| // This is the AFTER delimiter. We look for the first occurrence in id_1 |
| |
| const char *start = id_after.the_characters.c_str(); |
| const char *end = start + id_after.length; |
| const char *found = funky_find_backward(start, end, alpha, omega); |
| if( found ) |
| { |
| // We found id_after in id_1. We update omega to be |
| // at that location. |
| omega = found; |
| } |
| else |
| { |
| // If the AFTER isn't found, we need to adjust things so that nothing |
| // happens. |
| omega = id_1; |
| } |
| } |
| } |
| |
| static |
| void |
| inspect_backward_format_1(const size_t integers[]) |
| { |
| size_t int_index = 0; |
| size_t cblc_index = 0; |
| |
| // Reference the language specification for the meanings of identifier_X |
| |
| // Pick up the number of identifier_2 loops in this INSPECT statement |
| size_t n_identifier_2 = integers[int_index++]; |
| |
| std::vector<id_2_result> id_2_results(n_identifier_2); |
| |
| // Pick up identifier_1, which is the string being inspected |
| const cblc_field_t *id1 = __gg__treeplet_1f[cblc_index]; |
| size_t id1_o = __gg__treeplet_1o[cblc_index]; |
| size_t id1_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| // normalize it, according to the language specification. |
| normalized_operand normalized_id_1 = normalize_id(id1, id1_o, id1_s); |
| |
| std::vector<comparand> comparands; |
| |
| for(size_t i=0; i<n_identifier_2; i++) |
| { |
| // For each identifier_2, we pick up its value: |
| |
| id_2_results[i].id2 = __gg__treeplet_1f [cblc_index]; |
| id_2_results[i].id2_o = __gg__treeplet_1o[cblc_index]; |
| id_2_results[i].id2_s = __gg__treeplet_1s[cblc_index]; |
| |
| cblc_index += 1; |
| id_2_results[i].result = 0; |
| |
| // For each identifier 2, there is a count of operations: |
| size_t nbounds = integers[int_index++]; |
| |
| for(size_t j=0; j<nbounds; j++ ) |
| { |
| // each operation has a bound code: |
| cbl_inspect_bound_t operation |
| = (cbl_inspect_bound_t)integers[int_index++]; |
| switch( operation ) |
| { |
| case bound_characters_e: |
| { |
| // We are counting characters. There is no identifier-3, |
| // but we we hard-code the length to one to represent a |
| // single character. |
| comparand next_comparand = {}; |
| next_comparand.id_2_index = i; |
| next_comparand.operation = operation; |
| next_comparand.identifier_3.length = 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f [cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f [cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| next_comparand.alpha |
| = normalized_id_1.the_characters.c_str(); |
| |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| the_alpha_and_omega_backward( normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| comparands.push_back(next_comparand); |
| break; |
| } |
| default: |
| { |
| // We have some number of identifer-3 values, |
| // each with possible PHRASE1 modifiers. |
| size_t pair_count = integers[int_index++]; |
| |
| // We need to build up pair_count comparand structures: |
| |
| for(size_t k=0; k<pair_count; k++) |
| { |
| comparand next_comparand = {}; |
| next_comparand.id_2_index = i; |
| next_comparand.operation = operation; |
| |
| const cblc_field_t *id3 = __gg__treeplet_1f[cblc_index]; |
| size_t id3_o = __gg__treeplet_1o[cblc_index]; |
| size_t id3_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f[cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f[cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| next_comparand.identifier_3 |
| = normalize_id(id3, id3_o, id3_s); |
| |
| next_comparand.alpha |
| = normalized_id_1.the_characters.c_str(); |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| the_alpha_and_omega_backward( normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| next_comparand.leading = true; |
| next_comparand.leading_count = 0; |
| comparands.push_back(next_comparand); |
| } |
| } |
| } |
| } |
| } |
| |
| // We are now ready to walk through identifier-1, character by |
| // character, checking each of the comparands for a match: |
| |
| // We are now set up to accomplish the data flow described |
| // in the language specification. We loop through the |
| // the character positions in normalized_id_1: |
| const char *leftmost = normalized_id_1.the_characters.c_str(); |
| const char *rightmost = leftmost + normalized_id_1.length; |
| const char *the_end_of_the_world = rightmost; |
| |
| while( leftmost < rightmost ) |
| { |
| rightmost -= 1; |
| // We look at the rightmost position. If that position is within the |
| // alpha-to-omega qualified range, we check all possible matches: |
| |
| for(size_t k=0; k<comparands.size(); k++) |
| { |
| if( rightmost < comparands[k].alpha ) |
| { |
| // This can't be a match, because rightmost is |
| // to the left of the comparand's alpha. |
| continue; |
| } |
| if( rightmost + comparands[k].identifier_3.length > comparands[k].omega ) |
| { |
| // This can't be a match, because the rightmost |
| // character of the comparand falls to the right |
| // of the comparand's omega |
| continue; |
| } |
| if( rightmost + comparands[k].identifier_3.length > the_end_of_the_world ) |
| { |
| // This can't be a match, because the rightmost character of the |
| // comparand falls past the new edge of id_1 established by a prior |
| // match. |
| continue; |
| } |
| // A match is theoretically possible, because all |
| // the characters of the comparand fall between |
| // alpha and omega: |
| bool possible_match = true; |
| |
| if( comparands[k].operation != bound_characters_e ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] != rightmost[m] ) |
| { |
| possible_match = false; |
| break; |
| } |
| } |
| } |
| if( possible_match ) |
| { |
| // The characters of the comparand match the |
| // characters at rightmost. |
| bool match = false; |
| switch( comparands[k].operation ) |
| { |
| case bound_first_e: |
| // This can't happen in a FORMAT_1 |
| warnx("The compiler goofed: " |
| "INSPECT FORMAT 1 " |
| "shouldn't have " |
| "bound_first_e"); |
| abort(); |
| break; |
| |
| case bound_characters_e: |
| match = 1; |
| break; |
| |
| case bound_all_e: |
| { |
| // We have a match. |
| match = true; |
| break; |
| } |
| |
| case bound_leading_e: |
| { |
| // We have a match at rightmost. But we need to figure out if this |
| // particular match is valid for LEADING. |
| |
| if( comparands[k].leading ) |
| { |
| if( rightmost + comparands[k].identifier_3.length |
| == comparands[k].omega) |
| { |
| // This means that the match here is just the latest of a |
| // string of LEADING matches that started at .omega |
| comparands[k].leading_count += 1; |
| match = true; |
| } |
| } |
| break; |
| } |
| |
| case bound_trailing_e: |
| { |
| // We have a match at rightmost. |
| // |
| // We want to know if this is a trailing match. For that to be, |
| // all of the possible matches from here leftward to the alpha have |
| // to be true as well: |
| |
| if( (rightmost - comparands[k].alpha ) |
| % comparands[k].identifier_3.length == 0 ) |
| { |
| // The remaining number of characters is correct for a match. |
| // Keep checking. |
| |
| // Assume a match until we learn otherwise: |
| match = true; |
| const char *local_left = rightmost; |
| local_left -= comparands[k].identifier_3.length; |
| while( local_left >= comparands[k].alpha ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] |
| != local_left[m] ) |
| { |
| // We have a mismatched character, so no trailing match is |
| // possible |
| match = false; |
| break; |
| } |
| } |
| local_left -= comparands[k].identifier_3.length; |
| } |
| } |
| break; |
| } |
| } |
| |
| if( match ) |
| { |
| // We have a match at rightmost: |
| // Bump the result counter |
| id_2_results[comparands[k].id_2_index].result += 1; |
| |
| // Because we are scanning from right to left, we have to drag |
| // the goalpost along with us to ensure that following |
| // comparisions don't spill over into the characters we just matched. |
| the_end_of_the_world = rightmost; |
| |
| break; |
| } |
| } |
| else |
| { |
| // We are within alpha/omega, but there was no |
| // match, which permanently disqualifies the |
| // possibility of LEADING |
| comparands[k].leading = false; |
| } |
| } |
| } |
| |
| // Add our results to the identifier_2 values: |
| |
| for(size_t i = 0; i<id_2_results.size(); i++) |
| { |
| int rdigits; |
| __int128 id_2_value |
| = __gg__binary_value_from_qualified_field(&rdigits, |
| id_2_results[i].id2, |
| id_2_results[i].id2_o, |
| id_2_results[i].id2_s); |
| while(rdigits--) |
| { |
| id_2_value /= 10.0; |
| } |
| |
| // Accumulate what we've found into it |
| id_2_value += id_2_results[i].result; |
| |
| // And put it back: |
| __gg__int128_to_qualified_field(id_2_results[i].id2, |
| id_2_results[i].id2_o, |
| id_2_results[i].id2_s, |
| id_2_value, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__inspect_format_1(int backward, size_t integers[]) |
| { |
| if( backward ) |
| { |
| return inspect_backward_format_1(integers); |
| } |
| |
| size_t int_index = 0; |
| size_t cblc_index = 0; |
| |
| // Reference the language specification for the meanings of identifier_X |
| |
| // Pick up the number of identifier_2 loops in this INSPECT statement |
| size_t n_identifier_2 = integers[int_index++]; |
| |
| std::vector<id_2_result> id_2_results(n_identifier_2); |
| |
| // Pick up identifier_1, which is the string being inspected |
| const cblc_field_t *id1 = __gg__treeplet_1f[cblc_index]; |
| size_t id1_o = __gg__treeplet_1o[cblc_index]; |
| size_t id1_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| // normalize it, according to the language specification. |
| normalized_operand normalized_id_1 |
| = normalize_id(id1, id1_o, id1_s); |
| |
| std::vector<comparand> comparands; |
| |
| for(size_t i=0; i<n_identifier_2; i++) |
| { |
| // For each identifier_2, we pick up its value: |
| |
| id_2_results[i].id2 = __gg__treeplet_1f [cblc_index]; |
| id_2_results[i].id2_o = __gg__treeplet_1o[cblc_index]; |
| id_2_results[i].id2_s = __gg__treeplet_1s[cblc_index]; |
| |
| cblc_index += 1; |
| id_2_results[i].result = 0; |
| |
| // For each identifier 2, there is a count of operations: |
| size_t nbounds = integers[int_index++]; |
| |
| for(size_t j=0; j<nbounds; j++ ) |
| { |
| // each operation has a bound code: |
| cbl_inspect_bound_t operation |
| = (cbl_inspect_bound_t)integers[int_index++]; |
| switch( operation ) |
| { |
| case bound_characters_e: |
| { |
| // We are counting characters. There is no identifier-3, |
| // but we we hard-code the length to one to represent a |
| // single character. |
| comparand next_comparand = {}; |
| next_comparand.id_2_index = i; |
| next_comparand.operation = operation; |
| next_comparand.identifier_3.length = 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f [cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f [cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| next_comparand.alpha |
| = normalized_id_1.the_characters.c_str(); |
| |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| the_alpha_and_omega(normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| comparands.push_back(next_comparand); |
| break; |
| } |
| default: |
| { |
| // We have some number of identifer-3 values, |
| // each with possible PHRASE1 modifiers. |
| size_t pair_count = integers[int_index++]; |
| |
| // We need to build up pair_count comparand structures: |
| |
| for(size_t k=0; k<pair_count; k++) |
| { |
| comparand next_comparand = {}; |
| next_comparand.id_2_index = i; |
| next_comparand.operation = operation; |
| |
| const cblc_field_t *id3 = __gg__treeplet_1f[cblc_index]; |
| size_t id3_o = __gg__treeplet_1o[cblc_index]; |
| size_t id3_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f[cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f[cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| next_comparand.identifier_3 |
| = normalize_id(id3, id3_o, id3_s); |
| |
| next_comparand.alpha |
| = normalized_id_1.the_characters.c_str(); |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| the_alpha_and_omega(normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| next_comparand.leading = true; |
| next_comparand.leading_count = 0; |
| comparands.push_back(next_comparand); |
| } |
| } |
| } |
| } |
| } |
| |
| // We are now ready to walk through identifier-1, character by |
| // character, checking each of the comparands for a match: |
| |
| // We are now set up to accomplish the data flow described |
| // in the language specification. We loop through the |
| // the character positions in normalized_id_1: |
| const char *leftmost |
| = normalized_id_1.the_characters.c_str(); |
| const char *rightmost |
| = leftmost + normalized_id_1.length; |
| |
| while( leftmost < rightmost ) |
| { |
| // For each leftmost position, we check each of the |
| // pairs: |
| |
| for(size_t k=0; k<comparands.size(); k++) |
| { |
| if( leftmost < comparands[k].alpha ) |
| { |
| // This can't be a match, because leftmost is |
| // to the left of the comparand's alpha. |
| continue; |
| } |
| if( leftmost + comparands[k].identifier_3.length > comparands[k].omega ) |
| { |
| // This can't be a match, because the rightmost |
| // character of the comparand falls to the right |
| // of the comparand's omega |
| continue; |
| } |
| // A match is theoretically possible, because all |
| // the characters of the comparand fall between |
| // alpha and omega: |
| bool possible_match = true; |
| |
| if( comparands[k].operation != bound_characters_e ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] != leftmost[m] ) |
| { |
| possible_match = false; |
| break; |
| } |
| } |
| } |
| if( possible_match ) |
| { |
| // The characters of the comparand match the |
| // characters at leftmost. |
| bool match = false; |
| switch( comparands[k].operation ) |
| { |
| case bound_first_e: |
| // This can't happen in a FORMAT_1 |
| warnx("The compiler goofed: " |
| "INSPECT FORMAT 1 " |
| "shouldn't have " |
| "bound_first_e"); |
| abort(); |
| break; |
| |
| case bound_characters_e: |
| match = 1; |
| break; |
| |
| case bound_all_e: |
| { |
| // We have a match. |
| match = true; |
| break; |
| } |
| |
| case bound_leading_e: |
| { |
| // We have a match at leftmost. But we need to figure out if this |
| // particular match is valid for LEADING. |
| |
| // Hang onto your hat. This is delightfully clever. |
| // |
| // This position is LEADING if: |
| // 1) .leading is still true |
| // 2) leftmost / (length_of_comparand ) = current_count |
| // |
| // I get chills every time I look at that. |
| if( comparands[k].leading ) |
| { |
| // So far, so good. |
| size_t count = (leftmost - comparands[k].alpha) |
| / comparands[k].identifier_3.length; |
| if( count == comparands[k].leading_count ) |
| { |
| // This means that the match here is just the latest of a |
| // string of LEADING matches that started at .alpha |
| comparands[k].leading_count += 1; |
| match = true; |
| } |
| } |
| break; |
| } |
| |
| case bound_trailing_e: |
| { |
| // We have a match at leftmost. |
| // |
| // We want to know if this is a trailing match. For that to be, |
| // all of the possible matches from here to the omega have to be |
| // true as well: |
| |
| if( (comparands[k].omega-leftmost) |
| % comparands[k].identifier_3.length == 0 ) |
| { |
| // The remaining number of characters is correct for a match. |
| // Keep checking. |
| |
| // Assume a match until we learn otherwise: |
| match = true; |
| const char *local_left = leftmost; |
| local_left += comparands[k].identifier_3.length; |
| while( local_left < comparands[k].omega ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] |
| != local_left[m] ) |
| { |
| // We have a mismatched character, so no trailing match is |
| // possible |
| match = false; |
| break; |
| } |
| } |
| local_left += comparands[k].identifier_3.length; |
| } |
| } |
| break; |
| } |
| } |
| |
| if( match ) |
| { |
| // We have a match at leftmost: |
| |
| // Bump the result counter |
| id_2_results[comparands[k].id_2_index].result += 1; |
| |
| // Adjust the leftmost pointer to point to |
| // the rightmost character of the matched |
| // string, keeping in mind that it will be |
| // bumped again after we break out of the |
| // k<pair_count loop: |
| leftmost += comparands[k].identifier_3.length - 1; |
| break; |
| } |
| } |
| else |
| { |
| // We are within alpha/omega, but there was no |
| // match, which permanently disqualifies the |
| // possibility of LEADING |
| comparands[k].leading = false; |
| } |
| } |
| leftmost += 1; |
| } |
| |
| // Add our results to the identifier_2 values: |
| |
| |
| for(size_t i = 0; i<id_2_results.size(); i++) |
| { |
| int rdigits; |
| __int128 id_2_value |
| = __gg__binary_value_from_qualified_field(&rdigits, |
| id_2_results[i].id2, |
| id_2_results[i].id2_o, |
| id_2_results[i].id2_s); |
| while(rdigits--) |
| { |
| id_2_value /= 10.0; |
| } |
| |
| // Accumulate what we've found into it |
| id_2_value += id_2_results[i].result; |
| |
| // And put it back: |
| __gg__int128_to_qualified_field(id_2_results[i].id2, |
| id_2_results[i].id2_o, |
| id_2_results[i].id2_s, |
| id_2_value, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| } |
| |
| static |
| void |
| inspect_backward_format_2(const size_t integers[]) |
| { |
| size_t int_index = 0; |
| size_t cblc_index = 0; |
| |
| // Reference the language specification for the meanings of identifier_X |
| |
| // Pick up identifier_1, which is the string being inspected |
| cblc_field_t *id1 = __gg__treeplet_1f[cblc_index]; |
| size_t id1_o = __gg__treeplet_1o[cblc_index]; |
| size_t id1_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| // normalize it, according to the language specification. |
| normalized_operand normalized_id_1 |
| = normalize_id(id1, id1_o, id1_s); |
| |
| std::vector<comparand> comparands; |
| |
| // Pick up the count of operations: |
| size_t nbounds = integers[int_index++]; |
| |
| for(size_t j=0; j<nbounds; j++ ) |
| { |
| // each operation has a bound code: |
| cbl_inspect_bound_t operation = (cbl_inspect_bound_t)integers[int_index++]; |
| switch( operation ) |
| { |
| case bound_characters_e: |
| { |
| comparand next_comparand = {}; |
| next_comparand.operation = operation; |
| |
| const cblc_field_t *id5 = __gg__treeplet_1f[cblc_index]; |
| size_t id5_o = __gg__treeplet_1o[cblc_index]; |
| size_t id5_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f[cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f [cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| next_comparand.identifier_5 |
| = normalize_id(id5, id5_o, id5_s); |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| // Because this is a CHARACTER operation, the lengths of |
| // identifier-3 and identifier-5 should be one. Let's avoid the |
| // chaos that will otherwise ensue should the lengths *not* be |
| // one. |
| next_comparand.identifier_3.length = 1; |
| next_comparand.identifier_5.length = 1; |
| |
| next_comparand.alpha = normalized_id_1.the_characters.c_str(); |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| the_alpha_and_omega_backward( normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| comparands.push_back(next_comparand); |
| break; |
| } |
| default: |
| { |
| // We have some number of identifer-3/identifier-5 pairs, |
| // each with possible PHRASE1 modifiers. |
| size_t pair_count = integers[int_index++]; |
| |
| for(size_t k=0; k<pair_count; k++) |
| { |
| comparand next_comparand = {}; |
| next_comparand.operation = operation; |
| |
| const cblc_field_t *id3 = __gg__treeplet_1f[cblc_index]; |
| size_t id3_o = __gg__treeplet_1o[cblc_index]; |
| size_t id3_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id5 = __gg__treeplet_1f[cblc_index]; |
| size_t id5_o = __gg__treeplet_1o[cblc_index]; |
| size_t id5_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f[cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f[cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| next_comparand.identifier_3 = normalize_id(id3, id3_o, id3_s); |
| next_comparand.identifier_5 = normalize_id(id5, id5_o, id5_s); |
| |
| // Identifiers 3 and 5 have to be the same length. But |
| // but either, or both, can be figurative constants. If |
| // they are figurative constants, they start off with a |
| // length of one. We will expand figurative constants to |
| // match the length of the other one: |
| |
| if( id3->attr & FIGCONST_MASK ) |
| { |
| match_lengths( next_comparand.identifier_3, |
| next_comparand.identifier_5); |
| } |
| else if( id5->attr & FIGCONST_MASK ) |
| { |
| match_lengths( next_comparand.identifier_5, |
| next_comparand.identifier_3); |
| } |
| |
| next_comparand.alpha |
| = normalized_id_1.the_characters.c_str(); |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| the_alpha_and_omega_backward( normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| next_comparand.leading = true; |
| next_comparand.leading_count = 0; |
| next_comparand.first = true; |
| comparands.push_back(next_comparand); |
| } |
| } |
| } |
| } |
| |
| const char *leftmost = normalized_id_1.the_characters.c_str(); |
| const char *rightmost = leftmost + normalized_id_1.length; |
| const char *the_end_of_the_world = rightmost; |
| |
| while( leftmost < rightmost ) |
| { |
| rightmost -= 1; |
| // We look at the rightmost position. If that position is within the |
| // alpha-to-omega qualified range, we check all possible matches: |
| |
| for(size_t k=0; k<comparands.size(); k++) |
| { |
| if( rightmost < comparands[k].alpha ) |
| { |
| // This can't be a match, because rightmost is |
| // to the left of the comparand's alpha. |
| continue; |
| } |
| if( rightmost + comparands[k].identifier_3.length > comparands[k].omega ) |
| { |
| // This can't be a match, because the rightmost |
| // character of the comparand falls to the right |
| // of the comparand's omega |
| continue; |
| } |
| if( rightmost + comparands[k].identifier_3.length > the_end_of_the_world ) |
| { |
| // This can't be a match, because the rightmost character of the |
| // comparand falls past the new edge of id_1 established by a prior |
| // match. |
| continue; |
| } |
| // A match is theoretically possible, because all |
| // the characters of the comparand fall between |
| // alpha and omega: |
| bool possible_match = true; |
| |
| if( comparands[k].operation != bound_characters_e ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] != rightmost[m] ) |
| { |
| possible_match = false; |
| break; |
| } |
| } |
| } |
| if( possible_match ) |
| { |
| // The characters of the comparand match the |
| // characters at rightmost. |
| bool match = false; |
| switch( comparands[k].operation ) |
| { |
| case bound_first_e: |
| // This can't happen in a FORMAT_2 |
| warnx("The compiler goofed: " |
| "INSPECT FORMAT 2 " |
| "shouldn't have " |
| "bound_first_e"); |
| abort(); |
| break; |
| |
| case bound_characters_e: |
| match = 1; |
| break; |
| |
| case bound_all_e: |
| { |
| // We have a match. |
| match = true; |
| break; |
| } |
| |
| case bound_leading_e: |
| { |
| // We have a match at rightmost. But we need to figure out if this |
| // particular match is valid for LEADING. |
| |
| if( comparands[k].leading ) |
| { |
| if( rightmost |
| + comparands[k].identifier_3.length |
| + comparands[k].leading_count |
| == comparands[k].omega) |
| { |
| // This means that the match here is just the latest of a |
| // string of LEADING matches that started at .omega |
| comparands[k].leading_count += 1; |
| match = true; |
| } |
| } |
| break; |
| } |
| |
| case bound_trailing_e: |
| { |
| // We have a match at rightmost. |
| // |
| // We want to know if this is a trailing match. For that to be, |
| // all of the possible matches from here leftward to the alpha have |
| // to be true as well: |
| |
| if( (rightmost - comparands[k].alpha ) |
| % comparands[k].identifier_3.length == 0 ) |
| { |
| // The remaining number of characters is correct for a match. |
| // Keep checking. |
| |
| // Assume a match until we learn otherwise: |
| match = true; |
| const char *local_left = rightmost; |
| local_left -= comparands[k].identifier_3.length; |
| while( local_left >= comparands[k].alpha ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] |
| != local_left[m] ) |
| { |
| // We have a mismatched character, so no trailing match is |
| // possible |
| match = false; |
| break; |
| } |
| } |
| local_left -= comparands[k].identifier_3.length; |
| } |
| } |
| break; |
| } |
| } |
| |
| if( match ) |
| { |
| // We have a match at rightmost. We need to |
| // to replace the characters in normalized_id_1 |
| // with the characters from normalized_id_5 |
| //fprintf(stderr, "Rule: %ld %p %s\n", k+1, rightmost, rightmost); |
| |
| size_t index = rightmost - normalized_id_1.the_characters.c_str(); |
| for( size_t l = 0; |
| l < comparands[k].identifier_5.length; |
| l++ ) |
| { |
| char ch = comparands[k].identifier_5. |
| the_characters[l]; |
| normalized_id_1.the_characters[index++] = ch; |
| } |
| |
| the_end_of_the_world = rightmost; |
| |
| break; |
| } |
| } |
| else |
| { |
| comparands[k].leading = false; |
| } |
| } |
| } |
| |
| // Here is where we take the characters from normalized_id_1 and put them |
| // back into identifier_1. There is some special processing to make sure |
| // an embedded sign in a NumericDisplay survives the processing. |
| unsigned char *id1_data = id1->data + id1_o; |
| int index_dest = normalized_id_1.offset; |
| if( id1->type == FldNumericDisplay ) |
| { |
| for(size_t i=0; i<normalized_id_1.length; i++) |
| { |
| id1_data[index_dest] = normalized_id_1.the_characters[i]; |
| if( is_sign_bit_on (normalized_id_1.the_characters[i]) ) |
| { |
| turn_sign_bit_on(&id1_data[index_dest]); |
| } |
| else |
| { |
| turn_sign_bit_off(&id1_data[index_dest]); |
| } |
| index_dest += 1; |
| } |
| } |
| else |
| { |
| for(size_t i=0; i<normalized_id_1.length; i++) |
| { |
| id1_data[index_dest++] = normalized_id_1.the_characters[i]; |
| } |
| } |
| return; |
| } |
| |
| extern "C" |
| void |
| __gg__inspect_format_2(int backward, size_t integers[]) |
| { |
| if( backward ) |
| { |
| return inspect_backward_format_2(integers); |
| } |
| size_t int_index = 0; |
| size_t cblc_index = 0; |
| |
| // Reference the language specification for the meanings of identifier_X |
| |
| // Pick up identifier_1, which is the string being inspected |
| cblc_field_t *id1 = __gg__treeplet_1f[cblc_index]; |
| size_t id1_o = __gg__treeplet_1o[cblc_index]; |
| size_t id1_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| // normalize it, according to the language specification. |
| normalized_operand normalized_id_1 |
| = normalize_id(id1, id1_o, id1_s); |
| |
| std::vector<comparand> comparands; |
| |
| // Pick up the count of operations: |
| size_t nbounds = integers[int_index++]; |
| |
| for(size_t j=0; j<nbounds; j++ ) |
| { |
| // each operation has a bound code: |
| cbl_inspect_bound_t operation |
| = (cbl_inspect_bound_t)integers[int_index++]; |
| switch( operation ) |
| { |
| case bound_characters_e: |
| { |
| comparand next_comparand = {} ; |
| next_comparand.operation = operation; |
| |
| const cblc_field_t *id5 = __gg__treeplet_1f[cblc_index]; |
| size_t id5_o = __gg__treeplet_1o[cblc_index]; |
| size_t id5_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f[cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f [cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| next_comparand.identifier_5 |
| = normalize_id(id5, id5_o, id5_s); |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| // Because this is a CHARACTER operation, the lengths of |
| // identifier-3 and identifier-5 should be one. Let's avoid the |
| // chaos that will otherwise ensue should the lengths *not* be |
| // one. |
| next_comparand.identifier_3.length = 1; |
| next_comparand.identifier_5.length = 1; |
| |
| next_comparand.alpha = normalized_id_1.the_characters.c_str(); |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| the_alpha_and_omega(normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| comparands.push_back(next_comparand); |
| break; |
| } |
| default: |
| { |
| // We have some number of identifer-3/identifier-5 pairs, |
| // each with possible PHRASE1 modifiers. |
| size_t pair_count = integers[int_index++]; |
| |
| for(size_t k=0; k<pair_count; k++) |
| { |
| comparand next_comparand = {}; |
| next_comparand.operation = operation; |
| |
| const cblc_field_t *id3 = __gg__treeplet_1f[cblc_index]; |
| size_t id3_o = __gg__treeplet_1o[cblc_index]; |
| size_t id3_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id5 = __gg__treeplet_1f[cblc_index]; |
| size_t id5_o = __gg__treeplet_1o[cblc_index]; |
| size_t id5_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_before = __gg__treeplet_1f[cblc_index]; |
| size_t id4_before_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_before_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| const cblc_field_t *id4_after = __gg__treeplet_1f[cblc_index]; |
| size_t id4_after_o = __gg__treeplet_1o[cblc_index]; |
| size_t id4_after_s = __gg__treeplet_1s[cblc_index]; |
| cblc_index += 1; |
| |
| next_comparand.identifier_3 = normalize_id(id3, id3_o, id3_s); |
| next_comparand.identifier_5 = normalize_id(id5, id5_o, id5_s); |
| |
| // Identifiers 3 and 5 have to be the same length. But |
| // but either, or both, can be figurative constants. If |
| // they are figurative constants, they start off with a |
| // length of one. We will expand figurative constants to |
| // match the length of the other one: |
| |
| if( id3->attr & FIGCONST_MASK ) |
| { |
| match_lengths( next_comparand.identifier_3, |
| next_comparand.identifier_5); |
| } |
| else if( id5->attr & FIGCONST_MASK ) |
| { |
| match_lengths( next_comparand.identifier_5, |
| next_comparand.identifier_3); |
| } |
| |
| next_comparand.alpha |
| = normalized_id_1.the_characters.c_str(); |
| next_comparand.omega |
| = next_comparand.alpha + normalized_id_1.length; |
| |
| normalized_operand normalized_id_4_before |
| = normalize_id(id4_before, id4_before_o, id4_before_s); |
| normalized_operand normalized_id_4_after |
| = normalize_id(id4_after, id4_after_o, id4_after_s); |
| |
| the_alpha_and_omega(normalized_id_4_before, |
| normalized_id_4_after, |
| next_comparand.alpha, |
| next_comparand.omega); |
| next_comparand.leading = true; |
| next_comparand.leading_count = 0; |
| next_comparand.first = true; |
| comparands.push_back(next_comparand); |
| } |
| } |
| } |
| } |
| |
| // We are now set up to accomplish the data flow described |
| // in the language specification. We loop through the |
| // the character positions in normalized_id_1: |
| const char *leftmost |
| = normalized_id_1.the_characters.c_str(); |
| const char *rightmost |
| = leftmost + normalized_id_1.length; |
| |
| while( leftmost < rightmost ) |
| { |
| // For each leftmost position, we check each of the |
| // comparands |
| |
| for(size_t k=0; k<comparands.size(); k++) |
| { |
| if( leftmost < comparands[k].alpha ) |
| { |
| // This can't be a match, because leftmost is |
| // to the left of the comparand's alpha. |
| continue; |
| } |
| if( leftmost + comparands[k].identifier_3.length |
| > comparands[k].omega ) |
| { |
| // This can't be a match, because the rightmost |
| // character of the comparand falls to the right |
| // of the comparand's omega |
| continue; |
| } |
| // A match is theoretically possible, because all |
| // the characters of the comparand fall between |
| // alpha and omega: |
| bool possible_match = true; |
| if( comparands[k].operation != bound_characters_e) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] |
| != leftmost[m] ) |
| { |
| possible_match = false; |
| break; |
| } |
| } |
| } |
| if( possible_match ) |
| { |
| // The characters of the comparand match the |
| // characters at leftmost. See if further processing is |
| // indicated: |
| |
| bool match = false; |
| switch( comparands[k].operation ) |
| { |
| case bound_characters_e: |
| match = true; |
| break; |
| |
| case bound_first_e: |
| if( comparands[k].first ) |
| { |
| match = true; |
| comparands[k].first = false; |
| } |
| break; |
| |
| case bound_all_e: |
| { |
| // We have a match. |
| match = true; |
| break; |
| } |
| |
| case bound_leading_e: |
| { |
| // We have a match at leftmost. But we need to figure out if this |
| // particular match is valid for LEADING. |
| |
| // Hang onto your hat. This is delightfully clever. |
| // |
| // This position is LEADING if: |
| // 1) .leading is still true |
| // 2) leftmost / (length_of_comparand ) = current_count |
| // |
| // I get chills every time I look at that. |
| if( comparands[k].leading ) |
| { |
| // So far, so good. |
| size_t count = (leftmost - comparands[k].alpha) |
| / comparands[k].identifier_3.length; |
| if( count == comparands[k].leading_count ) |
| { |
| // This means that the match here is just the latest of a |
| // string of LEADING matches that started at .alpha |
| comparands[k].leading_count += 1; |
| match = true; |
| } |
| } |
| break; |
| } |
| |
| case bound_trailing_e: |
| { |
| // We have a match at leftmost. |
| // |
| // We want to know if this is a trailing match. For that to be, |
| // all of the possible matches from here to the omega have to be |
| // true as well: |
| |
| if( (comparands[k].omega-leftmost) |
| % comparands[k].identifier_3.length == 0 ) |
| { |
| // The remaining number of characters is correct for a match. |
| // Keep checking. |
| |
| // Assume a match until we learn otherwise: |
| match = true; |
| const char *local_left = leftmost; |
| local_left += comparands[k].identifier_3.length; |
| while( local_left < comparands[k].omega ) |
| { |
| for(size_t m=0; m<comparands[k].identifier_3.length; m++) |
| { |
| if( comparands[k].identifier_3.the_characters[m] |
| != local_left[m] ) |
| { |
| // We have a mismatched character, so no trailing match is |
| // possible |
| match = false; |
| break; |
| } |
| } |
| local_left += comparands[k].identifier_3.length; |
| } |
| } |
| break; |
| } |
| } |
| if( match ) |
| { |
| // We have a match at leftmost. We need to |
| // to replace the characters in normalized_id_1 |
| // with the characters from normalized_id_5 |
| |
| size_t index = leftmost |
| - normalized_id_1.the_characters.c_str(); |
| for( size_t l = 0; |
| l < comparands[k].identifier_5.length; |
| l++ ) |
| { |
| char ch = comparands[k].identifier_5. |
| the_characters[l]; |
| normalized_id_1.the_characters[index++] = ch; |
| } |
| // Adjust the leftmost pointer to point to |
| // the rightmost character of the matched |
| // string, keeping in mind that it will be |
| // bumped again after we break out of the |
| // k<pair_count loop: |
| leftmost += comparands[k].identifier_3.length - 1; |
| break; |
| } |
| } |
| else |
| { |
| comparands[k].leading = false; |
| } |
| } |
| leftmost += 1; |
| } |
| |
| // Here is where we take the characters from normalized_id_1 and put them |
| // back into identifier_1. There is some special processing to make sure |
| // an embedded sign in a NumericDisplay survives the processing. |
| unsigned char *id1_data = id1->data + id1_o; |
| int index_dest = normalized_id_1.offset; |
| if( id1->type == FldNumericDisplay ) |
| { |
| for(size_t i=0; i<normalized_id_1.length; i++) |
| { |
| id1_data[index_dest] = normalized_id_1.the_characters[i]; |
| if( is_sign_bit_on (normalized_id_1.the_characters[i]) ) |
| { |
| turn_sign_bit_on(&id1_data[index_dest]); |
| } |
| else |
| { |
| turn_sign_bit_off(&id1_data[index_dest]); |
| } |
| index_dest += 1; |
| } |
| } |
| else |
| { |
| for(size_t i=0; i<normalized_id_1.length; i++) |
| { |
| id1_data[index_dest++] = normalized_id_1.the_characters[i]; |
| } |
| } |
| return; |
| } |
| |
| extern "C" |
| void |
| __gg__inspect_format_4( int backward, |
| cblc_field_t *input, |
| size_t input_offset, |
| size_t input_size, |
| cblc_field_t *original, |
| size_t original_offset, |
| size_t original_size, |
| cblc_field_t *replacement, |
| size_t replacement_offset, |
| size_t replacement_size, |
| cblc_field_t *after, |
| size_t after_offset, |
| size_t after_size, |
| cblc_field_t *before, |
| size_t before_offset, |
| size_t before_size |
| ) |
| { |
| static size_t psz_input_size = MINIMUM_ALLOCATION_SIZE; |
| static size_t psz_original_size = MINIMUM_ALLOCATION_SIZE; |
| static size_t psz_replacement_size = MINIMUM_ALLOCATION_SIZE; |
| static size_t psz_after_size = MINIMUM_ALLOCATION_SIZE; |
| static size_t psz_before_size = MINIMUM_ALLOCATION_SIZE; |
| static size_t psz_figstring_size = MINIMUM_ALLOCATION_SIZE; |
| |
| static char *psz_input = static_cast<char *>(malloc(psz_input_size )); |
| static char *psz_original = static_cast<char *>(malloc(psz_original_size )); |
| static char *psz_replacement = static_cast<char *>(malloc(psz_replacement_size)); |
| static char *psz_after = static_cast<char *>(malloc(psz_after_size )); |
| static char *psz_before = static_cast<char *>(malloc(psz_before_size )); |
| static char *psz_figstring = static_cast<char *>(malloc(psz_figstring_size )); |
| |
| bool all = replacement_size == (size_t)(-1LL); |
| if( all ) |
| { |
| replacement_size = psz_original_size; |
| } |
| |
| psz_input = format_for_display_local(&psz_input , &psz_input_size , input , input_offset , input_size , 0); |
| psz_original = format_for_display_local(&psz_original , &psz_original_size , original , original_offset , original_size , 0); |
| psz_replacement = format_for_display_local(&psz_replacement, &psz_replacement_size, replacement, replacement_offset, replacement_size, 0); |
| psz_after = format_for_display_local(&psz_after , &psz_after_size , after , after_offset , after_size , 0); |
| psz_before = format_for_display_local(&psz_before , &psz_before_size , before , before_offset , before_size , 0); |
| |
| if( all ) |
| { |
| memset(psz_replacement, *(replacement->data+replacement_offset), replacement_size); |
| } |
| |
| cbl_figconst_t figconst = |
| (cbl_figconst_t)(replacement->attr & FIGCONST_MASK); |
| if( figconst ) |
| { |
| size_t figchars = strlen(psz_input)+1; |
| __gg__realloc_if_necessary(&psz_figstring, &psz_figstring_size, figchars); |
| char figchar = '\0'; |
| switch( figconst ) |
| { |
| case normal_value_e: |
| abort(); |
| break; |
| case low_value_e : |
| figchar = __gg__low_value_character; |
| break; |
| case zero_value_e : |
| figchar = internal_0; |
| break; |
| case space_value_e : |
| figchar = internal_space; |
| break; |
| case quote_value_e : |
| figchar = ascii_to_internal(__gg__quote_character); |
| break; |
| case high_value_e : |
| figchar = __gg__high_value_character; |
| break; |
| case null_value_e: |
| break; |
| } |
| memset(psz_figstring, figchar, figchars-1); |
| psz_figstring[figchars] = '\0'; |
| psz_replacement = psz_figstring; |
| } |
| |
| // Use a simple map to make this O(N), rather than an O(N-squared), |
| // computational complexity |
| static const unsigned char map_init[256] = |
| { |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, |
| 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, |
| 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, |
| 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, |
| 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, |
| 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, |
| 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, |
| 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, |
| 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, |
| 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, |
| 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, |
| 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, |
| 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, |
| 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, |
| 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, |
| 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff |
| }; |
| unsigned char map[256]; |
| unsigned char replaced[256]; |
| |
| memcpy(map, map_init, 256); |
| memset(replaced, 0, 256); |
| |
| for(size_t i=0; i<strlen(psz_original); i++) |
| { |
| if( !replaced[(unsigned char )psz_original[i]] ) |
| { |
| // The rule is, if the same character appears more than once in the |
| // original (which is identifier-6), then the first occurrence of the |
| // character is used for replacement |
| map[ (unsigned char )psz_original[i] ] = (unsigned char )psz_replacement[i]; |
| replaced[(unsigned char )psz_original[i]] = 1; |
| } |
| } |
| |
| char *pstart = NULL; |
| const char *pend = NULL; |
| if( backward ) |
| { |
| if( strlen(psz_before) ) |
| { |
| size_t nfound = std::string(psz_input).rfind(psz_before); |
| if( nfound == std::string::npos ) |
| { |
| // The BEFORE string isn't in the input, so we will scan from |
| // the leftmost character |
| pstart = psz_input; |
| } |
| else |
| { |
| pstart = psz_input + nfound; |
| if( !pstart ) |
| { |
| pstart = psz_input; |
| } |
| pstart += strlen(psz_before); |
| } |
| } |
| else |
| { |
| pstart = psz_input; |
| } |
| |
| if( strlen(psz_after) ) |
| { |
| size_t nfound = std::string(psz_input).rfind(psz_after); |
| if( nfound == std::string::npos ) |
| { |
| nfound = strlen(psz_input); |
| } |
| pend = psz_input + nfound; |
| } |
| if( !pend ) |
| { |
| pend = psz_input+strlen(psz_input); |
| } |
| } |
| else |
| { |
| if( strlen(psz_after) ) |
| { |
| pstart = strstr(psz_input, psz_after); |
| } |
| if( !pstart ) |
| { |
| pstart = psz_input; |
| } |
| pstart += strlen(psz_after); |
| |
| if( strlen(psz_before) ) |
| { |
| pend = strstr(psz_input, psz_before); |
| } |
| if( !pend ) |
| { |
| pend = psz_input + strlen(psz_input); |
| } |
| } |
| |
| |
| while(pstart && pstart < pend) |
| { |
| *pstart = map[(unsigned char)*pstart]; |
| pstart += 1; |
| } |
| |
| memcpy(input->data+input_offset, psz_input, input_size); |
| } |
| |
| static void |
| move_string(cblc_field_t *field, |
| size_t offset, |
| size_t length, |
| const char *from, |
| size_t strlen_from = (size_t)(-1) ) |
| { |
| bool moved = true; |
| |
| if( strlen_from == (size_t)(-1) ) |
| { |
| strlen_from = strlen(from); |
| } |
| |
| switch(field->type) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldAlphaEdited: |
| { |
| char *to = reinterpret_cast<char *>(field->data + offset); |
| size_t dest_length = length ? length : field->capacity; |
| size_t source_length = strlen_from; |
| size_t count = std::min(dest_length, source_length); |
| |
| if( source_length >= dest_length ) |
| { |
| // We have more source characters than places to put them |
| if( field->attr & rjust_e ) |
| { |
| // Destination is right-justified, so we |
| // discard the leading source characters: |
| memmove(to, |
| from + (source_length - count), |
| count); |
| } |
| else |
| { |
| // Destination is right-justified, so we |
| // discard the trailing source characters: |
| memmove(to, |
| from, |
| count); |
| } |
| } |
| else |
| { |
| // We have too few source characters to fill the destination. |
| if( field->attr & rjust_e ) |
| { |
| // The destination is right-justified, and the source is an |
| // ordinary string too short to fill it. So, we space-fill |
| // the leading characters. |
| memmove(to + (dest_length-count), |
| from, |
| count); |
| memset(to, internal_space, dest_length-count); |
| } |
| else |
| { |
| // The destination is left-justified |
| // We do the move first, in case this is an overlapping move |
| // involving characters that will be space-filled |
| memmove(to, |
| from, |
| count); |
| memset( to + count, |
| internal_space, |
| dest_length-count); |
| } |
| } |
| break; |
| } |
| |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldNumericBin5: |
| case FldIndex: |
| { |
| // We are starting with a string, and setting it to a numerical |
| // target. |
| int rdigits; |
| __int128 value = __gg__dirty_to_binary_internal( from, |
| strlen_from, |
| &rdigits); |
| __gg__int128_to_qualified_field(field, |
| offset, |
| length, |
| value, |
| rdigits, |
| truncation_e, |
| NULL); |
| break; |
| } |
| |
| default: |
| moved = false; |
| break; |
| } |
| if( !moved ) |
| { |
| fprintf(stderr, "%s() %s:%d -- We were unable move a string to " |
| "field type %d\n", |
| __func__, __FILE__, __LINE__, |
| field->type); |
| abort(); |
| } |
| } |
| |
| static char * |
| brute_force_trim(char *str) |
| { |
| char *retval = str; |
| while( *retval == internal_space ) |
| { |
| retval += 1; |
| } |
| char *p = retval + strlen(retval)-1; |
| while( p > retval && *p == internal_space ) |
| { |
| *p-- = NULLCH; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__string(const size_t integers[]) |
| { |
| // The first integer is the count of identifier-2 values. Call it N |
| // The following N integers are the counts of each of the identifier-1 values, |
| // one for each identifier-1. Call them M. |
| |
| // The first refer is the target |
| // The second refer is the pointer |
| // The third refer is identifier-2 for N1 |
| // That's followed by M1 identifier-1 values |
| // That's followed by identifier2 for N2 |
| // And so on |
| |
| cblc_field_t **ref = __gg__treeplet_1f; |
| const size_t *ref_o = __gg__treeplet_1o; |
| const size_t *ref_s = __gg__treeplet_1s; |
| |
| static const int INDEX_OF_POINTER = 1; |
| |
| size_t index_cblc = 0 ; |
| |
| char figlow[2] = {ascii_to_internal(__gg__low_value_character), 0x00}; |
| char fighigh[2] = {ascii_to_internal(__gg__high_value_character), 0x00}; |
| char figzero[2] = {(char)internal_zero, 0x00}; |
| char figquote[2] = {ascii_to_internal(__gg__quote_character), 0x00}; |
| char figspace[2] = {(char)internal_space, 0x00}; |
| |
| if( __gg__high_value_character == DEGENERATE_HIGH_VALUE ) |
| { |
| fighigh[0] = __gg__high_value_character; |
| } |
| else |
| { |
| fighigh[0] = ascii_to_internal(__gg__high_value_character); |
| } |
| |
| |
| // Pick up the target |
| const cblc_field_t *tgt = ref[index_cblc]; |
| size_t tgt_o = ref_o[index_cblc]; |
| size_t tgt_s = ref_s[index_cblc]; |
| index_cblc += 1; |
| char *dest = reinterpret_cast<char *>(tgt->data + tgt_o); |
| ssize_t dest_length = tgt_s; |
| |
| // Skip over the index of POINTER: |
| index_cblc += 1; |
| |
| // Pick up the pointer, if any |
| ssize_t pointer = 0; |
| if( ref[INDEX_OF_POINTER] ) |
| { |
| int rdigits; |
| pointer = (size_t)__gg__binary_value_from_qualified_field( |
| &rdigits, |
| ref [INDEX_OF_POINTER], |
| ref_o[INDEX_OF_POINTER], |
| ref_s[INDEX_OF_POINTER] |
| ); |
| pointer -= 1; |
| } |
| |
| int overflow = 0; |
| |
| // Make sure that the destination pointer is within the destination |
| if( pointer >= 0 || pointer < dest_length ) |
| { |
| // We are go for looping through identifier-2 values: |
| |
| size_t index_int = 0; |
| |
| // Pick up the number of identifier-2 values |
| size_t N = integers[index_int++]; |
| |
| for( size_t i=0; i<N; i++ ) |
| { |
| size_t M = integers[index_int++]; |
| |
| // Pick up the identifier_2 DELIMITED BY value |
| const cblc_field_t *id2 = ref[index_cblc]; |
| size_t id2_o = ref_o[index_cblc]; |
| size_t id2_s = ref_s[index_cblc]; |
| index_cblc += 1; |
| |
| char *piece; |
| const char *piece_end; |
| cbl_figconst_t figconst = (cbl_figconst_t) ( id2 |
| ? (id2->attr & FIGCONST_MASK) |
| : 0 ); |
| switch(figconst) |
| { |
| case low_value_e: |
| piece = figlow; |
| piece_end = piece + 1; |
| break; |
| case zero_value_e: |
| piece = figzero; |
| piece_end = piece + 1; |
| break; |
| case space_value_e: |
| piece = figspace; |
| piece_end = piece + 1; |
| break; |
| case quote_value_e: |
| piece = figquote; |
| piece_end = piece + 1; |
| break; |
| case high_value_e: |
| piece = fighigh; |
| piece_end = piece + 1; |
| break; |
| default: |
| piece = id2 ? reinterpret_cast<char *>(id2->data + id2_o) : NULL; |
| piece_end = id2 ? piece + id2_s : NULL; |
| break; |
| } |
| |
| for(size_t j=0; j<M; j++) |
| { |
| // Pick up the next identifier-1 source string: |
| const cblc_field_t *id1 = ref[index_cblc]; |
| size_t id1_o = ref_o[index_cblc]; |
| size_t id1_s = ref_s[index_cblc]; |
| index_cblc += 1; |
| |
| const char *whole = id1 ? reinterpret_cast<char *>(id1->data + id1_o): NULL ; |
| const char *whole_end = id1 ? whole + id1_s : NULL; |
| |
| // As usual, we need to cope with figurative constants: |
| figconst = (cbl_figconst_t) ( id1 ? (id1->attr & FIGCONST_MASK) : 0 ); |
| switch( figconst ) |
| { |
| case low_value_e: |
| whole = figlow; |
| whole_end = whole + 1; |
| break; |
| case zero_value_e: |
| whole = figzero; |
| whole_end = whole + 1; |
| break; |
| case space_value_e: |
| whole = figspace; |
| whole_end = whole + 1; |
| break; |
| case quote_value_e: |
| whole = figquote; |
| whole_end = whole + 1; |
| break; |
| case high_value_e: |
| whole = fighigh; |
| whole_end = whole + 1; |
| break; |
| default: |
| break; |
| } |
| |
| if(piece) |
| { |
| const char *found = funky_find( piece, piece_end, |
| whole, whole_end); |
| if(found) |
| { |
| whole_end = found; |
| } |
| } |
| while(whole < whole_end) |
| { |
| if(pointer >= dest_length) |
| { |
| overflow = 1; |
| break; |
| } |
| dest[pointer++] = *whole++; |
| } |
| if( overflow ) |
| { |
| break; |
| } |
| } |
| } |
| |
| // Update the pointer, if there is one |
| if( ref[INDEX_OF_POINTER] ) |
| { |
| __gg__int128_to_qualified_field(ref [INDEX_OF_POINTER], |
| ref_o[INDEX_OF_POINTER], |
| ref_s[INDEX_OF_POINTER], |
| (__int128)(pointer+1), |
| 0, |
| truncation_e, |
| NULL ); |
| } |
| } |
| else |
| { |
| // The initial pointer is not inside the destination |
| overflow = 1; |
| } |
| |
| return overflow; |
| } |
| |
| static |
| void |
| display_both(cblc_field_t *field, |
| unsigned char *qual_data, |
| size_t qual_size, |
| int flags, |
| int file_descriptor, |
| int advance ) |
| { |
| static size_t display_string_size = MINIMUM_ALLOCATION_SIZE; |
| static char *display_string = static_cast<char *>(malloc(display_string_size)); |
| |
| format_for_display_internal(&display_string, |
| &display_string_size, |
| field, |
| qual_data, |
| qual_size, |
| !!(flags & REFER_T_ADDRESS_OF) ); |
| |
| // Let's honor the locale of the system, as best we can: |
| static size_t converted_size = MINIMUM_ALLOCATION_SIZE; |
| static char *converted = static_cast<char *>(malloc(converted_size)); |
| |
| internal_to_console(&converted, &converted_size, display_string, strlen(display_string)); |
| |
| ssize_t ss = write( file_descriptor, |
| converted, |
| strlen(converted)); |
| if(ss == -1) |
| { |
| fprintf(stderr, "__gg__display() %s %p\n", field->name, qual_data); |
| fprintf(stderr, "__gg__display() %ld\n", static_cast<long>(converted_size)); |
| fprintf(stderr, "__gg__display() "); |
| for(size_t i=0; i<converted_size; i++) |
| { |
| fprintf(stderr, "%c(%2.2x) ", converted[i]<32 ? '?' : converted[i], converted[i]); |
| } |
| __gg__abort("display_both() some kind of write() error"); |
| fprintf(stderr, "\n"); |
| } |
| |
| if( advance ) |
| { |
| write( file_descriptor, |
| "\n", |
| 1); |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__display( cblc_field_t *field, |
| size_t offset, |
| size_t size, |
| int file_descriptor, |
| int advance ) |
| { |
| display_both( field, |
| field->data + offset, |
| size, |
| 0, |
| file_descriptor, |
| advance); |
| } |
| |
| extern "C" |
| void |
| __gg__display_clean(cblc_field_t *field, |
| int file_descriptor, |
| int advance ) |
| { |
| display_both( field, |
| field->data, |
| field->capacity, |
| 0, |
| file_descriptor, |
| advance); |
| } |
| |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wunused-result" |
| |
| extern "C" |
| void |
| __gg__display_string( int file_descriptor, |
| const char *str, |
| size_t length, |
| int advance ) |
| { |
| // Let's honor the locale of the system, as best we can: |
| static size_t converted_size = MINIMUM_ALLOCATION_SIZE; |
| static char *converted = static_cast<char *>(malloc(converted_size)); |
| |
| size_t max_possible = 2 * length; |
| if( max_possible > converted_size ) |
| { |
| converted_size = max_possible; |
| converted = static_cast<char *>(realloc(converted, converted_size)); |
| } |
| |
| __gg__ascii_to_console(&converted, &converted_size, str, length); |
| |
| write( file_descriptor, |
| converted, |
| strlen(converted)); |
| if( advance ) |
| { |
| write( file_descriptor, |
| "\n", |
| 1); |
| } |
| } |
| |
| static |
| char * |
| mangler_core(const char *s, const char *eos) |
| { |
| // The caller needs to be aware that the return value is a static character |
| // array. Program accordingly. |
| |
| // This is the run-time version of the code in cobol1.cc routine that mangles |
| // cobol names into workable form. The logic here has to match the logic |
| // there so that calls work. |
| |
| static char cobol_name[1024]; |
| |
| while( s < eos && *s == ascii_space ) |
| { |
| s += 1; |
| } |
| while( s < eos && *(eos-1) == ascii_space ) |
| { |
| eos -= 1; |
| } |
| |
| char *d = cobol_name; |
| if( s[0] >= ascii_0 && s[0] <= ascii_9 ) |
| { |
| *d++ = '_'; |
| } |
| |
| while(s < eos) |
| { |
| int ch = *s++; |
| if( ch == ascii_hyphen ) |
| { |
| *d++ = ascii_dollar_sign; |
| } |
| else |
| { |
| *d++ = tolower((unsigned char)ch); |
| } |
| } |
| *d++ = NULLCH; |
| |
| return cobol_name; |
| } |
| |
| static |
| char * |
| not_mangled_core(const char *s, const char *eos) |
| { |
| const char *s1 = s; |
| const char *s2 = eos; |
| bool has_dash = false; |
| |
| while( s < eos && *s == internal_space ) |
| { |
| s += 1; |
| } |
| while( s < eos && *(eos-1) == internal_space ) |
| { |
| eos -= 1; |
| } |
| |
| if( s[0] >= ascii_0 && s[0] <= ascii_9 ) |
| { |
| has_dash = true; |
| } |
| else |
| { |
| while(s < eos) |
| { |
| int ch = *s++; |
| if( ch == ascii_hyphen ) |
| { |
| has_dash = true; |
| } |
| } |
| } |
| |
| if( has_dash ) |
| { |
| return mangler_core(s1, s2); |
| } |
| |
| return (char *)s1; |
| } |
| |
| extern "C" |
| void |
| __gg__accept( enum special_name_t special_e, |
| cblc_field_t *field, |
| size_t offset, |
| size_t length) |
| { |
| int file_descriptor = 0; // Default to stdin |
| |
| size_t max_chars = length ? length : field->capacity; |
| |
| if( special_e == CONSOLE_e ) |
| { |
| // Welcome to the land of possibly screwball assumptions. If reading |
| // from CONSOLE/stdin it's possible that the target variable is |
| // a NumericBinary of length 4, which can hold a 10-digit number. So, |
| // we need room to accept the characters, which will later on be converted |
| // to a binary value. |
| |
| // But SYSIN and SYSIPT seem to require that characters be read until the |
| // size of the target variable is satisfied, which implies further that |
| // the target must be alphanumeric. |
| |
| // What reality will ultimately offer is unknown to me. But I'm doing |
| // the best I can with what I've got, and, right now, this is what |
| // I've got. |
| if( max_chars < 64 ) |
| { |
| // Set a floor for the length of the buffer. This will let us cope |
| // with, say, a four-byte binary value that can hold ten digits |
| max_chars = 64; |
| } |
| } |
| |
| char *buffer = static_cast<char *>(malloc(max_chars+1)); |
| massert(buffer); |
| memset(buffer, ascii_space, max_chars); |
| buffer[max_chars] = NULLCH; |
| size_t i = 0; |
| |
| for(;;) |
| { |
| char ch; |
| ssize_t bytes_read = read(file_descriptor, &ch, 1); |
| if( bytes_read <= 0 ) |
| { |
| // Error or end-of-file, so give up |
| break; |
| } |
| |
| if( ch == '\n' ) |
| { |
| // End-of-line |
| if( special_e == CONSOLE_e ) |
| { |
| // When reading from the console, a newline means that the |
| // typist pressed ENTER/RETURN, and the input is done. This is |
| // also the case even when stdin was redirected from a file or |
| // another process |
| break; |
| } |
| else |
| { |
| // But if SYSIN_e or SYSIPT_e was specified, we are emulating |
| // the universe of punched cards, so we just keep reading in |
| // characters until we have read in max_chars. We found it |
| // necessary to implement ACCEPT in this fashion to get the |
| // NIST test suite to work. |
| |
| // Note that in both cases, we keep reading until we hit |
| // an actual newline or end-of-file |
| |
| if( i >= max_chars ) |
| { |
| break; |
| } |
| continue; |
| } |
| } |
| if(i < max_chars) |
| { |
| buffer[i++] = ch; |
| } |
| } |
| |
| switch(field->type) |
| { |
| case FldGroup : |
| case FldAlphanumeric : |
| case FldAlphaEdited : |
| console_to_internal(buffer, i); |
| move_string(field, |
| offset, |
| length, |
| buffer, |
| strlen(buffer)); |
| break; |
| |
| case FldNumericDisplay: |
| { |
| // In the NIST tests, feeding ten digits 0123456789 into a |
| // PIC 9(9) results in a nine-digit 012345678 rather than our |
| // default 123456789 |
| |
| int digit_count = 0; |
| char *p = buffer; |
| while(*p && digit_count < field->digits) |
| { |
| if( *p == __gg__decimal_point ) |
| { |
| p += 1; |
| continue; |
| } |
| |
| switch(*p) |
| { |
| case ascii_0: |
| case ascii_1: |
| case ascii_2: |
| case ascii_3: |
| case ascii_4: |
| case ascii_5: |
| case ascii_6: |
| case ascii_7: |
| case ascii_8: |
| case ascii_9: |
| p += 1; |
| digit_count += 1; |
| continue; |
| break; |
| case ascii_minus: |
| case ascii_plus: |
| p += 1; |
| continue; |
| break; |
| default: |
| goto we_are_done; |
| break; |
| } |
| } |
| we_are_done: |
| *p = NULLCH; |
| |
| int rdigits; |
| __int128 value = __gg__dirty_to_binary_source( buffer, |
| (int)i, |
| &rdigits); |
| |
| __gg__int128_to_qualified_field(field, |
| offset, |
| length, |
| value, |
| rdigits, |
| truncation_e, |
| NULL); |
| break; |
| } |
| |
| default: |
| { |
| int rdigits; |
| |
| __int128 value = __gg__dirty_to_binary_source( buffer, |
| (int)i, |
| &rdigits); |
| |
| __gg__int128_to_qualified_field(field, |
| offset, |
| length, |
| value, |
| rdigits, |
| truncation_e, |
| NULL); |
| break; |
| } |
| } |
| #if LOGGING_FOR_TESTING |
| if( isatty(file_descriptor) ) |
| { |
| auto p = strchr(buffer, '\0'); |
| *p = '\n'; |
| write(1, buffer, (p - buffer) + 1); |
| } |
| #endif |
| free(buffer); |
| } |
| |
| extern "C" |
| __int128 |
| __gg__binary_value_from_field( int *rdigits, |
| cblc_field_t *var) |
| { |
| return get_binary_value_local( rdigits, |
| var, |
| var->data, |
| var->capacity); |
| } |
| |
| extern "C" |
| __int128 |
| __gg__binary_value_from_qualified_field(int *rdigits, |
| const cblc_field_t *var, |
| size_t offset, |
| size_t size) |
| { |
| return get_binary_value_local( rdigits, |
| var, |
| var->data + offset, |
| size); |
| } |
| |
| extern "C" |
| GCOB_FP128 |
| __gg__float128_from_field( cblc_field_t *field ) |
| { |
| GCOB_FP128 retval=0; |
| if( field->type == FldFloat || field->type == FldLiteralN ) |
| { |
| retval = get_float128(field, field->data); |
| } |
| else |
| { |
| int rdigits; |
| retval = (GCOB_FP128)__gg__binary_value_from_field(&rdigits, field); |
| if( rdigits ) |
| { |
| retval /= (GCOB_FP128)__gg__power_of_ten(rdigits); |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| GCOB_FP128 |
| __gg__float128_from_qualified_field(const cblc_field_t *field, size_t offset, size_t size) |
| { |
| GCOB_FP128 retval=0; |
| if( field->type == FldFloat || field->type == FldLiteralN ) |
| { |
| retval = get_float128(field, field->data+offset); |
| } |
| else |
| { |
| int rdigits; |
| retval = (GCOB_FP128)__gg__binary_value_from_qualified_field(&rdigits, field, offset, size); |
| if( rdigits ) |
| { |
| retval /= (GCOB_FP128)__gg__power_of_ten(rdigits); |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__integer_from_qualified_field( cblc_field_t *var, |
| size_t var_offset, |
| size_t var_size) |
| { |
| // This is useful when a temporary value with some number of rdigits |
| // is passed when the value is known to be an integer |
| |
| int rdigits; |
| __int128 retval = get_binary_value_local( &rdigits, |
| var, |
| var->data + var_offset, |
| var_size); |
| if( rdigits ) |
| { |
| retval /= __gg__power_of_ten(rdigits); |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__int128_to_field(cblc_field_t *tgt, |
| __int128 value, |
| int source_rdigits, |
| enum cbl_round_t rounded, |
| int *compute_error) |
| { |
| int128_to_field(tgt, |
| tgt->data, |
| tgt->capacity, |
| value, |
| source_rdigits, |
| rounded, |
| compute_error); |
| } |
| |
| extern "C" |
| void |
| __gg__int128_to_qualified_field(cblc_field_t *tgt, |
| size_t offset, |
| size_t length, |
| __int128 value, |
| int source_rdigits, |
| enum cbl_round_t rounded, |
| int *compute_error) |
| { |
| int128_to_field(tgt, |
| tgt->data + offset, |
| length ? length : tgt->capacity, |
| value, |
| source_rdigits, |
| rounded, |
| compute_error); |
| } |
| |
| static __int128 |
| float128_to_int128( int *rdigits, |
| const cblc_field_t *field, |
| GCOB_FP128 value, |
| cbl_round_t rounded, |
| int *compute_error) |
| { |
| __int128 retval = 0; |
| if( value == INFINITY ) |
| { |
| *compute_error = compute_error_overflow; |
| } |
| else if( value == NAN ) |
| { |
| *compute_error = compute_error_underflow; |
| } |
| else |
| { |
| // Our mission is to take a 128-bit floating point value and convert it |
| // to a 128-bit number. If we can't, we flag it appropriately. |
| |
| if( field->attr & intermediate_e ) |
| { |
| // Our target doesn't have a fixed number of rdigits. We will look at the |
| // value, and from that calculate the maximum number of rdigits we can |
| // get away with. |
| |
| // Calculate the number of digits to the left of the decimal point: |
| int digits = (int)(FP128_FUNC(floor)(FP128_FUNC(log)(FP128_FUNC(fabs)(value)))+1); |
| |
| // Make sure it is not a negative number |
| digits = std::max(0, digits); |
| |
| // From that we can calculate the number of rdigits |
| *rdigits = MAX_FIXED_POINT_DIGITS - digits; |
| } |
| else |
| { |
| // Our target has a fixed number of rdigits: |
| *rdigits = field->rdigits; |
| } |
| |
| // We now multiply our value by 10**rdigits, in order to make the |
| // floating-point value have the same magnitude as our target __int128 |
| |
| value *= FP128_FUNC(pow)(GCOB_FP128_LITERAL (10.0), (GCOB_FP128)(*rdigits)); |
| |
| // We are ready to cast value to an __int128. But this value could be |
| // too large to fit, which is an error condition we want to flag: |
| |
| if( FP128_FUNC(fabs)(value) >= GCOB_FP128_LITERAL (1.0E38) ) |
| { |
| *compute_error = compute_error_overflow; |
| } |
| else |
| { |
| retval = f128_to_i128_rounded(rounded, value, compute_error); |
| } |
| } |
| |
| return retval; |
| } |
| |
| static void |
| float128_to_location( cblc_field_t *tgt, |
| unsigned char *data, |
| size_t size, |
| GCOB_FP128 value, |
| enum cbl_round_t rounded, |
| int *compute_error) |
| { |
| switch(tgt->type) |
| { |
| case FldFloat: |
| { |
| switch(tgt->capacity) |
| { |
| case 4: |
| if( FP128_FUNC(fabs)(value) == (GCOB_FP128)INFINITY |
| || FP128_FUNC(fabs)(value) > GCOB_FP128_LITERAL (3.4028235E38) ) |
| { |
| if( compute_error ) |
| { |
| *compute_error |= compute_error_overflow; |
| } |
| if( value < 0 ) |
| { |
| *PTRCAST(float, data) = -INFINITY; |
| } |
| else |
| { |
| *PTRCAST(float, data) = INFINITY; |
| } |
| } |
| else |
| { |
| *PTRCAST(float, data) = static_cast<float>(value); |
| } |
| break; |
| |
| case 8: |
| if( FP128_FUNC(fabs)(value) == (GCOB_FP128)INFINITY |
| || FP128_FUNC(fabs)(value) > GCOB_FP128_LITERAL (1.7976931348623157E308) ) |
| { |
| if( compute_error ) |
| { |
| *compute_error |= compute_error_overflow; |
| } |
| if( value < 0 ) |
| { |
| *PTRCAST(double, data) = -INFINITY; |
| } |
| else |
| { |
| *PTRCAST(double, data) = INFINITY; |
| } |
| } |
| else |
| { |
| *PTRCAST(double, data) = static_cast<double>(value); |
| } |
| break; |
| |
| case 16: |
| if( FP128_FUNC(fabs)(value) == (GCOB_FP128)INFINITY ) |
| { |
| if( compute_error ) |
| { |
| *compute_error |= compute_error_overflow; |
| } |
| } |
| |
| //*(_Float128 *)(data) = value; |
| memcpy(data, &value, 16); |
| break; |
| } |
| break; |
| } |
| |
| default: |
| { |
| if( compute_error ) |
| { |
| int digits; |
| if( tgt->attr & intermediate_e ) |
| { |
| digits = MAX_FIXED_POINT_DIGITS; |
| } |
| else |
| { |
| digits = tgt->digits; |
| } |
| |
| GCOB_FP128 maximum; |
| |
| if( digits ) |
| { |
| maximum = __gg__power_of_ten(digits); |
| } |
| |
| // When digits is zero, this is a binary value without a PICTURE string. |
| // we don't truncate in that case |
| if( digits && FP128_FUNC(fabs)(value) >= maximum ) |
| { |
| *compute_error |= compute_error_truncate; |
| } |
| } |
| |
| int rdigits=0; // Initialized to quiet a compiler warning. |
| __int128 val128 = float128_to_int128( &rdigits, |
| tgt, |
| value, |
| rounded, |
| compute_error); |
| int128_to_field(tgt, |
| data, |
| size, |
| val128, |
| rdigits, |
| rounded, |
| compute_error); |
| break; |
| } |
| } |
| } |
| |
| |
| extern "C" |
| void |
| __gg__float128_to_field(cblc_field_t *tgt, |
| GCOB_FP128 value, |
| enum cbl_round_t rounded, |
| int *compute_error) |
| { |
| float128_to_location( tgt, |
| tgt->data, |
| tgt->capacity, |
| value, |
| rounded, |
| compute_error); |
| } |
| |
| extern "C" |
| void |
| __gg__float128_to_qualified_field(cblc_field_t *tgt, |
| size_t tgt_offset, |
| GCOB_FP128 value, |
| enum cbl_round_t rounded, |
| int *compute_error) |
| { |
| float128_to_location( tgt, |
| tgt->data + tgt_offset, |
| tgt->capacity, |
| value, |
| rounded, |
| compute_error); |
| } |
| |
| extern "C" |
| bool |
| __gg__bitop(cblc_field_t *a, |
| bitop_t op, |
| size_t bitmask) |
| { |
| bool retval = false; |
| int rdigits; |
| __int128 value = __gg__binary_value_from_field(&rdigits, a); |
| switch(op) |
| { |
| case bit_set_op: // set bits on |
| value |= bitmask; |
| __gg__int128_to_field(a, |
| value, |
| 0, |
| truncation_e, |
| NULL); |
| break; |
| |
| case bit_clear_op: // set bits off |
| value &= ~bitmask; |
| __gg__int128_to_field(a, |
| value, |
| 0, |
| truncation_e, |
| NULL); |
| break; |
| |
| case bit_on_op: // true if any bitmask bit is on |
| retval = bitmask & value; |
| break; |
| |
| case bit_off_op: // true if any bitmask bit is off |
| retval = bitmask & ~value; |
| break; |
| |
| default: |
| __gg__abort("__gg__bitop() unknown operation code"); |
| break; |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__bitwise_op( cblc_field_t *tgt, |
| cblc_field_t *a, |
| bitop_t op, |
| size_t bitmask) |
| { |
| int rdigits; |
| __int128 value = __gg__binary_value_from_field(&rdigits, a); |
| switch(op) |
| { |
| case bit_and_op: |
| value &= bitmask; |
| __gg__int128_to_field(tgt, |
| value, |
| 0, |
| truncation_e, |
| NULL); |
| break; |
| |
| case bit_or_op: |
| value |= bitmask; |
| __gg__int128_to_field(tgt, |
| value, |
| 0, |
| truncation_e, |
| NULL); |
| break; |
| |
| case bit_xor_op: |
| value ^= bitmask; |
| __gg__int128_to_field(tgt, |
| value, |
| 0, |
| truncation_e, |
| NULL); |
| break; |
| |
| default: |
| __gg__abort("__gg__bitwise_op() unknown operation code"); |
| break; |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__set_initial_switch_value( ) |
| { |
| // We need to establish the initial value of the UPSI-1 switch register |
| // We are using IBM's conventions: |
| // https://www.ibm.com/docs/en/zvse/6.2?topic=SSB27H_6.2.0/fa2sf_communicate_appl_progs_via_job_control.html |
| // UPSI 10000110 means that bits 0, 5, and 6 are on, which means that SW-0, SW-5, and SW-6 are on. |
| |
| __int128 value = 0; |
| __int128 bit = 1; |
| char ach[129]; |
| memset(ach, 0, sizeof(ach)); |
| const char *p = getenv("UPSI"); |
| if( p ) |
| { |
| snprintf(ach, sizeof(ach), "%s", p); |
| p = ach; |
| while(*p) |
| { |
| if( *p++ == ascii_1 ) |
| { |
| value |= bit; |
| } |
| bit <<= 1 ; |
| } |
| } |
| __gg__data_upsi_0[0] = (value>>0) & 0xFF; |
| __gg__data_upsi_0[1] = (value>>8) & 0xFF; |
| } |
| |
| static int |
| is_numeric_edited_numeric(cblc_field_t *, size_t, size_t ) |
| { |
| fprintf(stderr, "We don't know how to see if numeric-edited is numeric\n"); |
| abort(); |
| } |
| |
| static int |
| is_numeric_display_numeric(cblc_field_t *field, size_t offset, size_t size) |
| { |
| int retval = 1; |
| bool signable = !!(field->attr & signable_e); |
| bool leading = !!(field->attr & leading_e); |
| bool separate = !!(field->attr & separate_e); |
| |
| char *digits = reinterpret_cast<char *>(field->data + offset); |
| char *digits_e = digits + size; |
| |
| if( leading && separate && signable ) |
| { |
| // First character must be +/- |
| if( digits < digits_e |
| || ( *digits != internal_plus |
| && *digits != internal_minus) ) |
| { |
| retval = 0; |
| } |
| digits += 1; |
| } |
| |
| if( !leading && separate && signable ) |
| { |
| // Last character must be +/- |
| digits_e -= 1; |
| if( digits < digits_e |
| || ( *digits_e != internal_plus |
| && *digits_e != internal_minus) ) |
| { |
| retval = 0; |
| } |
| } |
| |
| if( leading && !separate && signable ) |
| { |
| // The first character is allowed to have a sign bit. |
| if( digits < digits_e ) |
| { |
| unsigned char first_char = (unsigned char)*digits; |
| turn_sign_bit_off(&first_char); |
| if(first_char<internal_0 || first_char>internal_9) |
| { |
| retval = 0; |
| } |
| } |
| digits += 1; |
| } |
| |
| if( !leading && !separate && signable ) |
| { |
| // The final character is allowed to have a sign bit. |
| if( digits < digits_e ) |
| { |
| digits_e -= 1; |
| unsigned char final_char = (unsigned char)*digits_e; |
| turn_sign_bit_off(&final_char); |
| if(final_char<internal_0 || final_char>internal_9) |
| { |
| retval = 0; |
| } |
| } |
| } |
| |
| // all remaining characters are supposed to be zero through nine |
| while( digits < digits_e ) |
| { |
| if( (unsigned char)(*digits)<internal_0 |
| || (unsigned char)(*digits)>internal_9 ) |
| { |
| retval = 0; |
| break; |
| } |
| digits += 1; |
| } |
| return retval; |
| } |
| |
| static int |
| is_packed_numeric(const cblc_field_t *field, size_t offset, size_t size) |
| { |
| int retval = 1; |
| bool is_comp6 = !!(field->attr&packed_no_sign_e); |
| int digits = field->digits; |
| bool signable = !!(field->attr & signable_e); |
| const unsigned char *bytes = field->data + offset; |
| |
| int nybble = 0; |
| int nybble_e = nybble + digits; |
| unsigned char should_be_zero = 0; |
| if( is_comp6 ) |
| { |
| // This is packed decimal with no sign nybble at the end |
| if( digits & 1 ) |
| { |
| // There are an odd number of digits, so the string starts on the |
| // the right side of the first byte |
| nybble += 1; |
| nybble_e += 1; |
| should_be_zero = *bytes & 0xF0; |
| } |
| } |
| else |
| { |
| // This is packed decimal, and ends with a sign nybble |
| if( size ) |
| { |
| unsigned char nyb = bytes[size-1] & 0x0F; |
| if( !signable && nyb != 0x0F) |
| { |
| retval = 0; |
| } |
| if( signable && nyb != 0x0C && nyb != 0x0D ) |
| { |
| retval = 0; |
| } |
| } |
| if( !(digits & 1) ) |
| { |
| // There are an even number of digits before the sign nybble. So the |
| // string starts on the right side of the first byte |
| nybble += 1; |
| nybble_e += 1; |
| should_be_zero = *bytes & 0xF0; |
| } |
| } |
| if( should_be_zero != 0 ) |
| { |
| retval = 0; |
| } |
| // At this point, all nybbles between nybble and nybble_e should be between |
| // 0x00 and 0x09. |
| while(nybble < nybble_e) |
| { |
| unsigned char nyb = bytes[nybble/2]; |
| if( !(nybble & 1)) |
| { |
| nyb >>= 4; |
| } |
| else |
| { |
| nyb &= 0xF; |
| } |
| if( nyb > 0x09 ) |
| { |
| retval = 0; |
| break; |
| } |
| nybble += 1; |
| } |
| return retval; |
| } |
| |
| static int |
| is_alpha_a_number(const cblc_field_t *field, |
| size_t offset, |
| size_t size) |
| { |
| int retval = 1; |
| const unsigned char *bytes = (field->data + offset); |
| for( size_t i=0; i<size; i++ ) |
| { |
| unsigned char ch = bytes[i]; |
| if( (ch < internal_0) |
| || (ch > internal_9) ) |
| { |
| retval = 0; |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__classify( classify_t type, |
| cblc_field_t *field, |
| size_t offset, |
| size_t size) |
| { |
| // The default answer is TRUE |
| int retval = 1; |
| |
| const unsigned char *alpha = reinterpret_cast<unsigned char *>(field->data+offset); |
| |
| size_t str_length = size; |
| |
| const unsigned char *omega = alpha + str_length; |
| |
| if(alpha >= omega) |
| { |
| // If there is nothing there, then it can't be TRUE. Can it? |
| retval = 0; |
| } |
| |
| unsigned char ch; |
| switch(type) |
| { |
| case ClassNumericType: |
| { |
| switch( field->type ) |
| { |
| case FldNumericEdited: |
| retval = is_numeric_edited_numeric(field, offset, size); |
| break; |
| case FldNumericDisplay: |
| retval = is_numeric_display_numeric(field, offset, size); |
| break; |
| case FldPacked: |
| retval = is_packed_numeric(field, offset, size); |
| break; |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldAlphaEdited: |
| retval = is_alpha_a_number(field, offset, size); |
| break; |
| |
| case FldNumericBinary: |
| case FldNumericBin5: |
| // These need to checked for fitting into field->digits |
| break; |
| |
| default: |
| fprintf(stderr, |
| "We need code for %s numeric type %d\n", |
| field->name, |
| field->type); |
| abort(); |
| break; |
| } |
| |
| break; |
| } |
| |
| case ClassAlphabeticType: |
| while(alpha < omega) |
| { |
| ch = (*alpha++)&0xFF; |
| if( ch == internal_space ) |
| { |
| continue; |
| } |
| // If necessary, this could be sped up with the creation of |
| // appropriate mapping tables. |
| |
| // The oddball construction of this if() statement is a consequence of |
| // EBCDIC. Because of peculiarities going all the back to the encoding |
| // of characters on IBM cards, where it wasn't a good idea to have too |
| // many consecutive punches in a column because it would weaken the card |
| // to the point where its structural integrity might be threatened, the |
| // coding for the letter of the alphabet are not contiguous. |
| if(!( ( ch >= internal_A && ch <= internal_I) |
| || (ch >= internal_J && ch <= internal_R) |
| || (ch >= internal_S && ch <= internal_Z) |
| || (ch >= internal_a && ch <= internal_i) |
| || (ch >= internal_j && ch <= internal_r) |
| || (ch >= internal_s && ch <= internal_z) ) ) |
| { |
| // The character is not alphabetic |
| retval = 0; |
| break; |
| } |
| } |
| break; |
| |
| case ClassLowerType: |
| while(alpha < omega) |
| { |
| ch = *alpha++; |
| if( ch == internal_space ) |
| { |
| continue; |
| } |
| if(!( ( ch >= internal_a && ch <= internal_i) |
| || (ch >= internal_j && ch <= internal_r) |
| || (ch >= internal_s && ch <= internal_z) ) ) |
| { |
| retval = 0; |
| break; |
| } |
| } |
| break; |
| case ClassUpperType: |
| while(alpha < omega) |
| { |
| ch = *alpha++; |
| if( ch == internal_space ) |
| { |
| continue; |
| } |
| if(!( ( ch >= internal_A && ch <= internal_I) |
| || (ch >= internal_J && ch <= internal_R) |
| || (ch >= internal_S && ch <= internal_Z) ) ) |
| { |
| retval = 0; |
| break; |
| } |
| } |
| break; |
| |
| case ClassInvalidType: |
| case ClassDbcsType: |
| case ClassKanjiType: |
| default: |
| warnx("%s(): Don't know how to handle %s", |
| __func__, |
| classify_str(type)); |
| abort(); |
| break; |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__accept_envar( cblc_field_t *tgt, |
| size_t tgt_offset, |
| size_t tgt_length, |
| cblc_field_t *name, |
| size_t name_offset, |
| size_t name_length) |
| { |
| int retval; |
| tgt_length = tgt_length ? tgt_length : tgt->capacity; |
| name_length = name_length ? name_length : name->capacity; |
| |
| // Pick up the environment variable name, which is in teh internal codeset |
| static char *env = NULL; |
| static size_t env_length = 0; |
| if( env_length < name_length+1 ) |
| { |
| env_length = name_length+1; |
| env = static_cast<char *>(realloc(env, env_length)); |
| } |
| memcpy(env, name->data + name_offset, name_length); |
| env[name_length] = '\0'; |
| |
| // Get rid of leading and trailing internal_space characters: |
| char *trimmed_env = brute_force_trim(env); |
| |
| // Convert the name to the console codeset: |
| __gg__internal_to_console_in_place(trimmed_env, strlen(trimmed_env)); |
| |
| // Pick up the environment variable, and convert it to the internal codeset |
| const char *p = getenv(trimmed_env); |
| if(p) |
| { |
| char *pp = strdup(p); |
| console_to_internal(pp, strlen(pp)); |
| retval = 0; // Okay |
| move_string(tgt, tgt_offset, tgt_length, pp); |
| free(pp); |
| } |
| else |
| { |
| retval = 1; // Could't find it |
| exception_raise(ec_argument_imp_environment_e); |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| bool |
| __gg__set_envar(cblc_field_t *name, |
| size_t name_offset, |
| size_t name_length, |
| cblc_field_t *value, |
| size_t value_offset, |
| size_t value_length) |
| { |
| bool retval = false; // true means the variable existed: |
| |
| name_length = name_length ? name_length : name->capacity; |
| value_length = value_length ? value_length : value->capacity; |
| |
| static char *env = NULL; |
| static size_t env_length = 0; |
| static char *val = NULL; |
| static size_t val_length = 0; |
| if( env_length < name_length+1 ) |
| { |
| env_length = name_length+1; |
| env = static_cast<char *>(realloc(env, env_length)); |
| } |
| if( val_length < value_length+1 ) |
| { |
| val_length = value_length+1; |
| val = static_cast<char *>(realloc(val, val_length)); |
| } |
| |
| massert(val); |
| massert(env); |
| |
| // The name and the value arrive in the internal codeset: |
| memcpy(env, name->data+name_offset , name_length); |
| env[name_length] = '\0'; |
| memcpy(val, value->data+value_offset, value_length); |
| val[value_length] = '\0'; |
| |
| // Get rid of leading and trailing internal_space characters |
| char *trimmed_env = brute_force_trim(env); |
| char *trimmed_val = brute_force_trim(val); |
| |
| // Conver them to the console codeset |
| __gg__internal_to_console_in_place(trimmed_env, strlen(trimmed_env)); |
| __gg__internal_to_console_in_place(trimmed_val, strlen(trimmed_val)); |
| |
| if( getenv(trimmed_env) ) |
| { |
| // It already existed: |
| retval = true; |
| } |
| |
| // And now, anticlimactically, set the variable: |
| setenv(trimmed_env, trimmed_val, 1); |
| |
| return retval; |
| } |
| |
| static int stashed_argc = 0; |
| static char **stashed_argv = NULL; |
| |
| extern "C" |
| void |
| __gg__stash_argc_argv(int argc, char **argv) |
| { |
| stashed_argc = argc; |
| stashed_argv = argv; |
| |
| // This routine is called once by main(), so it is a convenient place to make |
| // the stack much bigger, because when people create COBOL programs with |
| // ridiculous numbers of variables, the stack gets ridiculously large. |
| |
| struct rlimit stack_size = {33554432, 33554432}; |
| if( setrlimit(RLIMIT_STACK, &stack_size) ) |
| { |
| fprintf(stderr, "warning: attempt to set stack size to 32M failed\n"); |
| } |
| } |
| |
| static void |
| command_line_plan_b() |
| { |
| // It's vaguely possible that somebody can try to access these command-line |
| // functions without a main() function having been invoked. This code, for |
| // example, could have been created as a stand-alone .so, or it could be |
| // from a COBOL .o that was linked to main() from another language. So, we |
| // are going to believe that /proc/cmdline is available, and proceed from |
| // there: |
| if( !stashed_argc ) |
| { |
| static char input[4096]; |
| sprintf(input, "/proc/%ld/cmdline", (long)getpid()); |
| FILE *f = fopen(input, "r"); |
| if( f ) |
| { |
| size_t bytes_read = fread(input, 1, sizeof(input), f); |
| fclose(f); |
| if( bytes_read ) |
| { |
| char *p = input; |
| const char *p_end = p + bytes_read; |
| char prior_char = '\0'; |
| while( p < p_end ) |
| { |
| if( prior_char == '\0' ) |
| { |
| stashed_argc += 1; |
| stashed_argv = static_cast<char **>(realloc(stashed_argv, |
| stashed_argc * sizeof(char *))); |
| stashed_argv[stashed_argc-1] = p; |
| } |
| prior_char = *p++; |
| } |
| } |
| } |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__get_argc(cblc_field_t *dest, size_t offset, size_t length) |
| { |
| command_line_plan_b(); |
| char ach[128]; |
| sprintf(ach, "%d", stashed_argc); |
| ascii_to_internal_str(ach, strlen(ach)); |
| move_string(dest, offset, length, ach); |
| } |
| |
| extern "C" |
| int |
| __gg__get_argv( cblc_field_t *dest, |
| size_t dest_offset, |
| size_t dest_length, |
| cblc_field_t *index, |
| size_t index_offset, |
| size_t index_size) |
| { |
| int retcode; |
| command_line_plan_b(); |
| int rdigits; |
| __int128 N = get_binary_value_local(&rdigits, |
| index, |
| index->data + index_offset, |
| index_size); |
| |
| // N is 1-based, per normal COBOL. We have to decrement it here: |
| N -= 1; |
| |
| dest_length = dest_length ? dest_length : dest->capacity; |
| |
| // If he gives us fractional digits, just truncate |
| N /= __gg__power_of_ten(rdigits); |
| |
| if( N >= stashed_argc || N < 0 ) |
| { |
| exception_raise(ec_argument_imp_command_e); |
| retcode = 1; // Error |
| } |
| else |
| { |
| char *retval = strdup(stashed_argv[N]); |
| console_to_internal(retval, strlen(retval)); |
| move_string(dest, dest_offset, dest_length, retval); |
| free(retval); |
| retcode = 0; // Okay |
| } |
| return retcode; |
| } |
| |
| extern "C" |
| int |
| __gg__get_command_line( cblc_field_t *field, |
| size_t offset, |
| size_t flength) |
| { |
| int retcode; |
| command_line_plan_b(); |
| size_t length = 1; |
| char *retval = static_cast<char *>(malloc(length)); |
| massert(retval); |
| *retval = NULLCH; |
| |
| for( int i=1; i<stashed_argc; i++ ) |
| { |
| while( strlen(retval) + strlen(stashed_argv[i]) + 2 > length ) |
| { |
| length *= 2; |
| retval = static_cast<char *>(realloc(retval, length)); |
| massert(retval); |
| } |
| if( *retval ) |
| { |
| strcat(retval, " "); |
| } |
| strcat(retval, stashed_argv[i]); |
| } |
| |
| if( *retval ) |
| { |
| flength = flength ? flength : field->capacity; |
| console_to_internal(retval, strlen(retval)); |
| move_string(field, offset, flength, retval); |
| retcode = 0; // Okay |
| } |
| else |
| { |
| exception_raise(ec_argument_imp_command_e); |
| retcode = 1;// Error |
| } |
| |
| free(retval); |
| return retcode; |
| } |
| |
| extern "C" |
| void |
| __gg__set_pointer(cblc_field_t *target, |
| size_t target_o, |
| int target_flags, |
| const cblc_field_t *source, |
| size_t source_o, |
| int source_flags) |
| { |
| void *source_address; |
| if( source_flags & REFER_T_ADDRESS_OF ) |
| { |
| // This is SET <something> TO ADDRESS OF SOURCE |
| source_address = source->data + source_o; |
| } |
| else |
| { |
| // This is SET <something> TO POINTER |
| if( source ) |
| { |
| source_address = *reinterpret_cast<void **>(source->data + source_o); |
| } |
| else |
| { |
| // This is SET xxx TO NULL |
| source_address = NULL; |
| } |
| } |
| |
| if( target_flags & REFER_T_ADDRESS_OF ) |
| { |
| // This is SET ADDRESS OF target TO .... |
| // We know it has to be an unqualified LINKAGE level 01 or level 77 |
| target->data = reinterpret_cast<unsigned char *>(source_address); |
| // The caller will propogate data + offset to their children. |
| } |
| else |
| { |
| // This is SET <pointer> TO .... |
| if( source->type == FldLiteralN ) |
| { |
| // This is [almost certainly] INITIALIZE <pointer> when -fdefaultbyte |
| // was specified. |
| memset( target->data+target_o, |
| *reinterpret_cast<unsigned char *>(source_address), |
| target->capacity); |
| } |
| else |
| { |
| *reinterpret_cast<void **>(target->data+target_o) = source_address; |
| } |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__alphabet_use( cbl_encoding_t encoding, |
| size_t alphabet_index) |
| { |
| // We simply replace the values in the current program_state. If the |
| // state needs to be saved -- for example, if we are doing a SORT with an |
| // ALPHABET override -- that's up to the caller |
| |
| // When there is no DATA DIVISION, program_states can be empty when |
| // we arrive here. |
| if( program_states.empty() ) |
| { |
| initialize_program_state(); |
| } |
| |
| switch( encoding ) |
| { |
| case ASCII_e: |
| case iso646_e: |
| __gg__low_value_character = DEGENERATE_LOW_VALUE; |
| __gg__high_value_character = DEGENERATE_HIGH_VALUE; |
| |
| program_states.back().rt_low_value_character = DEGENERATE_LOW_VALUE; |
| program_states.back().rt_high_value_character = DEGENERATE_HIGH_VALUE; |
| |
| if( !internal_is_ebcdic ) |
| { |
| program_states.back().rt_collation = __gg__one_to_one_values; |
| } |
| else |
| { |
| program_states.back().rt_collation = __gg__ebcdic_to_cp1252_collation; |
| } |
| |
| break; |
| |
| case EBCDIC_e: |
| __gg__low_value_character = DEGENERATE_LOW_VALUE; |
| __gg__high_value_character = DEGENERATE_HIGH_VALUE; |
| |
| program_states.back().rt_low_value_character = DEGENERATE_LOW_VALUE; |
| program_states.back().rt_high_value_character = DEGENERATE_HIGH_VALUE; |
| if( internal_is_ebcdic ) |
| { |
| program_states.back().rt_collation = __gg__one_to_one_values; |
| } |
| else |
| { |
| program_states.back().rt_collation = __gg__cp1252_to_ebcdic_collation; |
| } |
| break; |
| |
| case custom_encoding_e: |
| { |
| std::unordered_map<size_t, alphabet_state>::const_iterator it = |
| __gg__alphabet_states.find(alphabet_index); |
| |
| if( it == __gg__alphabet_states.end() ) |
| { |
| __gg__abort("__gg__alphabet_use() fell off the end of __gg__alphabet_states"); |
| } |
| __gg__low_value_character = it->second.low_char; |
| __gg__high_value_character = it->second.high_char; |
| program_states.back().rt_low_value_character = it->second.low_char; |
| program_states.back().rt_high_value_character = it->second.high_char; |
| |
| program_states.back().rt_collation = it->second.collation; |
| break; |
| } |
| } |
| return; |
| } |
| |
| extern "C" |
| void |
| __gg__ascii_to_internal_field(cblc_field_t *var) |
| { |
| ascii_to_internal_str(reinterpret_cast<char *>(var->data), var->capacity); |
| } |
| |
| extern "C" |
| void |
| __gg__ascii_to_internal(char *location, size_t length) |
| { |
| ascii_to_internal_str(location, length); |
| } |
| |
| extern "C" |
| void |
| __gg__console_to_internal(char *location, size_t length) |
| { |
| console_to_internal(location, length); |
| } |
| |
| extern "C" |
| void |
| __gg__parser_set_conditional(cblc_field_t *var, int figconst_) |
| { |
| cbl_figconst_t figconst = (cbl_figconst_t)figconst_; |
| |
| unsigned char special = internal_space; |
| switch(figconst) |
| { |
| case space_value_e: |
| special = *__gg__data_space; |
| break; |
| case low_value_e: |
| special = *__gg__data_low_values; |
| break; |
| case high_value_e: |
| special = *__gg__data_high_values; |
| break; |
| case zero_value_e: |
| special = *__gg__data_zeros; |
| break; |
| case quote_value_e: |
| special = *__gg__data_quotes; |
| break; |
| default: |
| break; |
| } |
| memset( var->data, special, var->capacity); |
| } |
| |
| extern "C" |
| void |
| __gg__internal_to_console_in_place(char *loc, size_t length) |
| { |
| static size_t dest_size = MINIMUM_ALLOCATION_SIZE; |
| static char *dest = static_cast<char *>(malloc(dest_size)); |
| |
| internal_to_console(&dest, &dest_size, loc, length); |
| memcpy(loc, dest, length); |
| } |
| |
| extern "C" |
| int |
| __gg__routine_to_call(const char *name, |
| int program_id) |
| { |
| // The list of names is sorted, so at the very least this should be replaced |
| // with a binary search: |
| |
| std::unordered_map<int, char***>::const_iterator it = |
| accessible_programs.find(program_id); |
| |
| if(it == accessible_programs.end()) |
| { |
| __gg__abort("__gg__routine_to_call() couldn't find program_id"); |
| } |
| |
| char **names = *(it->second); |
| |
| int retval = -1; |
| |
| if( names ) |
| { |
| int i=0; |
| while(*names) |
| { |
| if( strstr(*names, name) ) |
| { |
| // The first part of the names match |
| if( (*names)[strlen(name)] == '.' ) |
| { |
| // And the first character after the match is a '.' |
| retval = i; |
| break; |
| } |
| } |
| i += 1; |
| names += 1; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__fetch_call_by_value_value(const cblc_field_t *field, |
| size_t field_o, |
| size_t field_s) |
| |
| { |
| int rdigits; |
| unsigned char *data = field->data + field_o; |
| const size_t length = field_s; |
| |
| __int128 retval = 0; |
| switch(field->type) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldAlphaEdited: |
| case FldLiteralA: |
| retval = *reinterpret_cast<char *>(data); |
| break; |
| |
| case FldFloat: |
| { |
| switch(length) |
| { |
| case 4: |
| *PTRCAST(float, &retval) = *PTRCAST(float, data); |
| break; |
| |
| case 8: |
| *PTRCAST(double, &retval) = *PTRCAST(double, data); |
| break; |
| |
| case 16: |
| // *(_Float128 *)(&retval) = double(*(_Float128 *)data); |
| GCOB_FP128 t; |
| memcpy(&t, data, 16); |
| memcpy(&retval, &t, 16); |
| break; |
| } |
| break; |
| } |
| |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldNumericDisplay: |
| case FldNumericEdited: |
| case FldLiteralN: |
| case FldIndex: |
| case FldPointer: |
| retval = __gg__binary_value_from_qualified_field( &rdigits, |
| field, |
| field_o, |
| field_s); |
| if( rdigits ) |
| { |
| retval /= __gg__power_of_ten(rdigits); |
| } |
| default: |
| break; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__assign_value_from_stack(cblc_field_t *dest, __int128 parameter) |
| { |
| switch(dest->type) |
| { |
| case FldGroup: |
| case FldAlphanumeric: |
| case FldAlphaEdited: |
| case FldNumericEdited: |
| if( dest->capacity >= 1) |
| { |
| warnx("%s is not valid for BY VALUE", dest->name); |
| abort(); |
| } |
| break; |
| |
| case FldFloat: |
| { |
| switch(dest->capacity) |
| { |
| case 4: |
| *PTRCAST(float, dest->data) = *PTRCAST(float, (¶meter)); |
| break; |
| |
| case 8: |
| *PTRCAST(double, dest->data) = *PTRCAST(double, (¶meter)); |
| break; |
| |
| case 16: |
| // *(_Float128 *)(dest->data) = *(_Float128 *)¶meter; |
| GCOB_FP128 t; |
| memcpy(&t, ¶meter, 16); |
| memcpy(dest->data, &t, 16); |
| break; |
| } |
| break; |
| } |
| |
| case FldNumericBinary: |
| case FldPacked: |
| case FldNumericBin5: |
| case FldNumericDisplay: |
| case FldLiteralN: |
| case FldIndex: |
| case FldPointer: |
| __gg__int128_to_field(dest, |
| parameter, |
| 0, |
| truncation_e, |
| NULL); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| extern "C" |
| int |
| __gg__literaln_alpha_compare(const char *left_side, |
| const cblc_field_t *right, |
| size_t offset, |
| size_t length, |
| int flags) |
| { |
| int retval; |
| if( length == 0 ) |
| { |
| length = right->capacity; |
| } |
| retval = compare_strings( left_side, |
| strlen(left_side), |
| false, |
| reinterpret_cast<char *>((right->data + offset)), |
| length, |
| !!(flags & REFER_T_MOVE_ALL) ); |
| return retval; |
| } |
| |
| static char * |
| string_in( char *str, |
| const char *str_e, |
| const char *frag, |
| const char *frag_e) |
| { |
| // This simple routine could be improved. Instead of using memcmp, we could |
| // use established, albeit complex, techniques of string searching: |
| |
| // Looking for "abcde" in "abcdabcde", for example. One could notice that |
| // starting at the first 'a' results in a mismatch at the second 'a'. There |
| // is thus no need to start the second search at the first 'b' in the searched |
| // string; one could jump ahead to the second 'a' and continue from there. |
| |
| // Feel free. It won't matter in the real world; a program whose innermost |
| // loop is an UNSTRING is difficult to imagine. But feel free. |
| |
| char *retval = NULL; |
| size_t nchars = frag_e - frag; |
| char *p = str; |
| while( p + nchars <= str_e ) |
| { |
| if( memcmp(p, frag, nchars) == 0 ) |
| { |
| retval = p; |
| break; |
| } |
| p += 1; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__unstring( const cblc_field_t *id1, // The string being unstring |
| size_t id1_o, |
| size_t id1_s, |
| size_t ndelimiteds, // The number of DELIMITED entries |
| const char *all_flags, // The number of ALL flags, one per ndelimiteds |
| size_t nreceivers, // The number of DELIMITER receivers |
| cblc_field_t *id7, // The index of characters, both for starting updated at end |
| size_t id7_o, |
| size_t id7_s, |
| cblc_field_t *id8, // Count of the number of times identifier-4 was updated |
| size_t id8_o, |
| size_t id8_s) |
| { |
| // The names of the parameters are based on the ISO 1989:2014 specification. |
| |
| // There are complexities because of figurative constants, including the |
| // LOW-VALUE figurative constant, which precludes the use of string |
| // operations that would be confused by embedded NUL characters. Dammit. |
| |
| // For each delimiter, there is an identifier-4 receiver that must be |
| // resolved. Each might have an identifier-5 delimiter, and each might have |
| // an identifier-6 count. |
| |
| // The delimiting strings; one per ndelimiteds |
| cblc_field_t **id2 = __gg__treeplet_1f; |
| const size_t *id2_o = __gg__treeplet_1o; |
| const size_t *id2_s = __gg__treeplet_1s; |
| // The delimited string; one per nreceiver |
| cblc_field_t **id4 = __gg__treeplet_2f; |
| const size_t *id4_o = __gg__treeplet_2o; |
| const size_t *id4_s = __gg__treeplet_2s; |
| // The delimiting string; one per receiver |
| cblc_field_t **id5 = __gg__treeplet_3f; |
| const size_t *id5_o = __gg__treeplet_3o; |
| const size_t *id5_s = __gg__treeplet_3s; |
| // The count of characters examined; one per receiver |
| cblc_field_t **id6 = __gg__treeplet_4f; |
| const size_t *id6_o = __gg__treeplet_4o; |
| const size_t *id6_s = __gg__treeplet_4s; |
| |
| // Initialize the state variables |
| int overflow = 0; |
| int tally = 0; |
| int pointer = 1; |
| size_t nreceiver; |
| char *left = NULL; |
| char *right = NULL; |
| int previous_delimiter; |
| |
| if( id8 ) |
| { |
| int rdigits; |
| tally = (int)__gg__binary_value_from_qualified_field(&rdigits, |
| id8, |
| id8_o, |
| id8_s); |
| } |
| |
| if( id7 ) |
| { |
| int rdigits; |
| pointer = (int)__gg__binary_value_from_qualified_field(&rdigits, |
| id7, |
| id7_o, |
| id7_s); |
| } |
| |
| // As per the spec, if the string is zero-length; we are done. |
| if( id1_s == 0 ) |
| { |
| goto done; |
| } |
| |
| // As per the spec, we have an overflow condition if pointer is out of |
| // range: |
| if( pointer < 1 || pointer > (int)id1_s ) |
| { |
| overflow = 1; |
| goto done; |
| } |
| |
| left = reinterpret_cast<char *>(id1->data+id1_o) + pointer-1; |
| right = reinterpret_cast<char *>(id1->data+id1_o) + id1_s; |
| |
| if( ndelimiteds == 0 ) |
| { |
| // There are no DELIMITED BY identifier-2 values, so we just peel off |
| // characters from identifier-1 and put them into each identifier-4: |
| for( size_t i=0; i<nreceivers; i++ ) |
| { |
| if( left >= right ) |
| { |
| break; |
| } |
| size_t id_4_size = id4_s[i]; |
| if( id4[i]->attr & separate_e ) |
| { |
| // The receiver is NumericDisplay with a separate signe |
| id_4_size = id4_s[i] - 1; |
| } |
| |
| // Make sure id_4_size doesn't move past the end of the universe |
| if( left + id_4_size > right ) |
| { |
| id_4_size = right - left; |
| } |
| |
| // Move the data into place: |
| move_string(id4[i], id4_o[i], id4_s[i], left, id_4_size); |
| |
| // Update the state variables: |
| left += id_4_size; |
| pointer += id_4_size; |
| tally += 1; |
| } |
| goto done; |
| } |
| |
| // Arriving here means there is some number of ndelimiteds |
| |
| nreceiver = 0; |
| previous_delimiter = -1; |
| while( left < right ) |
| { |
| // Starting at 'left', see if we can find any of the delimiters |
| char *leftmost_delimiter = NULL; |
| int ifound = -1; |
| cbl_figconst_t figconst; |
| char achfigconst[1]; |
| for( size_t i=0; i<ndelimiteds; i++ ) |
| { |
| char *pfound; |
| figconst = (cbl_figconst_t)(id2[i]->attr & FIGCONST_MASK); |
| |
| switch(figconst) |
| { |
| case low_value_e : |
| achfigconst[0] = ascii_to_internal(__gg__low_value_character); |
| pfound = string_in( left, |
| right, |
| achfigconst, |
| achfigconst+1); |
| break; |
| |
| case zero_value_e : |
| achfigconst[0] = internal_zero; |
| pfound = string_in( left, |
| right, |
| achfigconst, |
| achfigconst+1); |
| break; |
| |
| case space_value_e : |
| achfigconst[0] = internal_space; |
| pfound = string_in( left, |
| right, |
| achfigconst, |
| achfigconst+1); |
| break; |
| |
| case quote_value_e : |
| achfigconst[0] = ascii_to_internal(__gg__quote_character); |
| pfound = string_in( left, |
| right, |
| achfigconst, |
| achfigconst+1); |
| break; |
| |
| case high_value_e : |
| achfigconst[0] = ascii_to_internal(__gg__high_value_character); |
| pfound = string_in( left, |
| right, |
| achfigconst, |
| achfigconst+1); |
| break; |
| |
| case normal_value_e : |
| default: |
| pfound = string_in( left, |
| right, |
| reinterpret_cast<char *>(id2[i]->data+id2_o[i]), |
| reinterpret_cast<char *>((id2[i]->data+id2_o[i]) |
| + id2_s[i])); |
| break; |
| } |
| |
| if( pfound ) |
| { |
| // We found a delimiter |
| if( !leftmost_delimiter || pfound < leftmost_delimiter ) |
| { |
| ifound = i; |
| leftmost_delimiter = pfound; |
| } |
| } |
| } |
| |
| if( ifound >= 0 |
| && leftmost_delimiter == left |
| && ifound == previous_delimiter ) |
| { |
| // We found another instance of an ALL delimiter. |
| // So, we just skip it. |
| left += id2_s[previous_delimiter]; |
| pointer += id2_s[previous_delimiter]; |
| continue; |
| } |
| |
| // We did not re-find an ALL DELIMITER |
| previous_delimiter = -1; |
| |
| // If we've used up all receivers, we bail at this point |
| if( nreceiver >= nreceivers ) |
| { |
| break; |
| } |
| |
| if( ifound >= 0 && all_flags[ifound] == ascii_1 ) |
| { |
| // Arriving here means we found a new delimiter. |
| // If the ALL flag was on, set up to notice repeats |
| previous_delimiter = ifound; |
| } |
| |
| if( !leftmost_delimiter ) |
| { |
| // We were unable to find a delimiter, so we eat up the remainder |
| // of the sender: |
| leftmost_delimiter = right; |
| } |
| |
| // Apply what we have learned to the next receiver: |
| |
| size_t examined = leftmost_delimiter - left; |
| |
| // Move the data into place: |
| move_string(id4[nreceiver], id4_o[nreceiver], id4_s[nreceiver], left, examined); |
| |
| // Update the left pointer |
| left = leftmost_delimiter; |
| if( ifound >= 0 ) |
| { |
| // And skip over the delimiter |
| left += id2_s[ifound]; |
| } |
| |
| if( id5[nreceiver] ) |
| { |
| if( ifound >= 0 ) |
| { |
| if( figconst ) |
| { |
| move_string(id5[nreceiver], id5_o[nreceiver], id5_s[nreceiver], |
| achfigconst, |
| 1); |
| } |
| else |
| { |
| move_string(id5[nreceiver], id5_o[nreceiver], id5_s[nreceiver], |
| reinterpret_cast<char *>(id2[ifound]->data+id2_o[ifound]), |
| id2_s[ifound]); |
| } |
| } |
| else |
| { |
| move_string(id5[nreceiver], id5_o[nreceiver], id5_s[nreceiver], ""); |
| } |
| } |
| |
| if( id6[nreceiver] ) |
| { |
| __gg__int128_to_qualified_field(id6[nreceiver], |
| id6_o[nreceiver], |
| id6_s[nreceiver], |
| (__int128)examined, |
| 0, |
| truncation_e, |
| NULL ); |
| } |
| |
| // Update the state variables: |
| pointer += examined + id2_s[ifound]; |
| tally += 1; |
| nreceiver += 1; |
| } |
| |
| done: |
| |
| if( id8 ) |
| { |
| __gg__int128_to_qualified_field(id8, |
| id8_o, |
| id8_s, |
| (__int128)tally, |
| 0, |
| truncation_e, |
| NULL ); |
| } |
| |
| if( id7 ) |
| { |
| __gg__int128_to_qualified_field(id7, |
| id7_o, |
| id7_s, |
| (__int128)pointer, |
| 0, |
| truncation_e, |
| NULL ); |
| } |
| |
| if( left < right ) |
| { |
| overflow = 1; |
| } |
| |
| return overflow; |
| } |
| |
| static std::set<size_t> to_be_canceled; |
| |
| extern "C" |
| void __gg__to_be_canceled(size_t function_pointer) |
| { |
| if( function_pointer ) |
| { |
| to_be_canceled.insert(function_pointer); |
| } |
| } |
| |
| extern "C" |
| int __gg__is_canceled(size_t function_pointer) |
| { |
| int retval = 0; |
| std::set<size_t>::iterator it = to_be_canceled.find(function_pointer); |
| if( it == to_be_canceled.end() ) |
| { |
| retval = 0; |
| } |
| else |
| { |
| retval = 1; |
| to_be_canceled.erase(it); |
| } |
| return retval; |
| } |
| |
| // Tue Feb 28 10:10:16 2023 |
| // Associate specific I/O status an with exception condition. |
| // Cf. ISO Table 14 — Exception-names and exception conditions |
| |
| static inline ec_type_t |
| local_ec_type_of( file_status_t status ) |
| { |
| int status10 = (int)status / 10; |
| assert( 0 <= status10 ); // was enum, can't be negative. |
| if( 10 < status10 ) |
| { |
| __gg__abort("local_ec_type_of(): status10 out of range"); |
| } |
| |
| static const std::vector<ec_type_t> ec_by_status { |
| /* 0 */ ec_none_e, // ec_io_warning_e if low byte is nonzero |
| /* 1 */ ec_io_at_end_e, |
| /* 2 */ ec_io_invalid_key_e, |
| /* 3 */ ec_io_permanent_error_e, |
| /* 4 */ ec_io_logic_error_e, |
| /* 5 */ ec_io_record_operation_e, |
| /* 6 */ ec_io_file_sharing_e, |
| /* 7 */ ec_io_record_content_e, |
| /* 8 */ ec_none_e, // unused, not defined by ISO |
| /* 9 */ ec_io_imp_e, |
| }; |
| assert(ec_by_status.size() == 10); |
| |
| return ec_by_status[status10]; |
| } |
| |
| /* |
| * Store and report the enabled exceptions. |
| * 7.3.20.3 General rules: |
| * 1) The default TURN directive is '>>TURN EC-ALL CHECKING OFF'. |
| */ |
| struct exception_descr_t { |
| bool location; |
| //std::set<size_t> files; |
| }; |
| |
| struct cbl_exception_t { |
| // size_t program, |
| size_t file; |
| ec_type_t type; |
| cbl_file_mode_t mode; |
| }; |
| |
| /* |
| * Compare the raised exception, cbl_exception_t, to the USE critera |
| * of a declarative, cbl_declarative_t. |
| */ |
| static bool |
| match_declarative( bool enabled, |
| const cbl_exception_t& raised, |
| const cbl_declarative_t& dcl ) |
| { |
| if( MATCH_DECLARATIVE && raised.type) { |
| warnx("match_declarative: checking: ec %s vs. dcl %s (%s enabled and %s format_1)", |
| local_ec_type_str(raised.type), |
| local_ec_type_str(dcl.type), |
| enabled? "is" : "not", |
| dcl.is_format_1()? "is" : "not"); |
| } |
| if( ! (enabled || dcl.is_format_1()) ) return false; |
| |
| bool matches = ec_cmp(raised.type, (dcl.type)); |
| |
| if( matches && dcl.nfile > 0 ) { |
| matches = dcl.match_file(raised.file); |
| } |
| |
| // Having matched, the EC must either be enabled, or |
| // the Declarative must be USE Format 1. |
| if( matches ) { |
| // I/O declaratives match by file or mode, not EC. |
| if( dcl.is_format_1() ) { // declarative is for particular files or mode |
| if( dcl.nfile == 0 ) { |
| matches = raised.mode == dcl.mode; |
| } |
| } else { |
| matches = enabled; |
| } |
| |
| if( matches && MATCH_DECLARATIVE ) { |
| warnx(" matches exception %s (file %u mode %s)", |
| local_ec_type_str(raised.type), |
| static_cast<unsigned int>(raised.file), |
| cbl_file_mode_str(raised.mode)); |
| } |
| } |
| return matches; |
| } |
| |
| /* |
| * The default exception handler is called if: |
| * 1. The EC is enabled and was not handled by a Declarative, or |
| * 2. The EC is EC-I-O and was not handled by a Format-1 Declarative, or |
| * 3. The EC is EC-I-O, associated with a file, and is not OPEN or CLOSE. |
| */ |
| static void |
| default_exception_handler( ec_type_t ec ) |
| { |
| #if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME |
| /* Declared in errno.h, when available. */ |
| static const char * const ident = program_invocation_short_name; |
| #elif defined (HAVE_GETPROGNAME) |
| /* Declared in stdlib.h. */ |
| static const char * const ident = getprogname(); |
| #else |
| /* Avoid a NULL entry. */ |
| static const char * const ident = "unnamed_COBOL_program"; |
| #endif |
| static bool first_time = true; |
| static const int priority = LOG_INFO, option = LOG_PERROR, facility = LOG_USER; |
| ec_disposition_t disposition = ec_category_fatal_e; |
| |
| if( first_time ) { |
| // TODO: Program to set option in library via command-line and/or environment. |
| // Library listens to program, not to the environment. |
| openlog(ident, option, facility); |
| first_time = false; |
| } |
| |
| if( ec != ec_none_e ) { |
| auto pec = std::find_if( __gg__exception_table, __gg__exception_table_end, |
| [ec](const ec_descr_t& descr) { |
| return descr.type == ec; |
| } ); |
| if( pec != __gg__exception_table_end ) { |
| disposition = pec->disposition; |
| } else { |
| warnx("logic error: unknown exception %x", ec ); |
| } |
| /* |
| * An enabled, unhandled fatal EC normally results in termination. But |
| * EC-I-O is a special case: |
| * OPEN and CLOSE never result in termination. |
| * A SELECT statement with FILE STATUS indicates the user will handle the error. |
| * Only I/O statements are considered. |
| * Declaratives are handled first. We are in the default handler here, |
| * which is reached only if no Declarative was matched. |
| */ |
| auto file = ec_status.file_status(); |
| const char *filename = nullptr; |
| |
| if( file.ifile ) { |
| filename = file.filename; |
| switch( last_exception_file_operation ) { |
| case file_op_none: // not an I/O statement |
| assert(false); |
| abort(); |
| case file_op_open: |
| case file_op_close: // No OPEN/CLOSE results in a fatal error. |
| disposition = ec_category_none_e; |
| break; |
| default: |
| if( file.user_status ) { |
| // Not fatal if FILE STATUS is part of the file's SELECT statement. |
| disposition = ec_category_none_e; |
| } |
| break; |
| } |
| } else { |
| assert( ec_status.is_enabled() ); |
| assert( ec_status.is_enabled(ec) ); |
| } |
| |
| switch( disposition ) { |
| case ec_category_none_e: |
| case uc_category_none_e: |
| break; |
| case ec_category_fatal_e: |
| case uc_category_fatal_e: |
| if( filename ) { |
| syslog(priority, "fatal exception: %s:%d: %s %s: %s (%s)", |
| program_name, |
| ec_status.lineno, |
| ec_status.statement, |
| filename, // show affected file before EC name |
| pec->name, |
| pec->description); |
| } else { |
| syslog(priority, "fatal exception: %s:%d: %s: %s (%s)", |
| program_name, |
| ec_status.lineno, |
| ec_status.statement, |
| pec->name, |
| pec->description); |
| } |
| abort(); |
| break; |
| case ec_category_nonfatal_e: |
| case uc_category_nonfatal_e: |
| syslog(priority, "%s:%d: %s: %s (%s)", |
| program_name, |
| ec_status.lineno, |
| ec_status.statement, |
| pec->name, |
| pec->description); |
| break; |
| case ec_category_implementor_e: |
| case uc_category_implementor_e: |
| break; |
| } |
| |
| ec_status.clear(); |
| } |
| } |
| |
| /* |
| * To reach the default handler, an EC must have effect and not have been |
| * handled by program logic. To have effect, it must have been enabled |
| * explictly, or be of type EC-I-O. An EC may be handled by the statement or |
| * by a Declarative. |
| * |
| * Any EC handled by statement's conditional clause (e.g. ON SIZE ERROR) |
| * prevents an EC from being raised. Because it is not raised, it is handled |
| * neither by a Declarative, nor by the the default handler. |
| * |
| * A nonfatal EC matched to a Declarative is considered handled. A fatal EC is |
| * considered handled if the Declarative uses RESUME. For any EC that is |
| * handled (with RESUME for fatal), program control passes to the next |
| * statement. Else control passes here first. |
| * |
| * Any EC explicitly enabled (with >>TURN) must be explicitly handled. Only |
| * explicitly enabled ECs appear in enabled_ECs. when EC-I-O is raised as a |
| * byproduct of error status on a file operation, we say it is "implicitly |
| * enabled". It need not be explicitly handled. |
| * |
| * Implicit EC-I-O not handled by the statement or a Declarative is considered |
| * handled if the statement includes the FILE STATUS phrase. OPEN and CLOSE |
| * never cause program termination with EC-I-O; for those two statements the |
| * fatal status is ignored. These conditions are screened out by |
| * __gg__check_fatal_exception(), so that the default handler is not called. |
| * |
| * An unhandled EC reaches the default handler for any of 3 reasons: |
| * 1. It is EC-I-O (enabled does not matter). |
| * 2. It is enabled. |
| * 3. It is fatal and was matched to a Declarative that did not use RESUME. |
| * The default handler, default_exception_handler(), logs the EC. For a fatal |
| * EC, the process terminated with abort(3). |
| * |
| * Except for OPEN and CLOSE, I/O statements that raise an unhandled fatal EC |
| * cause program termination, consistent with IBM documentation. See |
| * Enterprise COBOL for z/OS: Enterprise COBOL for z/OS 6.4 Programming Guide, |
| * page 244, "Handling errors in input and output operations". |
| */ |
| extern "C" |
| void |
| __gg__check_fatal_exception() |
| { |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: ec_status is %s", __func__, ec_status.unset()? "unset" : "set"); |
| |
| if( ec_status.copy_environment().unset() ) |
| { |
| ec_status.update(); // __gg__match_exception was not called first |
| // This is a good time to set the exception code back to zero |
| __gg__exception_code = 0; |
| } |
| |
| if( ec_status.done() ) { // false for part-handled fatal |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: clearing ec_status", __func__); |
| ec_status.clear(); |
| return; // already handled |
| } |
| |
| auto ec = ec_status.unhandled(); |
| |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: %s was not handled %s enabled", __func__, |
| local_ec_type_str(ec), ec_status.is_enabled(ec)? "is" : "is not"); |
| |
| // Look for ways I/O statement might have dealt with EC. |
| auto file = ec_status.file_status(); |
| if( file.ifile && ec_cmp(ec, ec_io_e) ) { |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: %s with %sFILE STATUS", __func__, |
| file.op_str(), file.user_status? "" : "no "); |
| if( file.user_status ) { |
| ec_status.clear(); |
| return; // has FILE STATUS, ok |
| } |
| switch( file.operation ) { |
| case file_op_none: |
| assert(false); |
| abort(); |
| case file_op_open: // implicit, no Declarative, no FILE STATUS, but ok |
| case file_op_close: |
| ec_status.clear(); |
| return; |
| case file_op_start: |
| case file_op_read: |
| case file_op_write: |
| case file_op_rewrite: |
| case file_op_delete: |
| break; |
| } |
| } else { |
| if( ! ec_status.is_enabled() ) { |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: %s is not enabled", __func__, local_ec_type_str(ec)); |
| ec_status.clear(); |
| return; |
| } |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: %s is enabled", __func__, local_ec_type_str(ec)); |
| } |
| |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: calling default_exception_handler(%s)", __func__, |
| local_ec_type_str(ec)); |
| |
| default_exception_handler(ec); |
| } |
| |
| /* |
| * Preserve the state of the raised EC during Declarative execution. |
| */ |
| extern "C" |
| void |
| __gg__exception_push() |
| { |
| ec_stack.push(ec_status); |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: %s: %u ECs, %u declaratives", __func__, |
| __gg__exception_statement, |
| static_cast<unsigned int>(enabled_ECs.size()), |
| static_cast<unsigned int>(declaratives.size())); |
| } |
| |
| /* |
| * Restore the state of the raised EC after Declarative execution. |
| */ |
| extern "C" |
| void |
| __gg__exception_pop() |
| { |
| ec_status = ec_stack.top(); |
| ec_stack.pop(); |
| ec_status.reset_environment(); |
| if( MATCH_DECLARATIVE ) |
| warnx("%s: %s: %u ECs, %u declaratives", __func__, |
| __gg__exception_statement, |
| static_cast<unsigned int>(enabled_ECs.size()), |
| static_cast<unsigned int>(declaratives.size())); |
| __gg__check_fatal_exception(); |
| } |
| |
| // Called for RESUME in a Declarative to indicate a fatal EC was handled. |
| extern "C" |
| void |
| __gg__clear_exception() |
| { |
| ec_stack.top().clear(); |
| } |
| |
| void |
| cbl_enabled_exception_t::dump( int i ) const { |
| warnx("cbl_enabled_exception_t: %2d {%s, %s, %u}", |
| i, |
| location? "location" : " none", |
| local_ec_type_str(ec), |
| static_cast<unsigned int>(file) ); |
| } |
| |
| /* |
| * Match the raised exception against a Declarative. |
| * |
| * A Declarative that handles I/O errors with USE Format 1 doesn't name a |
| * specific EC. It's matched based on the file's status, irrespective of |
| * whether or not EC-I-O is enabled. USE Format 1 Declaratives are honored |
| * regardless of any >>TURN directive. |
| * |
| * An EC is enabled by the >>TURN directive. The only ECs that can be disabled |
| * are those that were explicitly enabled. If EC-I-O is enabled, and mentioned |
| * in a Declarative with USE Format 3, then it is matched just like any other. |
| */ |
| extern "C" |
| void |
| __gg__match_exception( cblc_field_t *index ) |
| { |
| size_t isection = 0; |
| |
| if( MATCH_DECLARATIVE ) enabled_ECs.dump("match_exception begin"); |
| |
| auto ec = ec_status.update().unhandled(); |
| |
| if( ec != ec_none_e ) { |
| /* |
| * An EC was raised and was not handled by the statement. |
| * We know the EC and, for I/O, the current file and its mode. |
| * Scan declaratives for a match: |
| * - EC is enabled or program has a Format 1 Declarative |
| * - EC matches the Declarative's USE statement |
| * Format 1 declaratives apply only to EC-I-O, whether or not enabled. |
| * Format 1 may be restricted to a particular mode (for all files). |
| * Format 1 and 3 may be restricted to a set of files. |
| */ |
| |
| // This is a good time to set the actual exception code back to zero. |
| __gg__exception_code = 0; |
| |
| auto f = ec_status.file_status(); |
| cbl_exception_t raised = { /*0,*/ f.ifile, ec, f.mode }; |
| bool enabled = enabled_ECs.match(ec); |
| |
| if( MATCH_DECLARATIVE ) enabled_ECs.dump("match_exception enabled"); |
| |
| auto p = std::find_if( declaratives.begin(), declaratives.end(), |
| [enabled, raised]( const cbl_declarative_t& dcl ) { |
| return match_declarative(enabled, raised, dcl); |
| } ); |
| |
| if( p == declaratives.end() ) { |
| if( MATCH_DECLARATIVE ) { |
| warnx("__gg__match_exception:%d: raised exception " |
| "%s not matched (%u enabled)", __LINE__, |
| local_ec_type_str(ec), |
| static_cast<unsigned int>(enabled_ECs.size())); |
| } |
| } else { |
| isection = p->section; |
| ec_status.handled_by(isection); |
| |
| if( MATCH_DECLARATIVE ) { |
| warnx("__gg__match_exception:%d: matched " |
| "%s against mask %s for section #%u", |
| __LINE__, |
| local_ec_type_str(ec), |
| local_ec_type_str(p->type), |
| static_cast<unsigned int>(p->section)); |
| } |
| } |
| assert(ec != ec_none_e); |
| } // end EC match logic |
| |
| // If a declarative matches the raised exception, return its |
| // symbol_table index. |
| __gg__int128_to_field(index, |
| (__int128)isection, |
| 0, |
| truncation_e, |
| NULL); |
| } |
| |
| static std::vector<void *>proc_signatures; |
| static std::vector<void *>return_addresses; |
| static std::vector<size_t>bookmarks; |
| |
| extern "C" |
| void |
| __gg__pseudo_return_push( void *proc_signature, |
| void *return_address) |
| { |
| proc_signatures.push_back(proc_signature); |
| return_addresses.push_back(return_address); |
| __gg__exit_address = proc_signature; |
| } |
| |
| extern "C" |
| void * |
| __gg__pseudo_return_pop() |
| { |
| void *retval = return_addresses.back(); |
| |
| return_addresses.pop_back(); |
| proc_signatures.pop_back(); |
| |
| if( proc_signatures.size() > bookmarks.back() ) |
| { |
| __gg__exit_address = proc_signatures.back(); |
| } |
| else |
| { |
| // We can't go below the floor established by the bookmark. |
| __gg__exit_address = NULL; |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__pseudo_return_bookmark() |
| { |
| bookmarks.push_back(proc_signatures.size()); |
| } |
| |
| extern "C" |
| void |
| __gg__pseudo_return_flush() |
| { |
| if( bookmarks.size() == 0 ) |
| { |
| __gg__abort("__gg__pseudo_return_flush(): bookmarks.size() is zero"); |
| } |
| proc_signatures.resize(bookmarks.back()); |
| return_addresses.resize(bookmarks.back()); |
| bookmarks.pop_back(); |
| |
| if( proc_signatures.size() ) |
| { |
| __gg__exit_address = proc_signatures.back(); |
| } |
| } |
| |
| extern "C" |
| GCOB_FP128 |
| __gg__float128_from_location( const cblc_field_t *var, |
| const unsigned char *location) |
| { |
| GCOB_FP128 retval = 0; |
| switch( var->capacity ) |
| { |
| case 4: |
| { |
| retval = *reinterpret_cast<_Float32 *>( |
| const_cast<unsigned char *>(location)); |
| break; |
| } |
| |
| case 8: |
| { |
| retval = *reinterpret_cast<_Float64 *>( |
| const_cast<unsigned char *>(location)); |
| break; |
| } |
| |
| case 16: |
| { |
| //retval = *(_Float128 *)location; |
| memcpy(&retval, location, 16); |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| __int128 |
| __gg__integer_from_float128(const cblc_field_t *field) |
| { |
| GCOB_FP128 fvalue = __gg__float128_from_location(field, field->data); |
| // we round() to take care of the possible 2.99999999999... problem. |
| fvalue = FP128_FUNC(round)(fvalue); |
| return (__int128)fvalue; |
| } |
| |
| |
| extern "C" |
| void |
| __gg__adjust_dest_size(cblc_field_t *dest, size_t ncount) |
| { |
| if( dest->attr & (intermediate_e) ) |
| { |
| if( dest->allocated < ncount ) |
| { |
| fprintf(stderr, "libgcobol.cc:__gg__adjust_dest_size(): Adjusting size upward is not possible.\n"); |
| abort(); |
| // dest->allocated = ncount; |
| // dest->data = (unsigned char *)realloc(dest->data, ncount); |
| } |
| dest->capacity = ncount; |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__func_exception_location(cblc_field_t *dest) |
| { |
| char ach[512] = " "; |
| if( last_exception_code ) |
| { |
| ach[0] = '\0'; |
| if( last_exception_program_id ) |
| { |
| strcat(ach, last_exception_program_id); |
| strcat(ach, "; "); |
| } |
| |
| if( last_exception_paragraph ) |
| { |
| strcat(ach, last_exception_paragraph ); |
| if( last_exception_section ) |
| { |
| strcat(ach, " OF "); |
| strcat(ach, last_exception_section); |
| } |
| } |
| else |
| { |
| if( last_exception_section ) |
| { |
| strcat(ach, last_exception_section); |
| } |
| } |
| strcat(ach, "; "); |
| |
| if( last_exception_source_file ) |
| { |
| char achSource[128] = ""; |
| snprintf( achSource, |
| sizeof(achSource), |
| "%s:%d ", |
| last_exception_source_file, |
| last_exception_line_number); |
| strcat(ach, achSource); |
| } |
| else |
| { |
| strcpy(ach, " "); |
| } |
| } |
| __gg__adjust_dest_size(dest, strlen(ach)); |
| memcpy(dest->data, ach, strlen(ach)); |
| } |
| |
| extern "C" |
| void |
| __gg__func_exception_statement(cblc_field_t *dest) |
| { |
| char ach[128] = " "; |
| if(last_exception_statement) |
| { |
| snprintf(ach, sizeof(ach), "%s", last_exception_statement); |
| ach[sizeof(ach)-1] = '\0'; |
| } |
| __gg__adjust_dest_size(dest, strlen(ach)); |
| memcpy(dest->data, ach, strlen(ach)); |
| } |
| |
| extern "C" |
| void |
| __gg__func_exception_status(cblc_field_t *dest) |
| { |
| char ach[128] = "<not in table?>"; |
| if(last_exception_code) |
| { |
| ec_descr_t *p = __gg__exception_table; |
| while(p < __gg__exception_table_end ) |
| { |
| if( p->type == (ec_type_t)last_exception_code ) |
| { |
| snprintf(ach, sizeof(ach), "%s", p->name); |
| break; |
| } |
| p += 1; |
| } |
| } |
| else |
| { |
| strcpy(ach, " "); |
| } |
| __gg__adjust_dest_size(dest, strlen(ach)); |
| memcpy(dest->data, ach, strlen(ach)); |
| } |
| |
| extern "C" |
| void |
| __gg__set_exception_file(const cblc_file_t *file) |
| { |
| ec_type_t ec = local_ec_type_of( file->io_status ); |
| if( ec ) |
| { |
| // During SORT operations, which routinely read files until they end, we |
| // need to suppress them. |
| if( ec != ec_io_at_end_e || !sv_suppress_eof_ec ) |
| { |
| last_exception_file_operation = file->prior_op; |
| last_exception_file_status = file->io_status; |
| last_exception_file_name = file->name; |
| exception_raise(ec); |
| } |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__func_exception_file(cblc_field_t *dest, |
| const cblc_file_t *file) |
| { |
| char ach[128]; |
| if( !file ) |
| { |
| // This is where we process FUNCTION EXCEPTION-FILE <no parameter> |
| if( !(last_exception_code & ec_io_e) ) |
| { |
| // There is no EC-I-O exception code, so we return two alphanumeric zeros. |
| strcpy(ach, "00"); |
| } |
| else |
| { |
| // The last exception code is an EC-I-O |
| if( sv_from_raise_statement ) |
| { |
| strcpy(ach, " "); |
| } |
| else |
| { |
| snprintf( ach, |
| sizeof(ach), "%2.2d%s", |
| last_exception_file_status, |
| last_exception_file_name); |
| } |
| } |
| } |
| else |
| { |
| // This is where we process FUNCTION EXCEPTION-FILE file->name |
| if( file->prior_op == file_op_none ) |
| { |
| // this file hasn't been accessed |
| strcpy(ach, " "); |
| } |
| else |
| { |
| snprintf(ach, sizeof(ach), "%2.2d%s", file->io_status, file->name); |
| } |
| } |
| |
| __gg__adjust_dest_size(dest, strlen(ach)); |
| memcpy(dest->data, ach, strlen(ach)); |
| } |
| |
| extern "C" |
| void |
| __gg__set_exception_code(ec_type_t ec, int from_raise_statement) |
| { |
| if( MATCH_DECLARATIVE ) |
| { |
| warnx("%s: %s:%u: %s: %s", |
| __func__, |
| __gg__exception_source_file, |
| __gg__exception_line_number, |
| __gg__exception_statement, |
| local_ec_type_str(ec)); |
| } |
| sv_from_raise_statement = from_raise_statement; |
| |
| __gg__exception_code = ec; |
| if( ec == ec_none_e) |
| { |
| last_exception_code = 0 ; |
| last_exception_program_id = NULL ; |
| last_exception_section = NULL ; |
| last_exception_paragraph = NULL ; |
| last_exception_source_file = NULL ; |
| last_exception_line_number = 0 ; |
| last_exception_statement = NULL ; |
| last_exception_file_operation = file_op_none ; |
| last_exception_file_status = FsSuccess ; |
| last_exception_file_name = NULL ; |
| } |
| else |
| { |
| last_exception_code = __gg__exception_code ; |
| last_exception_program_id = __gg__exception_program_id ; |
| last_exception_section = __gg__exception_section ; |
| last_exception_paragraph = __gg__exception_paragraph ; |
| last_exception_source_file = __gg__exception_source_file ; |
| last_exception_line_number = __gg__exception_line_number ; |
| last_exception_statement = __gg__exception_statement ; |
| |
| // These are set in __gg__set_exception_file just before this routine is |
| // called. In cases where the ec is not a file-i-o operation, we clear |
| // them here: |
| if( !(ec & ec_io_e) ) |
| { |
| last_exception_file_operation = file_op_none ; |
| last_exception_file_status = FsSuccess ; |
| last_exception_file_name = NULL ; |
| } |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__float32_from_int128(cblc_field_t *destination, |
| size_t destination_offset, |
| cblc_field_t *source, |
| size_t source_offset, |
| cbl_round_t rounded, |
| int *size_error) |
| { |
| int rdigits; |
| GCOB_FP128 value = get_binary_value_local( &rdigits, |
| source, |
| source->data + source_offset, |
| source->capacity); |
| value /= __gg__power_of_ten(rdigits); |
| |
| if( FP128_FUNC(fabs)(value) > GCOB_FP128_LITERAL (3.4028235E38) ) |
| { |
| if(size_error) |
| { |
| *size_error = 1; |
| } |
| } |
| else |
| { |
| if(size_error) |
| { |
| *size_error = 0; |
| } |
| __gg__float128_to_qualified_field(destination, |
| destination_offset, |
| value, |
| rounded, |
| NULL); |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__float64_from_int128(cblc_field_t *destination, |
| size_t destination_offset, |
| cblc_field_t *source, |
| size_t source_offset, |
| cbl_round_t rounded, |
| int *size_error) |
| { |
| if(size_error) |
| { |
| *size_error = 0; |
| } |
| int rdigits; |
| GCOB_FP128 value = get_binary_value_local( &rdigits, |
| source, |
| source->data + source_offset, |
| source->capacity); |
| value /= __gg__power_of_ten(rdigits); |
| __gg__float128_to_qualified_field(destination, |
| destination_offset, |
| value, |
| rounded, |
| NULL); |
| } |
| |
| extern "C" |
| void |
| __gg__float128_from_int128(cblc_field_t *destination, |
| size_t destination_offset, |
| cblc_field_t *source, |
| size_t source_offset, |
| cbl_round_t rounded, |
| int *size_error) |
| { |
| if(size_error) *size_error = 0; |
| int rdigits; |
| GCOB_FP128 value = get_binary_value_local( &rdigits, |
| source, |
| source->data + source_offset, |
| source->capacity); |
| value /= __gg__power_of_ten(rdigits); |
| __gg__float128_to_qualified_field(destination, |
| destination_offset, |
| value, |
| rounded, |
| NULL); |
| } |
| |
| extern "C" |
| int |
| __gg__is_float_infinite(const cblc_field_t *source, size_t offset) |
| { |
| int retval = 0; |
| switch(source->capacity) |
| { |
| case 4: |
| retval = fpclassify( *reinterpret_cast<_Float32*>(source->data+offset)) == FP_INFINITE; |
| break; |
| case 8: |
| retval = fpclassify( *reinterpret_cast<_Float64*>(source->data+offset)) == FP_INFINITE; |
| break; |
| case 16: |
| // retval = *(_Float128*)(source->data+offset) == INFINITY; |
| GCOB_FP128 t; |
| memcpy(&t, source->data+offset, 16); |
| retval = t == INFINITY; |
| break; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__float32_from_128( const cblc_field_t *dest, |
| size_t dest_offset, |
| const cblc_field_t *source, |
| size_t source_offset) |
| { |
| int retval = 0; |
| //_Float128 value = *(_Float128*)(source->data+source_offset); |
| GCOB_FP128 value; |
| memcpy(&value, source->data+source_offset, 16); |
| if( FP128_FUNC(fabs)(value) > GCOB_FP128_LITERAL (3.4028235E38) ) |
| { |
| retval = 1; |
| } |
| else |
| { |
| *reinterpret_cast<_Float32 *>(dest->data+dest_offset) = (_Float32)value; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__float32_from_64( const cblc_field_t *dest, |
| size_t dest_offset, |
| const cblc_field_t *source, |
| size_t source_offset) |
| { |
| int retval = 0; |
| _Float64 value = *reinterpret_cast<_Float64*>(source->data+source_offset); |
| if( FP128_FUNC(fabs)(value) > GCOB_FP128_LITERAL (3.4028235E38) ) |
| { |
| retval = 1; |
| } |
| else |
| { |
| *reinterpret_cast<_Float32 *>(dest->data+dest_offset) = (_Float32)value; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| int |
| __gg__float64_from_128( const cblc_field_t *dest, |
| size_t dest_offset, |
| const cblc_field_t *source, |
| size_t source_offset) |
| { |
| int retval = 0; |
| // _Float128 value = *(_Float128*)(source->data+source_offset); |
| GCOB_FP128 value; |
| memcpy(&value, source->data+source_offset, 16); |
| if( FP128_FUNC(fabs)(value) > GCOB_FP128_LITERAL(1.7976931348623157E308) ) |
| { |
| retval = 1; |
| } |
| else |
| { |
| *reinterpret_cast<_Float64 *>(dest->data+dest_offset) = (_Float64)value; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| char * |
| __gg__display_int128(__int128 value) |
| { |
| static char ach[64]; |
| |
| if( value == 0 ) |
| { |
| strcpy(ach, "0"); |
| return ach; |
| } |
| |
| bool is_negative = false; |
| if( value < 0 ) |
| { |
| is_negative = true; |
| value = -value; |
| } |
| char *p = ach+64; |
| *--p = '\0'; |
| |
| while(value) |
| { |
| *--p = value%10 + '0'; |
| value /= 10; |
| } |
| if( is_negative ) |
| { |
| *--p = '-'; |
| } |
| return p; |
| } |
| |
| typedef struct LV_DATA |
| { |
| size_t unique_id; |
| cblc_field_t *field; |
| unsigned char *data; |
| }LV_DATA; |
| |
| static std::vector<LV_DATA> lv_variables; |
| |
| extern "C" |
| void |
| __gg__push_local_variable(cblc_field_t *field) |
| { |
| LV_DATA lv_data; |
| lv_data.unique_id = __gg__unique_prog_id; |
| lv_data.field = field; |
| lv_data.data = field->data; |
| lv_variables.push_back(lv_data); |
| } |
| |
| extern "C" |
| void |
| __gg__pop_local_variables() |
| { |
| while( lv_variables.size() |
| && lv_variables.back().unique_id == __gg__unique_prog_id) |
| { |
| lv_variables.back().field->data = lv_variables.back().data; |
| lv_variables.pop_back(); |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__copy_as_big_endian( unsigned char *dest, |
| const unsigned char *source) |
| { |
| // copy eight bytes of source to dest, flipping the endianness |
| for(size_t i=0; i<8; i++) |
| { |
| dest[i] = source[7-i]; |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__codeset_figurative_constants() |
| { |
| // This routine gets called after the codeset has been changed |
| *__gg__data_space = internal_space; |
| *__gg__data_low_values = ascii_to_internal(__gg__low_value_character); |
| *__gg__data_zeros = internal_0; |
| *__gg__data_high_values = ascii_to_internal(__gg__high_value_character); |
| *__gg__data_quotes = ascii_to_internal(__gg__quote_character);; |
| } |
| |
| extern "C" |
| unsigned char * |
| __gg__get_figconst_data(const cblc_field_t *field) |
| { |
| unsigned char *retval = NULL; |
| cbl_figconst_t figconst = (cbl_figconst_t)(size_t)(field->initial); |
| switch(figconst) |
| { |
| case low_value_e : |
| retval = __gg__data_low_values; |
| break; |
| case zero_value_e : |
| retval = __gg__data_zeros; |
| break; |
| case space_value_e : |
| retval = __gg__data_space; |
| break; |
| case quote_value_e : |
| retval = __gg__data_quotes; |
| break; |
| case high_value_e : |
| retval = __gg__data_high_values; |
| break; |
| case normal_value_e : |
| fprintf(stderr, "__gg__get_figconst_data(): Weird figconst\n"); |
| abort(); |
| break; |
| case null_value_e: |
| break; |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__set_program_list( int program_id, |
| char ***prog_list, |
| PFUNC **prog_pointers) |
| { |
| accessible_programs[program_id] = prog_list; |
| accessible_pointers[program_id] = prog_pointers; |
| } |
| |
| static std::unordered_map<std::string, void *> already_found; |
| |
| static |
| void * |
| find_in_dirs(const char *dirs, char *unmangled_name, char *mangled_name) |
| { |
| |
| std::unordered_map<std::string, void *>::const_iterator it = |
| already_found.find(unmangled_name); |
| |
| if( it != already_found.end() ) |
| { |
| return it->second; |
| } |
| it = already_found.find(mangled_name); |
| if( it != already_found.end() ) |
| { |
| return it->second; |
| } |
| |
| void *retval = NULL; |
| if( dirs ) |
| { |
| char directory[1024]; |
| char file[1024]; |
| const char *p = dirs; |
| while( !retval && *p ) |
| { |
| size_t index = 0; |
| while( index < sizeof(directory)-1 && *p && *p != ':' ) |
| { |
| directory[index++] = *p++; |
| } |
| directory[index++] = '\0'; |
| if( *p == ':' ) |
| { |
| p += 1; |
| } |
| // directory is the next one for us to check: |
| DIR *dir = opendir(directory); |
| if( dir ) |
| { |
| while( !retval ) |
| { |
| const dirent *entry = readdir(dir); |
| if( !entry ) |
| { |
| break; |
| } |
| size_t len = strlen(entry->d_name); |
| if( len > 3 |
| && entry->d_name[len-3] == '.' |
| && entry->d_name[len-2] == 's' |
| && entry->d_name[len-1] == 'o' |
| ) |
| { |
| strcpy(file, directory); |
| strcat(file, "/"); |
| strcat(file, entry->d_name); |
| void *handle = dlopen(file, RTLD_LAZY|RTLD_NODELETE ); |
| if( handle ) |
| { |
| retval = dlsym(handle, unmangled_name); |
| if( retval ) |
| { |
| already_found[unmangled_name] = retval; |
| break; |
| } |
| retval = dlsym(handle, mangled_name); |
| if( retval ) |
| { |
| already_found[mangled_name] = retval; |
| break; |
| } |
| dlclose(handle); |
| } |
| } |
| } |
| closedir(dir); |
| } |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void * |
| __gg__function_handle_from_cobpath( char *unmangled_name, char *mangled_name) |
| { |
| void *retval = NULL; |
| |
| // We search for a function. We check first for the unmangled name, and then |
| // the mangled name. We do this first for the executable, then for .so |
| // files in COBPATH, and then for files in LD_LIBRARY_PATH |
| |
| static void *handle_executable = NULL; |
| if( !handle_executable ) |
| { |
| handle_executable = dlopen(NULL, RTLD_LAZY); |
| } |
| //if( !retval ) |
| { |
| retval = dlsym(handle_executable, unmangled_name); |
| } |
| if( !retval ) |
| { |
| retval = dlsym(handle_executable, mangled_name); |
| } |
| if( !retval ) |
| { |
| const char *COBPATH = getenv("GCOBOL_LIBRARY_PATH"); |
| retval = find_in_dirs(COBPATH, unmangled_name, mangled_name); |
| } |
| if( !retval ) |
| { |
| const char *LD_LIBRARY_PATH = getenv("LD_LIBRARY_PATH"); |
| retval = find_in_dirs(LD_LIBRARY_PATH, unmangled_name, mangled_name); |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__just_mangle_name( const cblc_field_t *field, |
| char **mangled_name |
| ) |
| { |
| static char ach_name[1024]; |
| static char ach_unmangled[1024]; |
| static char ach_mangled[1024]; |
| |
| assert(field); |
| assert(field->data); |
| |
| size_t length; |
| length = field->capacity; |
| memcpy(ach_name, field->data, length); |
| ach_name[length] = '\0'; |
| |
| if( internal_is_ebcdic) |
| { |
| // The name is in EBCDIC |
| __gg__ebcdic_to_ascii(ach_name, length); |
| } |
| |
| bool is_pointer = false; |
| |
| if( field->type == FldPointer ) |
| { |
| is_pointer = true; |
| } |
| |
| if( is_pointer ) |
| { |
| strcpy(ach_name, "<pointer>"); |
| } |
| |
| // At this point we have a null-terminated ascii function name. |
| |
| // Convert it to both unmangled and mangled forms: |
| strcpy(ach_unmangled, not_mangled_core(ach_name, ach_name+length)); |
| strcpy(ach_mangled, mangler_core( ach_name, ach_name+length)); |
| |
| if( mangled_name ) |
| { |
| *mangled_name = ach_mangled; |
| } |
| } |
| |
| extern "C" |
| void * |
| __gg__function_handle_from_literal(int program_id, |
| const char *literal) |
| { |
| void *retval = NULL; |
| static char ach_unmangled[1024]; |
| static char ach_mangled[1024]; |
| |
| size_t length; |
| |
| length = strlen(literal); |
| |
| // At this point we have a null-terminated ascii function name. |
| |
| // Convert it to both unmangled and mangled forms: |
| strcpy(ach_unmangled, not_mangled_core(literal, literal+length)); |
| strcpy(ach_mangled, mangler_core( literal, literal+length)); |
| |
| int function_index = __gg__routine_to_call(ach_mangled, program_id); |
| if( function_index > -1 ) |
| { |
| std::unordered_map<int, PFUNC**>::const_iterator it = |
| accessible_pointers.find(program_id); |
| if( it == accessible_pointers.end() ) |
| { |
| __gg__abort("__gg__function_handle_from_literal():" |
| " fell off the end of accessible_pointers"); |
| } |
| PFUNC **pointers_p = it->second; |
| PFUNC *pointers = *pointers_p; |
| retval = reinterpret_cast<void *>(pointers[function_index]); |
| } |
| else |
| { |
| retval = __gg__function_handle_from_cobpath(ach_unmangled, ach_mangled); |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| void * |
| __gg__function_handle_from_name(int program_id, |
| const cblc_field_t *field, |
| size_t offset, |
| size_t length ) |
| { |
| void *retval = NULL; |
| static char ach_name[1024]; |
| static char ach_unmangled[1024]; |
| static char ach_mangled[1024]; |
| |
| if( length == 0 ) |
| { |
| length = field->capacity; |
| } |
| |
| memcpy(ach_name, field->data + offset, length); |
| |
| if( internal_is_ebcdic) |
| { |
| // The name is in EBCDIC |
| __gg__ebcdic_to_ascii(ach_name, length); |
| } |
| |
| // At this point we have a null-terminated ascii function name. |
| |
| // Convert it to both unmangled and mangled forms: |
| strcpy(ach_unmangled, not_mangled_core(ach_name, ach_name+length)); |
| strcpy(ach_mangled, mangler_core( ach_name, ach_name+length)); |
| |
| int function_index = __gg__routine_to_call(ach_mangled, program_id); |
| if( function_index > -1 ) |
| { |
| std::unordered_map<int, PFUNC**>::const_iterator it = |
| accessible_pointers.find(program_id); |
| if(it == accessible_pointers.end()) |
| { |
| __gg__abort("__gg__function_handle_from_name():" |
| " fell off the end of accessible_pointers"); |
| } |
| PFUNC **pointers_p = it->second; |
| PFUNC *pointers = *pointers_p; |
| retval = reinterpret_cast<void *>(pointers[function_index]); |
| } |
| else |
| { |
| retval = __gg__function_handle_from_cobpath(ach_unmangled, ach_mangled); |
| } |
| |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__variables_to_init(cblc_field_t *array[], const char *clear) |
| { |
| int i=0; |
| for(;;) |
| { |
| cblc_field_t *field = array[i++]; |
| if( !field ) |
| { |
| break; |
| } |
| int flag_bits = 0; |
| flag_bits |= clear |
| ? DEFAULTBYTE_BIT + (*clear & DEFAULT_BYTE_MASK) |
| : 0; |
| __gg__initialize_variable_clean(field, flag_bits); |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__mirror_range( size_t nrows, |
| cblc_field_t *src, // The row |
| size_t src_o, |
| size_t nspans, // The number of spans |
| const size_t *spans, |
| size_t table, |
| size_t ntbl, |
| const size_t *tbls) |
| { |
| static std::unordered_map<size_t, size_t> rows_in_table; |
| static std::unordered_map<size_t, size_t> widths_of_table; |
| static std::unordered_map<size_t, std::vector<size_t>> spans_in_table; |
| |
| // Let's do the memorization bookkeeping for the target table |
| // We have to do this every time, because if we have memory, everything gets |
| // higgledepiggledy when INITIALZE ALL FILLER is followed by |
| // INITIALIZE ALL VALUE |
| std::vector<size_t> spans_this_time(2*nspans); |
| for(size_t i=0; i<2*nspans; i++) |
| { |
| spans_this_time[i] = spans[i]; |
| } |
| rows_in_table[table] = nrows; |
| spans_in_table[table] = spans_this_time; |
| |
| // We need to know the width of one row of this table, which is different |
| // depending on type of src: |
| |
| const cblc_field_t *parent = src; |
| while( parent ) |
| { |
| if( parent->occurs_upper ) |
| { |
| break; |
| } |
| } |
| if( parent == nullptr ) |
| { |
| __gg__abort("__gg__mirror_range() parent is NULL"); |
| } |
| size_t width; |
| if( parent->type == FldGroup ) |
| { |
| width = parent->capacity; |
| } |
| else |
| { |
| width = parent->capacity * parent->occurs_upper; |
| } |
| widths_of_table[table] = width; |
| |
| if( nspans == 0 && ntbl == 0 ) |
| { |
| // There are no FILLERS to be skipped, so we can just do our cute line |
| // duplicating trick. We understand that there isn't actually much in the |
| // the way of computational savings -- every byte has to be written, after |
| // all -- but it is satisfying. |
| size_t stride = src->capacity; |
| unsigned char *source = src->data + src_o; |
| unsigned char *dest = source + stride; |
| nrows -= 1; // This reflects that row[0] equals row[0] |
| |
| size_t nrows_this_time = 1; |
| while(nrows) |
| { |
| memcpy(dest, source, nrows_this_time * stride); |
| dest += nrows_this_time * stride; |
| nrows -= nrows_this_time; |
| nrows_this_time *= 2; |
| nrows_this_time = std::min(nrows_this_time, nrows); |
| } |
| } |
| else |
| { |
| size_t stride = src->capacity; |
| unsigned char *source = src->data + src_o; |
| unsigned char *dest = source; |
| |
| while( nrows-- ) |
| { |
| // These spans are for non-table elements |
| if( nspans == 0 ) |
| { |
| // Special case: We have no spans, but in the case of, for example, |
| // 01 four-by-four2. |
| // 05 label5 pic x(12) value "four-by-four". |
| // 05 four-outer2 occurs 4 times. |
| // 10 four-inner2 occurs 4 times. |
| // 15 label15 pic x(12) value "four-inner". |
| // 15 FNAME PIC X(7) VALUE "James". |
| // 15 FILLER PIC X(7) VALUE "Keen ". |
| // 15 LNAME PIC X(7) VALUE "Lowden". |
| // 10 label10 pic x(12) value "four-outer". |
| // |
| // the label10 field *should* be a span. The parser doesn't do that, |
| // though, so we have to: |
| size_t left = 0; |
| size_t right = stride; |
| for(size_t subtable=0; subtable<ntbl; subtable++) |
| { |
| size_t subtable_offset = tbls[2*subtable ]; |
| size_t subtable_index = tbls[2*subtable+1]; |
| |
| if( widths_of_table.find(subtable_index) == widths_of_table.end() ) |
| { |
| __gg__abort("__gg__mirror_range() fell off widths_of_table"); |
| } |
| size_t subtable_width = widths_of_table[subtable_index]; |
| size_t subtable_rows = rows_in_table [subtable_index]; |
| |
| right = subtable_offset; |
| |
| memcpy(dest + left, |
| source + left , |
| right - left); |
| left = right + subtable_rows * subtable_width; |
| // Set right to stride, in case this is the final 'subtable' iteration |
| right = stride; |
| } |
| |
| if( left < right ) |
| { |
| memcpy(dest + left, |
| source + left , |
| right - left); |
| } |
| } |
| for(size_t span=0; span<nspans; span++) |
| { |
| size_t offset = spans[2*span]; |
| size_t length = spans[2*span + 1] - offset; |
| memcpy( dest+offset, source+offset, length); |
| } |
| |
| // Our table's row might have sub-tables: |
| for(size_t subtable=0; subtable<ntbl; subtable++) |
| { |
| size_t subtable_offset = tbls[2*subtable ]; |
| size_t subtable_index = tbls[2*subtable+1]; |
| |
| if( widths_of_table.find(subtable_index) == widths_of_table.end() ) |
| { |
| __gg__abort("__gg__mirror_range(): fell off widths of table"); |
| } |
| |
| size_t subtable_stride = widths_of_table[subtable_index]; |
| size_t subtable_rows = rows_in_table [subtable_index]; |
| std::vector<size_t> subtable_spans |
| = spans_in_table [subtable_index]; |
| |
| const unsigned char *subtable_source = source + subtable_offset; |
| |
| if( subtable_spans.size() == 0 ) |
| { |
| unsigned char *subtable_dest = dest + subtable_offset; |
| size_t span_start = 0; |
| size_t span_end = subtable_stride; |
| size_t subtable_row = subtable_rows; |
| while( subtable_row-- ) |
| { |
| memcpy(subtable_dest + span_start, |
| subtable_source + span_start, |
| span_end - span_start); |
| subtable_dest += subtable_stride; |
| } |
| } |
| |
| for(size_t span=0; span<subtable_spans.size(); span += 2 ) |
| { |
| unsigned char *subtable_dest = dest + subtable_offset; |
| size_t span_start = subtable_spans[span ]; |
| size_t span_end = subtable_spans[span+1]; |
| size_t subtable_row = subtable_rows; |
| while( subtable_row-- ) |
| { |
| memcpy(subtable_dest + span_start, |
| subtable_source + span_start, |
| span_end - span_start); |
| subtable_dest += subtable_stride; |
| } |
| } |
| } |
| dest += stride; |
| } |
| } |
| } |
| |
| extern "C" |
| void |
| __gg__sleep(cblc_field_t *field, size_t offset, size_t size) |
| { |
| int rdigits; |
| __int128 value = get_binary_value_local( &rdigits, |
| field, |
| field->data + offset, |
| size); |
| double delay = (double)value / __gg__power_of_ten(rdigits); |
| if( delay < 0 ) |
| { |
| exception_raise(ec_continue_less_than_zero); |
| delay = 0; |
| } |
| |
| // Convert the time to nanoseconds. |
| delay = delay * 1000000000; |
| |
| // Convert the result to seconds/nanoseconds for nanosleep() |
| size_t tdelay = (size_t)delay; |
| timespec duration; |
| |
| duration.tv_sec = tdelay / 1000000000; |
| duration.tv_nsec = tdelay % 1000000000; |
| |
| nanosleep(&duration, NULL); |
| } |
| |
| extern "C" |
| void |
| __gg__deallocate( cblc_field_t *target, |
| size_t offset, |
| int addr_of) |
| { |
| if( addr_of || (target->attr & based_e)) |
| { |
| // Free the target's data pointer |
| if( target->data ) |
| { |
| free(target->data); |
| target->data = NULL; |
| } |
| } |
| else if (target->type == FldPointer) |
| { |
| // Target is a pointer. Free the data location |
| int rdigits; |
| size_t addrv = get_binary_value_local(&rdigits, |
| target, |
| target->data + offset, |
| sizeof(void *)); |
| void *ptr = reinterpret_cast<void *>(addrv); |
| if( ptr ) |
| { |
| free(ptr); |
| // And set the data location to zero |
| *static_cast<char **>(static_cast<void *>(target->data + offset)) |
| = NULL; |
| } |
| } |
| } |
| |
| static int |
| get_the_byte(cblc_field_t *field) |
| { |
| int retval = -1; |
| if( field ) |
| { |
| cbl_figconst_t figconst = (cbl_figconst_t)(field->attr & FIGCONST_MASK); |
| switch(figconst) |
| { |
| case null_value_e: |
| retval = 0; |
| break; |
| case low_value_e: |
| retval = ascii_to_internal(__gg__low_value_character); |
| break; |
| case zero_value_e: |
| retval = internal_zero; |
| break; |
| case space_value_e: |
| retval = internal_space; |
| break; |
| case quote_value_e: |
| retval = ascii_to_internal(__gg__quote_character); |
| break; |
| case high_value_e: |
| retval = ascii_to_internal(__gg__high_value_character) & 0xFF; |
| break; |
| case normal_value_e: |
| retval = (int)__gg__get_integer_binary_value(field); |
| break; |
| } |
| } |
| return retval; |
| } |
| |
| extern "C" |
| void |
| __gg__allocate( cblc_field_t *first, |
| size_t first_offset, |
| int initialized, |
| int default_byte, |
| cblc_field_t *f_working_byte, |
| cblc_field_t *f_local_byte, |
| const cblc_field_t *returning, |
| size_t returning_offset) |
| { |
| int working_byte = get_the_byte(f_working_byte); |
| int local_byte = get_the_byte(f_local_byte); |
| int fill_char; |
| |
| unsigned char *retval = NULL; |
| if( first->attr & based_e ) |
| { |
| // first is the BASED variable we are allocating memory for |
| if( first->capacity ) |
| { |
| retval = static_cast<unsigned char *>(malloc(first->capacity)); |
| |
| fill_char = 0; |
| if( initialized ) |
| { |
| // This is ISO 2023 ALLOCATE rule 7 (ALL TO VALUE) |
| if( default_byte >= 0 ) |
| { |
| fill_char = default_byte; |
| memset(retval, fill_char, first->capacity); |
| } |
| } |
| else |
| { |
| // This is ISO 2023 ALLOCATE rule 9 (pointers NULL, otherwise OPT_INIT) |
| if( default_byte >= 0 ) |
| { |
| fill_char = default_byte; |
| } |
| if( first->attr & (linkage_e | local_e) ) |
| { |
| if( local_byte >= 0 ) |
| { |
| fill_char = local_byte; |
| } |
| } |
| else |
| { |
| if( working_byte >= 0 ) |
| { |
| fill_char = working_byte; |
| } |
| } |
| memset(retval, fill_char, first->capacity); |
| } |
| } |
| first->data = retval; |
| } |
| else |
| { |
| // This is an ALLOCATE CHARACTERS |
| // first contains the number of bytes to allocate |
| int rdigits; |
| ssize_t tsize = (ssize_t)get_binary_value_local(&rdigits, |
| first, |
| first->data + first_offset, |
| sizeof(void *)); |
| tsize = std::max(0L, tsize); |
| size_t pof10 = __gg__power_of_ten(rdigits); |
| |
| // If there are any non-zero digits to the right of the decimal point, |
| // increment the units place: |
| tsize += (pof10-1); |
| |
| // Adjust the result to be an integer. |
| tsize /= pof10; |
| if( tsize ) |
| { |
| retval = static_cast<unsigned char *>(malloc(tsize)); |
| if(!retval) |
| { |
| abort(); |
| } |
| |
| fill_char = 0; |
| if( initialized ) |
| { |
| // This is ISO 2023 rule 6 (defaultbyte if specified, else zero) |
| if( default_byte >= 0 ) |
| { |
| fill_char = default_byte; |
| } |
| } |
| else |
| { |
| // This is ISO 2023 rule 8 (OPT_INIT if specified, otherwise defaultbyte, otherwise zero)") |
| if( default_byte >= 0 ) |
| { |
| fill_char = default_byte; |
| } |
| if( first->attr & (linkage_e | local_e) ) |
| { |
| if( local_byte >= 0 ) |
| { |
| fill_char = local_byte; |
| } |
| } |
| else |
| { |
| if( working_byte >= 0 ) |
| { |
| fill_char = working_byte; |
| } |
| } |
| } |
| |
| memset(retval, fill_char, tsize); |
| } |
| } |
| |
| if( returning ) |
| { |
| // 'returning' has to be a FldPointer variable; assign the retval to it. |
| *reinterpret_cast<unsigned char **>(returning->data + returning_offset) = retval; |
| } |
| } |
| |
| static std::vector<std::string>module_name_stack; |
| |
| extern "C" |
| void |
| __gg__module_name_push(const char *module_name) |
| { |
| module_name_stack.push_back(module_name); |
| } |
| |
| extern "C" |
| void |
| __gg__module_name_pop() |
| { |
| if( module_name_stack.size() == 0 ) |
| { |
| __gg__abort("__gg__module_name_pop(): module_name_stack is empty"); |
| } |
| module_name_stack.pop_back(); |
| } |
| |
| extern "C" |
| void |
| __gg__module_name(cblc_field_t *dest, module_type_t type) |
| { |
| static size_t result_size = 64; |
| static char *result = static_cast<char *>(malloc(result_size)); |
| massert(result); |
| |
| strcpy(result, ""); |
| |
| size_t ssize = module_name_stack.size(); |
| |
| switch( type ) |
| { |
| case module_activating_e: |
| /* 5) If the ACTIVATING keyword is specified and the function is in a COBOL |
| main program, then the returned value shall be a single space. The |
| implementor shall document how a main program is identified. If the function |
| is not specified in a main program, then the returned value is the name of |
| the runtime element that activated the currently running runtime element. |
| This may be by a CALL statement, an INVOKE statement, a function reference, |
| or an inline invocation. */ |
| |
| /* 6) If the ACTIVATING keyword is specified and the activating statement |
| is within a nested program, then it is implementor defined what value is |
| returned. This may be the name of the nested program or the name of the |
| outermost program containing the nested program. */ |
| |
| switch( module_name_stack.back()[0] ) |
| { |
| case 'T': |
| // We are in a top-level program, not nested: |
| if( module_name_stack.size() == 1 |
| || (module_name_stack.size() == 2 |
| && module_name_stack.front()[0] == 'M' ) ) |
| { |
| // This is a "main program", so we return a single space. |
| strcpy(result, " "); |
| } |
| else |
| { |
| // This is a called program, so we return the name of the "runtime |
| // element" that called us: |
| strcpy(result, module_name_stack[ssize-2].substr(1).c_str()); |
| } |
| break; |
| |
| case 'N': |
| // We are in a nested function. |
| { |
| // This is a called program, so we return the name of the program that |
| // called us |
| strcpy(result, module_name_stack[ssize-2].substr(1).c_str()); |
| } |
| } |
| break; |
| |
| case module_current_e: |
| // 7) If the CURRENT keyword is specified then the returned value is the |
| // name of the runtime element of the outermost program of the compilation |
| // unit’s code that is currently running. |
| |
| // Look upward for our parent T. |
| // Termination is weird because size_t is unsigned |
| for(size_t i=ssize-1; i<ssize; i--) |
| { |
| if( module_name_stack[i][0] == 'T' ) |
| { |
| strcpy(result, module_name_stack[i].substr(1).c_str()); |
| break; |
| } |
| } |
| break; |
| |
| case module_nested_e: |
| // 8) If the NESTED keyword is specified, then the returned value is the |
| // name, as specified in the PROGRAM-ID, of the currently running, most |
| // recently nested program. |
| |
| // This specification seems weird to me. What if the currently running |
| // program isn't nested? |
| |
| // So, we'll just return us |
| strcpy(result, module_name_stack[ssize-1].substr(1).c_str()); |
| break; |
| |
| case module_stack_e: |
| for(size_t i=ssize-1; i<ssize; i--) |
| { |
| if( module_name_stack[i][0] == 'T' |
| || module_name_stack[i][0] == 'N' ) |
| { |
| if( strlen(result) + module_name_stack[i].substr(1).length() + 4 > result_size) |
| { |
| result_size *= 2; |
| result = static_cast<char *>(realloc(result, result_size)); |
| } |
| strcat(result, module_name_stack[i].substr(1).c_str()); |
| strcat(result, ";"); |
| } |
| } |
| strcat(result, " "); |
| |
| break; |
| |
| case module_toplevel_e: |
| { |
| size_t i = 0; |
| if( module_name_stack[i] == "Mmain" ) |
| { |
| i += 1; |
| } |
| strcpy(result, module_name_stack[i].substr(1).c_str()); |
| } |
| break; |
| } |
| |
| __gg__adjust_dest_size(dest, strlen(result)); |
| memcpy(dest->data, result, strlen(result)+1); |
| } |
| |
| /* |
| * Runtime functions defined for cbl_enabled_exceptions_t |
| */ |
| cbl_enabled_exceptions_t& |
| cbl_enabled_exceptions_t::decode( const std::vector<uint64_t>& encoded ) { |
| auto p = encoded.begin(); |
| while( p != encoded.end() ) { |
| auto location = static_cast<bool>(*p++); |
| auto ec = static_cast<ec_type_t>(*p++); |
| auto file = *p++; |
| cbl_enabled_exception_t enabled(location, ec, file); |
| insert(enabled); |
| } |
| return *this; |
| } |
| const cbl_enabled_exception_t * |
| cbl_enabled_exceptions_t::match( ec_type_t type, size_t file ) const { |
| auto output = enabled_exception_match( begin(), end(), type, file ); |
| |
| if( output != end() ) { |
| if( MATCH_DECLARATIVE ) |
| warnx(" enabled_exception_match found %x in input\n", type); |
| return &*output; |
| } |
| return nullptr; |
| } |
| |
| void |
| cbl_enabled_exceptions_t::dump( const char tag[] ) const { |
| if( empty() ) { |
| warnx("%s: no enabled exceptions", tag ); |
| return; |
| } |
| int i = 1; |
| for( auto& elem : *this ) { |
| warnx("%s: %2d {%s, %04x %s, %u}", tag, |
| i++, |
| elem.location? "with location" : " no location", |
| elem.ec, |
| local_ec_type_str(elem.ec), |
| static_cast<unsigned int>(elem.file) ); |
| } |
| } |
| |
| |
| static std::vector<cbl_declarative_t>& |
| decode( std::vector<cbl_declarative_t>& dcls, |
| const std::vector<uint64_t>& encoded ) { |
| auto p = encoded.begin(); |
| while( p != encoded.end() ) { |
| auto section = static_cast<size_t>(*p++); |
| auto global = static_cast<bool>(*p++); |
| auto type = static_cast<ec_type_t>(*p++); |
| auto nfile = static_cast<uint32_t>(*p++); |
| std::list<size_t> files; |
| assert(nfile <= cbl_declarative_t::files_max); |
| auto pend = p + nfile; |
| std::copy(p, pend, std::back_inserter(files)); |
| p += cbl_declarative_t::files_max; |
| auto mode = cbl_file_mode_t(*p++); |
| cbl_declarative_t dcl( section, type, files, mode, global ); |
| dcls.push_back(dcl); |
| } |
| return dcls; |
| } |
| |
| static std::vector<cbl_declarative_t>& |
| operator<<( std::vector<cbl_declarative_t>& dcls, |
| const std::vector<uint64_t>& encoded ) { |
| return decode( dcls, encoded ); |
| } |
| |
| // The first element of each array is the number of elements that follow |
| extern "C" |
| void |
| __gg__set_exception_environment( uint64_t *ecs, uint64_t *dcls ) |
| { |
| static struct prior_t { |
| uint64_t *ecs = nullptr, *dcls = nullptr; |
| } prior; |
| |
| if( MATCH_DECLARATIVE ) |
| if( prior.ecs != ecs || prior.dcls != dcls ) |
| warnx("set_exception_environment: %s: %p, %p", |
| __gg__exception_statement, ecs, dcls); |
| |
| if( ecs ) { |
| if( prior.ecs != ecs ) { |
| uint64_t *ecs_begin = ecs + 1, *ecs_end = ecs_begin + ecs[0]; |
| if( MATCH_DECLARATIVE ) { |
| warnx("%u elements implies %u ECs", |
| static_cast<unsigned int>(ecs[0]), |
| static_cast<unsigned int>(ecs[0] / 3)); |
| } |
| cbl_enabled_exceptions_t enabled; |
| enabled_ECs = enabled.decode( std::vector<uint64_t>(ecs_begin, ecs_end) ); |
| if( MATCH_DECLARATIVE ) enabled_ECs.dump("set_exception_environment"); |
| } |
| } else { |
| enabled_ECs.clear(); |
| } |
| |
| if( dcls ) { |
| if( prior.dcls != dcls ) { |
| uint64_t *dcls_begin = dcls + 1, *dcls_end = dcls_begin + dcls[0]; |
| if( MATCH_DECLARATIVE ) { |
| warnx("%u elements implies %u declaratives", |
| static_cast<unsigned int>(dcls[0]), |
| static_cast<unsigned int>(dcls[0] / 21)); |
| } |
| declaratives.clear(); |
| declaratives << std::vector<uint64_t>( dcls_begin, dcls_end ); |
| } |
| } else { |
| declaratives.clear(); |
| } |
| |
| __gg__exception_code = ec_none_e; |
| |
| prior.ecs = ecs; |
| prior.dcls = dcls; |
| } |
| |
| static char *sv_envname = NULL; |
| |
| extern "C" |
| void |
| __gg__set_env_name( const cblc_field_t *var, |
| size_t offset, |
| size_t length ) |
| { |
| free(sv_envname); |
| sv_envname = static_cast<char *>(malloc(length+1)); |
| massert(sv_envname); |
| memcpy(sv_envname, var->data+offset, length); |
| sv_envname[length] = '\0'; |
| } |
| |
| extern "C" |
| void |
| __gg__set_env_value(const cblc_field_t *value, |
| size_t offset, |
| size_t length ) |
| { |
| size_t name_length = strlen(sv_envname); |
| size_t value_length = length; |
| |
| static size_t env_length = 16; |
| static char *env = static_cast<char *>(malloc(env_length+1)); |
| static size_t val_length = 16; |
| static char *val = static_cast<char *>(malloc(val_length+1)); |
| if( env_length < name_length+1 ) |
| { |
| env_length = name_length+1; |
| env = static_cast<char *>(realloc(env, env_length)); |
| } |
| if( val_length < value_length+1 ) |
| { |
| val_length = value_length+1; |
| val = static_cast<char *>(realloc(val, val_length)); |
| } |
| massert(env); |
| massert(val); |
| |
| // The name and the value arrive in the internal codeset: |
| memcpy(env, sv_envname, name_length); |
| env[name_length] = '\0'; |
| memcpy(val, value->data+offset, value_length); |
| val[value_length] = '\0'; |
| |
| // Get rid of leading and trailing internal_space characters |
| char *trimmed_env = brute_force_trim(env); |
| char *trimmed_val = brute_force_trim(val); |
| |
| // Conver them to the console codeset |
| __gg__internal_to_console_in_place(trimmed_env, strlen(trimmed_env)); |
| __gg__internal_to_console_in_place(trimmed_val, strlen(trimmed_val)); |
| |
| // And now, anticlimactically, set the variable: |
| setenv(trimmed_env, trimmed_val, 1); |
| } |
| |
| extern "C" |
| void |
| __gg__fprintf_stderr(const char *format_string, ...) |
| { |
| /* This routine allows the compiler to send stuff to stderr in a way |
| that is straightforward to use.. */ |
| va_list ap; |
| va_start(ap, format_string); |
| vfprintf(stderr, format_string, ap); |
| va_end(ap); |
| } |
| |