| /* | |
| * Copyright (c) 2021-2026 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 <cassert> | |
| #include <cctype> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <unistd.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <map> | |
| #include <string> | |
| #include <vector> | |
| #include "cobol-system.h" | |
| #include <coretypes.h> | |
| #include <tree.h> | |
| #include <fold-const.h> | |
| #undef yy_flex_debug | |
| #include <langinfo.h> | |
| #include <version.h> | |
| #include <demangle.h> | |
| #include <intl.h> | |
| #include <backtrace.h> | |
| #include <diagnostic.h> | |
| #include <opts.h> | |
| #include "util.h" | |
| #include "cbldiag.h" | |
| #include "cdfval.h" | |
| #include "lexio.h" | |
| #include "../../libgcobol/ec.h" | |
| #include "../../libgcobol/common-defs.h" | |
| #include "symbols.h" | |
| namespace picture_validation { | |
| /* | |
| * The picture is parsed via a state machine, implementing an automaton. If | |
| * it reaches the end of the input, by definition it accepts the string. The | |
| * acceptance state is a recognized type of COBOL data item. | |
| */ | |
| enum state_t { | |
| invalid_e, | |
| none_e, | |
| boolean_e, | |
| alphabetic_e, | |
| unicode_e, | |
| alphanumeric_e, | |
| alpha_ed_e, | |
| national_e, | |
| national_ed_e, | |
| numeric_e, | |
| numeric_ed_e, | |
| c_need_r_e, | |
| d_need_b_e, | |
| postdec_e, | |
| antedec_e, | |
| exponent_e, | |
| accept_e, // keep last | |
| }; | |
| const char *state_str( state_t state ) { | |
| switch(state) { | |
| case accept_e: return "accept_e"; | |
| case alpha_ed_e: return "alpha_ed_e"; | |
| case alphabetic_e: return "alphabetic_e"; | |
| case alphanumeric_e: return "alphanumeric_e"; | |
| case antedec_e: return "antedec_e"; | |
| case boolean_e: return "boolean_e"; | |
| case c_need_r_e: return "c_need_r_e"; | |
| case d_need_b_e: return "d_need_b_e"; | |
| case exponent_e: return "exponent_e"; | |
| case invalid_e: return "invalid_e"; | |
| case national_e: return "national_e"; | |
| case national_ed_e: return "national_ed_e"; | |
| case none_e: return "none_e"; | |
| case numeric_e: return "numeric_e"; | |
| case numeric_ed_e: return "numeric_ed_e"; | |
| case postdec_e: return "postdec_e"; | |
| case unicode_e: return "unicode_e"; | |
| } | |
| return "???"; | |
| } | |
| //////////////////////////////////////////////////////////////// | |
| struct transition_t { | |
| state_t state; | |
| char ch; | |
| state_t next_state; | |
| transition_t( state_t state, | |
| char ch, | |
| state_t next_state ) | |
| : state(state) | |
| , ch(ch) | |
| , next_state(next_state) | |
| {} | |
| bool operator<(const transition_t& that) const { | |
| if (state == that.state) return ch < that.ch; | |
| return state < that.state; | |
| } | |
| bool operator==(const transition_t& that) const { return match(that); } | |
| bool operator!=(const transition_t& that) const { return ! match(that); } | |
| protected: | |
| // match verifies that the row returned by std::lower_bound actually | |
| // matches, and isn't the next "bigger" one. | |
| bool match(const transition_t& that) const { | |
| return state == that.state | |
| && ch == that.ch; | |
| } | |
| }; | |
| #define dot '.' | |
| #define com ',' | |
| static std::vector<transition_t> picture_rules { | |
| // Alpha and Alphanumeric | |
| { none_e, 'A', alphabetic_e }, | |
| { none_e, 'X', alphanumeric_e }, | |
| { none_e, 'B', alpha_ed_e }, | |
| { none_e, '0', alpha_ed_e }, | |
| { none_e, '/', alpha_ed_e }, | |
| { none_e, 'N', national_e }, | |
| { none_e, '9', antedec_e }, // 9 before V | |
| { none_e, 'P', antedec_e }, | |
| { none_e, 'S', antedec_e }, | |
| { none_e, 'V', postdec_e, }, // V before 9 | |
| { none_e, '1', boolean_e }, | |
| { boolean_e, '1', boolean_e }, | |
| { alphabetic_e, 'A', alphabetic_e }, | |
| { alphabetic_e, 'B', alpha_ed_e }, | |
| { alphabetic_e, '0', alpha_ed_e }, | |
| { alphabetic_e, '/', alpha_ed_e }, | |
| { alphabetic_e, 'X', alphanumeric_e }, | |
| { alphabetic_e, '9', alphanumeric_e }, | |
| { alphabetic_e, '$', numeric_ed_e }, | |
| { alphanumeric_e, 'A', alphanumeric_e }, | |
| { alphanumeric_e, 'X', alphanumeric_e }, | |
| { alphanumeric_e, '9', alphanumeric_e }, | |
| // Alpha edited | |
| { alpha_ed_e, 'B', alpha_ed_e }, | |
| { alpha_ed_e, '0', alpha_ed_e }, | |
| { alpha_ed_e, '/', alpha_ed_e }, | |
| { alpha_ed_e, 'A', alpha_ed_e }, | |
| { alpha_ed_e, 'X', alpha_ed_e }, | |
| { alpha_ed_e, '9', numeric_ed_e }, | |
| { alpha_ed_e, 'Z', numeric_ed_e }, | |
| { alpha_ed_e, '*', numeric_ed_e }, | |
| { alpha_ed_e, '+', numeric_ed_e }, | |
| { alpha_ed_e, '-', numeric_ed_e }, | |
| { alpha_ed_e, dot, numeric_ed_e }, | |
| { alpha_ed_e, com, numeric_ed_e }, | |
| { alpha_ed_e, '$', numeric_ed_e }, | |
| { alphanumeric_e, 'B', alpha_ed_e }, | |
| { alphanumeric_e, '0', alpha_ed_e }, | |
| { alphanumeric_e, '/', alpha_ed_e }, | |
| // National | |
| { national_e, 'N', national_e }, | |
| { national_e, '\0', alphanumeric_e }, | |
| // National edited | |
| { national_e, 'B', national_ed_e }, | |
| { national_e, '0', national_ed_e }, | |
| { national_e, '/', national_ed_e }, | |
| { national_ed_e, 'N', national_ed_e }, | |
| { national_ed_e, 'B', national_ed_e }, | |
| { national_ed_e, '0', national_ed_e }, | |
| { national_ed_e, '/', national_ed_e }, | |
| { national_ed_e, '\0', alpha_ed_e }, | |
| // Numeric edited | |
| // [B*] or [+-].*[+-] or (9 and [$B0/,.+-], where $ is cs. | |
| { none_e, 'Z', numeric_ed_e }, | |
| { none_e, '*', numeric_ed_e }, | |
| { none_e, dot, numeric_ed_e }, | |
| { none_e, com, numeric_ed_e }, | |
| { none_e, '-', numeric_ed_e }, | |
| { none_e, '+', numeric_ed_e }, | |
| { none_e, '$', numeric_ed_e }, | |
| { antedec_e, '9', antedec_e }, | |
| { antedec_e, 'A', alphanumeric_e }, | |
| { antedec_e, 'P', numeric_e }, | |
| { antedec_e, 'V', numeric_e }, | |
| { antedec_e, 'X', alphanumeric_e }, | |
| { antedec_e, '+', numeric_ed_e }, | |
| { antedec_e, '-', numeric_ed_e }, | |
| { antedec_e, 'C', c_need_r_e }, | |
| { antedec_e, 'D', d_need_b_e }, | |
| { antedec_e, 'Z', numeric_ed_e }, | |
| { antedec_e, '*', numeric_ed_e }, | |
| { antedec_e, 'B', numeric_ed_e }, | |
| { antedec_e, '0', numeric_ed_e }, | |
| { antedec_e, '/', numeric_ed_e }, | |
| { antedec_e, '$', numeric_ed_e }, | |
| { antedec_e, dot, numeric_ed_e }, | |
| { antedec_e, com, numeric_ed_e }, | |
| { postdec_e, '9', numeric_e }, | |
| { postdec_e, 'P', numeric_e }, | |
| { numeric_e, '9', numeric_e }, | |
| { numeric_e, 'P', numeric_e }, | |
| { numeric_e, 'V', numeric_e }, | |
| { numeric_e, 'Z', numeric_ed_e }, | |
| { numeric_e, '*', numeric_ed_e }, | |
| { numeric_e, 'B', numeric_ed_e }, | |
| { numeric_e, '0', numeric_ed_e }, | |
| { numeric_e, '/', numeric_ed_e }, | |
| { numeric_e, dot, numeric_ed_e }, | |
| { numeric_e, com, numeric_ed_e }, | |
| { numeric_e, '+', numeric_ed_e }, | |
| { numeric_e, '-', numeric_ed_e }, | |
| { numeric_e, '$', numeric_ed_e }, | |
| { numeric_e, 'C', c_need_r_e }, | |
| { numeric_e, 'D', d_need_b_e }, | |
| { numeric_ed_e, 'Z', numeric_ed_e }, | |
| { numeric_ed_e, 'B', numeric_ed_e }, | |
| { numeric_ed_e, '9', numeric_ed_e }, | |
| { numeric_ed_e, '0', numeric_ed_e }, | |
| { numeric_ed_e, dot, numeric_ed_e }, | |
| { numeric_ed_e, com, numeric_ed_e }, | |
| { numeric_ed_e, '/', numeric_ed_e }, | |
| { numeric_ed_e, '-', numeric_ed_e }, | |
| { numeric_ed_e, '+', numeric_ed_e }, | |
| { numeric_ed_e, '*', numeric_ed_e }, | |
| { numeric_ed_e, '$', numeric_ed_e }, | |
| { numeric_ed_e, 'P', numeric_ed_e }, | |
| { numeric_ed_e, 'V', numeric_ed_e }, // See Table 10 if in doubt. | |
| { numeric_ed_e, 'C', c_need_r_e }, | |
| { numeric_ed_e, 'D', d_need_b_e }, | |
| // End of the road | |
| { c_need_r_e, 'R', numeric_ed_e }, | |
| { d_need_b_e, 'B', numeric_ed_e }, | |
| }; | |
| typedef std::array<state_t, 256> automaton_elem_t; | |
| typedef std::vector<automaton_elem_t> automaton_matrix_t; | |
| const automaton_matrix_t& prepare_automaton() { | |
| static automaton_matrix_t matrix(accept_e); | |
| static bool ready = false; | |
| if( ready ) return matrix; | |
| int i __attribute__ ((unused)) = 0, nextra = 0; | |
| for( const auto& rule : picture_rules ) { | |
| unsigned char ch(rule.ch); | |
| #ifdef COBOL_PICTURE | |
| if( matrix[rule.state][ch] ) { | |
| nextra++; | |
| fprintf(stderr, "%s: redundant rule: #%d { %s '%c' %s }\n", | |
| __func__, i, | |
| state_str(rule.state), rule.ch, | |
| state_str(rule.next_state)); | |
| } | |
| #endif | |
| matrix[rule.state][ch] = rule.next_state; | |
| i++; | |
| } | |
| if( nextra ) { // cppcheck-suppress knownConditionTrueFalse | |
| cbl_internal_error("%s: %d redundant rules", __func__, nextra); | |
| } | |
| ready = true; | |
| return matrix; | |
| } | |
| /* | |
| * As the picture string is validated, constraint_t captures what has been | |
| * seen: the preceding character and what characters have appeared that | |
| * signify alphanumeric-edited or numeric-edited. | |
| */ | |
| static constexpr uint64_t hiword(char ch) { | |
| return (1ULL << (ch - 'A')) << 32; | |
| } | |
| class constraint_t { | |
| // Domain of symbols in a picture string. | |
| enum picsym_t : uint64_t { | |
| p_plus = 0x0010, // + | |
| p_minus = 0x0020, // - | |
| p_dot = 0x0040, // . | |
| p_comma = 0x0080, // , | |
| p_slash = 0x0100, // / | |
| p_cs = 0x0200, // $ | |
| p_star = 0x0400, // * | |
| p_zero = 0x0800, // 0 | |
| p_one = 0x1000, // 1 | |
| p_nine = 0x0009, // 9, claim the whole nybble | |
| // Bits 0-15: punctuation, currency, and 0, 1, and 9. | |
| // Bits 32-58: A-Z | |
| p_A = hiword('A'), | |
| p_B = hiword('B'), | |
| p_C = hiword('C'), | |
| p_D = hiword('D'), | |
| p_E = hiword('E'), | |
| p_N = hiword('N'), | |
| p_P = hiword('P'), | |
| p_S = hiword('S'), | |
| p_U = hiword('U'), | |
| p_V = hiword('V'), | |
| p_X = hiword('X'), | |
| p_Z = hiword('Z'), | |
| p_alphanumeric_e = (p_A | p_X | p_nine), | |
| p_b_0_slash_e = (p_B | p_zero | p_slash), | |
| p_alpha_ed_e = (p_alphanumeric_e | p_b_0_slash_e), | |
| p_national_ed_e = (p_N | p_b_0_slash_e), | |
| p_numeric_e = (p_nine | p_P | p_S | p_V), | |
| // B P V Z 9 0 / , . + - CR DB * cs (CR/DB not in mask because at end) | |
| p_numeric_ed2_e = (p_b_0_slash_e | p_comma | p_dot | p_plus | p_minus), | |
| p_numeric_ed_e = ( p_P | p_V | p_Z | p_nine | p_b_0_slash_e | | |
| p_comma | p_dot | p_plus | p_minus | p_star | p_cs), | |
| }; | |
| uint64_t symbol_mask; | |
| bool both( picsym_t a, picsym_t b ) const { | |
| return (symbol_mask & (a | b)) == (a | b); | |
| } | |
| template <typename T> | |
| bool only(T bits) const { | |
| return symbol_mask == (symbol_mask & bits); | |
| } | |
| /* | |
| * 13.18.40.3 Syntax rules FORMAT 1 | |
| */ | |
| bool is_allowed( char ch ) const { | |
| switch( ch ) { | |
| case 'P': // no P in numeric-edited | |
| case 'V': // no V in numeric-edited | |
| case dot: // Neither P nor V with dot. | |
| if( both(p_P, p_dot) ) { // rule 17 | |
| error_msg(loc, "%qc and %qc are mutually exclusive", 'P', dot); | |
| return false; | |
| } | |
| if( both(p_V, p_dot) ) { // rule 20 | |
| error_msg(loc, "%qc and %qc are mutually exclusive", 'V', dot); | |
| return false; | |
| } | |
| break; | |
| case 'Z': case '*': // Only blanks or stars | |
| if( both(p_Z, p_star) ) { // rule 21 | |
| error_msg(loc, "%qc and %qc are mutually exclusive", 'Z', '*'); | |
| return false; | |
| } | |
| break; | |
| } | |
| return true; | |
| } | |
| bool is_boolean() const { return symbol_mask == p_one; } // cppcheck-suppress unusedPrivateFunction | |
| bool is_alphabetic() const { return symbol_mask == p_A; } | |
| bool is_national() const { return symbol_mask == p_N; } | |
| bool is_alphanumeric() const { | |
| auto bits = symbol_mask & p_alphanumeric_e; | |
| return 0 < bits && symbol_mask == bits; | |
| } | |
| bool is_alpha_ed() const { | |
| return only(p_alpha_ed_e) | |
| && 0 != (symbol_mask & p_alphanumeric_e) | |
| && 0 != (symbol_mask & p_b_0_slash_e); | |
| } | |
| bool is_national_ed() const { | |
| return only(p_national_ed_e) | |
| && 0 != (symbol_mask & p_N) | |
| && 0 != (symbol_mask & p_b_0_slash_e); | |
| } | |
| bool is_numeric() const { | |
| return only(p_numeric_e) | |
| && 0 != (symbol_mask & p_nine) | |
| && 0 != (symbol_mask & p_numeric_e); | |
| } | |
| bool is_numeric9_ed() const { | |
| return only(p_numeric_ed_e) | |
| && 0 != (symbol_mask & p_nine) | |
| && 0 != (symbol_mask & p_numeric_ed2_e); | |
| } | |
| bool is_numeric_ed() const { | |
| return only(p_numeric_ed_e) | |
| && ( 0 != (symbol_mask & (p_Z | p_star)) | |
| || two_signs() | |
| || is_numeric9_ed() ); | |
| } | |
| bool two_signs() const { return 1 < nplus || 1 < nminus; } | |
| state_t pic_state; // to lookup sequence validity | |
| uint64_t picsym_set( char ch ) { | |
| switch(ch) { | |
| case '$': return symbol_mask |= p_cs; | |
| case '*': return symbol_mask |= p_star; | |
| case '+': return symbol_mask |= p_plus; | |
| case ',': return symbol_mask |= p_comma; | |
| case '-': return symbol_mask |= p_minus; | |
| case '.': return symbol_mask |= p_dot; | |
| case '/': return symbol_mask |= p_slash; | |
| case '0': return symbol_mask |= p_zero; | |
| case '1': return symbol_mask |= p_one; | |
| case '9': return symbol_mask |= p_nine; | |
| case 'A': return symbol_mask |= p_A; | |
| case 'B': return symbol_mask |= p_B; | |
| case 'C': return symbol_mask |= p_C; | |
| case 'D': return symbol_mask |= p_D; | |
| case 'E': return symbol_mask |= p_E; | |
| case 'N': return symbol_mask |= p_N; | |
| case 'P': return symbol_mask |= p_P; | |
| case 'S': return symbol_mask |= p_S; | |
| case 'U': return symbol_mask |= p_U; | |
| case 'V': return symbol_mask |= p_V; | |
| case 'X': return symbol_mask |= p_X; | |
| case 'Z': return symbol_mask |= p_Z; | |
| } | |
| return symbol_mask; | |
| } | |
| bool picsym_seen( char ch ) const { | |
| switch(ch) { | |
| case '$': return 0 < (symbol_mask & p_cs); | |
| case '*': return 0 < (symbol_mask & p_star); | |
| case '+': return 0 < (symbol_mask & p_plus); | |
| case ',': return 0 < (symbol_mask & p_comma); | |
| case '-': return 0 < (symbol_mask & p_minus); | |
| case '.': return 0 < (symbol_mask & p_dot); | |
| case '/': return 0 < (symbol_mask & p_slash); | |
| case '0': return 0 < (symbol_mask & p_zero); | |
| case '1': return 0 < (symbol_mask & p_one); | |
| case '9': return 0 < (symbol_mask & p_nine); | |
| case 'A': return 0 < (symbol_mask & p_A); | |
| case 'B': return 0 < (symbol_mask & p_B); | |
| case 'C': return 0 < (symbol_mask & p_C); | |
| case 'D': return 0 < (symbol_mask & p_D); | |
| case 'E': return 0 < (symbol_mask & p_E); | |
| case 'N': return 0 < (symbol_mask & p_N); | |
| case 'P': return 0 < (symbol_mask & p_P); | |
| case 'S': return 0 < (symbol_mask & p_S); | |
| case 'U': return 0 < (symbol_mask & p_U); | |
| case 'V': return 0 < (symbol_mask & p_V); | |
| case 'X': return 0 < (symbol_mask & p_X); | |
| case 'Z': return 0 < (symbol_mask & p_Z); | |
| } | |
| return false; | |
| } | |
| static picsym_t picsym_of( char ch ) { | |
| switch(ch) { | |
| case '$': return p_cs; | |
| case '*': return p_star; | |
| case '+': return p_plus; | |
| case ',': return p_comma; | |
| case '-': return p_minus; | |
| case '.': return p_dot; | |
| case '/': return p_slash; | |
| case '0': return p_zero; | |
| case '1': return p_one; | |
| case '9': return p_nine; | |
| case 'A': return p_A; | |
| case 'B': return p_B; | |
| case 'C': return p_C; | |
| case 'D': return p_D; | |
| case 'E': return p_E; | |
| case 'N': return p_N; | |
| case 'P': return p_P; | |
| case 'S': return p_S; | |
| case 'U': return p_U; | |
| case 'V': return p_V; | |
| case 'X': return p_X; | |
| case 'Z': return p_Z; | |
| } | |
| gcc_unreachable(); | |
| return picsym_t(0); | |
| } | |
| bool picsym_settable( char ch ) const { | |
| switch(ch) { | |
| case 'V': | |
| return ! picsym_seen(ch); | |
| case 'P': | |
| return ch == prior_ch || ! picsym_seen(ch); | |
| } | |
| if( picsym_seen('S') ) { | |
| if( picsym_of(ch) != (picsym_of(ch) & p_numeric_e) ) { | |
| dbgmsg("%s:%d: nonnumeric: '%c', %lx not in %lx", __func__, __LINE__, | |
| ch, picsym_of(ch), p_numeric_e); | |
| } | |
| return picsym_of(ch) == (picsym_of(ch) & p_numeric_e); | |
| } | |
| return true; | |
| } | |
| cbl_loc_t loc; | |
| char prior_ch; | |
| const char decimal_point; | |
| int np, nplus, nminus, ndollar; | |
| uint64_t field_attr; | |
| cbl_field_data_t field_data; | |
| struct contiguous_t { | |
| char ch; | |
| int n; | |
| explicit contiguous_t( char ch ) : ch(ch), n(1) {} | |
| contiguous_t& operator++() { n++; return *this; } | |
| }; | |
| std::vector<contiguous_t> contiguous; | |
| // 13.18.40.3 Syntax rules #27 No more than 1 of certain character | |
| // sequences. This function misinterprets the rule. The rule is not that | |
| // e.g. $$,$$ is invalid. the rule is that the list items are mutually | |
| // exclusive, $$Z is invalid. Keeping the structure for now in case needed. | |
| #if 0 | |
| bool contiguous_ok(char ch) const { | |
| int n = 0; | |
| switch(ch) { | |
| case '+': case '-': case '$': | |
| n = 2; | |
| break; | |
| case 'Z': case '*': | |
| n = 1; | |
| break; | |
| } | |
| if( n ) { | |
| n = std::count_if( contiguous.begin(), contiguous.end(), | |
| [ch, n]( const auto& elem ) { | |
| return ch == elem.ch && n <= elem.n; | |
| } ); | |
| } | |
| return n < 2; | |
| } | |
| #endif | |
| bool add_contiguous(char ch) { | |
| if( contiguous.empty() ) { | |
| contiguous.push_back( contiguous_t(ch) ); | |
| return true; | |
| } | |
| auto& last( contiguous.back() ); | |
| if( ch == last.ch ) { | |
| ++last; | |
| } else { | |
| contiguous.push_back( contiguous_t(ch) ); | |
| } | |
| return true; // contiguous_ok(ch); | |
| } | |
| public: | |
| const char *bad_repeat; | |
| constraint_t(const cbl_loc_t& loc, const cbl_name_t picture) | |
| : symbol_mask(0) | |
| , pic_state(none_e) | |
| , loc(loc) | |
| , prior_ch('\0') | |
| , decimal_point(symbol_decimal_point()) | |
| , np(0), nplus(0), nminus(0), ndollar(0) | |
| , field_attr(0) | |
| , bad_repeat(nullptr) | |
| { | |
| // Trim trailing punctuation from picture for later revalidation, because | |
| // we do. | |
| auto pic = xstrdup(picture); | |
| auto pend = pic + strlen(pic); | |
| if( pic < --pend ) { | |
| switch( *pend ) { | |
| case ';': case ',': case '.': | |
| *pend = '\0'; | |
| break; | |
| } | |
| } | |
| field_data.picture = pic; | |
| } | |
| bool append( char ch, size_t len ) { | |
| if( prior_ch == '\0' ) { | |
| switch(ch) { | |
| case 'S': case '+': case '-': | |
| field_attr |= signable_e; | |
| } | |
| } | |
| if( prior_ch == '.' && ch == '.' ) return false; | |
| if( ! add_contiguous(ch) ) return false; // cppcheck-suppress knownConditionTrueFalse | |
| if( ! picsym_settable(ch) ) { return false; } | |
| picsym_set(ch); | |
| // emits message about invalid combinations | |
| if( ! is_allowed(ch) ) { return false; } | |
| prior_ch = ch; | |
| add_capacity(len? len : 1); | |
| observe_the_dot(ch); | |
| loc++; | |
| return true; | |
| } | |
| inline void move_caret( int n ) { loc += n; } | |
| bool has_s_star() const { return both(p_S, p_star); } | |
| void extra_dot( size_t pos ) { | |
| if( field_data.picture[pos] != '\0') { | |
| assert(field_data.picture[pos+1] == '\0'); | |
| assert(field_data.picture[pos] == '.'); | |
| const_cast<char*>(field_data.picture)[pos] = '\0'; | |
| } | |
| } | |
| bool repeatable() const { | |
| switch(prior_ch) { | |
| case '1': | |
| case 'P': | |
| case 'A': case 'X': case '9': | |
| case 'N': | |
| case 'U': | |
| case 'Z': case '*': | |
| case 'B': case '0': // B and 0 but not slash | |
| case '$': case '+': case '-': | |
| return true; | |
| } | |
| return false; | |
| } | |
| char prior() const { return prior_ch; } | |
| cbl_field_data_t data() const { return field_data; } | |
| uint64_t attr() const { return field_attr; } | |
| const cbl_loc_t& location() const { return loc; } | |
| state_t picture_state(state_t state) { return pic_state = state; } | |
| state_t picture_state() const { return pic_state; } | |
| state_t state() const { | |
| if( is_alphabetic() ) return alphabetic_e; | |
| if( is_alphanumeric() ) return alphanumeric_e; | |
| if( is_alpha_ed() ) return alpha_ed_e; | |
| if( is_national() ) return national_e; | |
| if( is_national_ed() ) return national_ed_e; | |
| if( is_numeric() ) return numeric_e; | |
| if( is_numeric_ed() ) return numeric_ed_e; | |
| return invalid_e; | |
| } | |
| void maybe_signable( char ch ) { | |
| switch(TOUPPER(ch)) { | |
| case '+': case '-': | |
| field_attr |= signable_e; | |
| } | |
| dbgmsg("%s: '%c', %ssignable", __func__, | |
| ch, (field_attr & signable_e)? "" : "not "); | |
| } | |
| void maybe_signable( state_t state ) { | |
| switch(state) { | |
| case c_need_r_e: | |
| case d_need_b_e: | |
| maybe_signable('+'); | |
| default: | |
| break; | |
| } | |
| } | |
| bool day_follows_night( char ch ) const { | |
| const static std::string domain("*+,-./019ABENPSVXZCRDB"); | |
| return std::string::npos != domain.find(ch); | |
| } | |
| inline bool first_dollar( char ch ) { return ch == '$' && ndollar == 0; } | |
| /* | |
| * 1) Every character except V adds to the capacity, with the caveat that | |
| * the first CURRENCY PICTURE character adds the length of the CURRENCY | |
| * SIGN string. | |
| * 2) data.digits has to be the count of 9 + count of Z + count of asterisk | |
| * + (count of $ minus 1) + (count of - minus one) + (count of + minus 1) | |
| * 3) data.rdigits is the subset of 2 that is to the right of either V or | |
| * . (edited) | |
| */ | |
| void add_capacity(int n) { | |
| switch( prior_ch ) { | |
| case 'V': | |
| n = 0; // only in debug message | |
| break; | |
| case 'P': | |
| np += n; | |
| field_attr |= scaled_e; | |
| // P in front has positive rdigit less one; P aft is negative rdigit. | |
| { | |
| int ndigit = only(p_P | p_S)? (1 < np? n : n - 1) : -n; | |
| field_data.rdigits += ndigit; | |
| } | |
| n = 0; // only in debug message | |
| break; | |
| default: | |
| field_data.add_capacity(n); | |
| if( digital() ) { | |
| field_data.digits += n; | |
| if( pic_state == postdec_e ) { | |
| field_data.rdigits += n; | |
| } | |
| } | |
| } | |
| dbgmsg("%s: '%c', added %d: {%u, %u,%d}", __func__, prior_ch, n, | |
| field_data.capacity(), field_data.digits, field_data.rdigits); | |
| } | |
| protected: | |
| void observe_the_dot( char ch ) { | |
| if( pic_state == none_e ) pic_state = antedec_e; | |
| if( ch == decimal_point || ch == 'V' ) pic_state = postdec_e; | |
| } | |
| bool digital() { | |
| switch(prior_ch) { | |
| case '9': case 'Z': case '*': | |
| return true; | |
| case 'V': | |
| return false; | |
| case '+': | |
| return 0 < nplus++; | |
| case '-': | |
| return 0 < nminus++; | |
| case '$': | |
| return 0 < ndollar++; | |
| } | |
| return false; | |
| } | |
| }; | |
| } // end namespace | |
| using picture_validation::transition_t; | |
| using picture_validation::state_t; | |
| /* | |
| * If a character within parentheses cannot be part of a COBOL word, then the | |
| * sequence is not an integer or data-item name. | |
| */ | |
| static const char * | |
| seek_paren( const char *p, const char *epicture ) { | |
| for( p++; p < epicture; p++ ) { | |
| if( ! ISALNUM(*p) ) { | |
| switch(*p) { | |
| case '-': | |
| case '_': | |
| continue; // part of COBOL text word, or a digit | |
| } | |
| break; | |
| } | |
| } | |
| return p; | |
| } | |
| static bool verbose = true; | |
| // Dot as last character is period separator because the lexer defines | |
| // end-of-picture as a space. | |
| bool | |
| end_of_picture( const char *p, const char *epicture ) { | |
| if( p + 1 == epicture ) { | |
| switch( *p ) { | |
| case '.': case ';': case ',': | |
| return true; | |
| } | |
| } | |
| return p == epicture; | |
| } | |
| #include "semantic_token.h" | |
| class picture_t : public lex_picture_t { | |
| state_t picture_state; | |
| public: | |
| picture_t() | |
| : picture_state(picture_validation::invalid_e) | |
| { | |
| attr = none_e; | |
| pos = 0; | |
| blank_when_zero_ok = true; | |
| type = FldInvalid; | |
| encoding = current_encoding('A'); | |
| data = new cbl_field_data_t; | |
| } | |
| void state( state_t state ) { | |
| picture_state = state; | |
| type = field_type(); | |
| encoding = field_encoding(); | |
| if( picture_state == picture_validation::alphabetic_e ) { | |
| attr |= uint64_t(all_alpha_e); | |
| } | |
| } | |
| picture_t& update( const picture_validation::constraint_t& constraint ) | |
| { | |
| blank_when_zero_ok = ! constraint.has_s_star(); | |
| attr = constraint.attr(); | |
| *data = constraint.data(); | |
| return *this; | |
| } | |
| protected: | |
| cbl_encoding_t field_encoding() const { | |
| switch(picture_state) { | |
| case picture_validation::unicode_e: | |
| cbl_unimplemented("Unicode pictures not implemented"); | |
| return current_encoding('A'); | |
| case picture_validation::national_e: | |
| return current_encoding('N'); | |
| default: // alphanumeric by construction | |
| return current_encoding('A'); | |
| break; | |
| } | |
| } | |
| cbl_field_type_t field_type() const { | |
| switch(picture_state) { | |
| case picture_validation::alphabetic_e: | |
| case picture_validation::unicode_e: | |
| case picture_validation::alphanumeric_e: | |
| case picture_validation::national_e: | |
| return FldAlphanumeric; | |
| case picture_validation::alpha_ed_e: | |
| case picture_validation::national_ed_e: | |
| return FldAlphaEdited; | |
| case picture_validation::antedec_e: | |
| case picture_validation::numeric_e: | |
| return FldNumericDisplay; | |
| case picture_validation::numeric_ed_e: | |
| return FldNumericEdited; | |
| case picture_validation::invalid_e: | |
| case picture_validation::none_e: | |
| case picture_validation::boolean_e: | |
| case picture_validation::c_need_r_e: | |
| case picture_validation::d_need_b_e: | |
| case picture_validation::postdec_e: | |
| case picture_validation::exponent_e: | |
| case picture_validation::accept_e: | |
| break; | |
| } | |
| return FldInvalid; | |
| } | |
| }; | |
| lex_picture_t | |
| is_valid_picture(const cbl_loc_t& loc, const char picture[]) { | |
| picture_validation::constraint_t constraint(loc, picture); | |
| const picture_validation::automaton_matrix_t& | |
| automaton = picture_validation::prepare_automaton(); | |
| if( ! picture ) { | |
| return lex_picture_t(); | |
| } | |
| state_t state = picture_validation::none_e; | |
| const char *p = picture, *epicture = picture + strlen(picture); | |
| picture_t output; | |
| /* | |
| * If a currency has a symbol, width is set to its length until appended, | |
| * then becomes 1. | |
| */ | |
| size_t width = 0; | |
| for( ; p < epicture; p++, output.pos++ ) { | |
| // Advance lookahead past any count, which might be a name. | |
| // No picture begins with a '('. | |
| if( *p == '(' && p+1 < epicture ) { | |
| if( constraint.repeatable() ) { | |
| const char *paren = p; | |
| p = seek_paren(p, epicture); | |
| if( *p != ')' ) { | |
| output.pos += p - paren; | |
| return output; | |
| } | |
| if( p[-1] == '(' ) { // pic 9() is invalid | |
| output.pos += p - paren; | |
| return output; | |
| } | |
| constraint.move_caret(p - paren); | |
| p++; | |
| auto result = repeat_count(constraint.location(), --paren); | |
| int count = result.first; | |
| if( count == 0 ) { | |
| dbgmsg("%s: odd zero repeat count for '%s'", __func__, paren); | |
| // cppcheck-suppress unreadVariable | |
| constraint.bad_repeat = paren; // pointless, not used | |
| return output; | |
| } | |
| constraint.add_capacity(--count); | |
| output.pos = p - picture; | |
| } | |
| } | |
| // We may reach end of picture prematurely if it ends with ')'. | |
| if( end_of_picture(p, epicture) ) { | |
| epicture = p; | |
| break; | |
| } | |
| char ch = TOUPPER(*p); | |
| // Is the current character ever allowed to follow the prior? | |
| if( ! constraint.day_follows_night(ch) || constraint.first_dollar(ch)) { | |
| const char *currency = symbol_currency(ch); | |
| if( currency ) { | |
| ch = '$'; | |
| width = width == 0? strlen(currency) : 1; | |
| } else { | |
| dbgmsg("%s:%d: no %2lu: %c -> %c (%s)\n", __func__, __LINE__, | |
| (unsigned long)(p - picture), constraint.prior(), ch, | |
| picture_validation::state_str(constraint.picture_state())); | |
| return output; | |
| } | |
| if( verbose ) { | |
| dbgmsg("ok %2lu: %c (next: %s)\n", (unsigned long)(p - picture), ch, | |
| picture_validation::state_str(state)); | |
| } | |
| } | |
| // Update constraint record and advance to the next state. | |
| const auto old_state = picture_validation::state_str(state); | |
| if( constraint.append(ch, width) ) { | |
| if( 1 < width ) width = 1; | |
| state = automaton[state][ch]; | |
| constraint.maybe_signable(state); | |
| } else { | |
| state = picture_validation::invalid_e; | |
| } | |
| if( state == picture_validation::invalid_e ) { | |
| dbgmsg("%s:%d: automaton has no transition for %s '%c'", | |
| __func__, __LINE__, old_state, ch); | |
| return output; | |
| } | |
| } | |
| assert( p == epicture ); | |
| if( picture < p ) { | |
| char ch = p == epicture? p[-1] : p[0]; | |
| constraint.maybe_signable(ch); | |
| } | |
| output.state(state); | |
| output.update(constraint); | |
| dbgmsg("%s:%d: decided on %s", __func__, __LINE__, | |
| cbl_field_type_str(output.type)); | |
| return output; | |
| } | |
| #ifdef COBOL_PICTURE | |
| /* | |
| * The test harness is not part of the compiler. It can be used to create a | |
| * standalone executable for testing picture validation. | |
| */ | |
| static bool | |
| process_line( const char picture[], int lineno = 1 ) { | |
| assert(picture); | |
| unsigned int mask = picture_validation::invalid_e; | |
| auto result = is_valid_picture(picture); | |
| if( 0 == (mask & result.second) ) { // cppcheck-suppress knownConditionTrueFalse | |
| printf( "valid : %s (%s)\n", | |
| picture, picture_validation::state_str(result.second) ); | |
| } else { | |
| auto p = result.first; | |
| printf( "INVALID : %s at position %u '%c' (%s) line %d\n", | |
| picture, unsigned(p - picture), *p, | |
| picture_validation::state_str(result.second), lineno ); | |
| } | |
| return result.second & picture_validation::invalid_e; // cppcheck-suppress knownConditionTrueFalse | |
| } | |
| static bool | |
| process_file(const char* filename) { | |
| auto input = stdin; | |
| if( filename ) { | |
| input = fopen(filename, "r"); | |
| if( !input ) { | |
| fprintf(stderr, "could not open '%s': %m\n", filename); | |
| return false; | |
| } | |
| } | |
| char *picture = nullptr; | |
| size_t plen = 0; | |
| ssize_t len; | |
| int lineno = 0; | |
| while( (len = getline(&picture, &plen, input)) != -1 ) { | |
| auto p = std::find(picture, picture + len, '\n'); | |
| if( p < picture + len ) *p = '\0'; | |
| process_line(picture, ++lineno); | |
| } | |
| return len != -1; // cppcheck-suppress knownConditionTrueFalse | |
| } | |
| int | |
| main(int argc, char* argv[]) { | |
| int opt; | |
| bool fOK; | |
| const char *picture = nullptr; | |
| while ((opt = getopt(argc, argv, "p:v")) != -1) { | |
| switch (opt) { | |
| case 'p': | |
| picture = optarg; | |
| break; | |
| case 'v': | |
| verbose = true; | |
| break; | |
| } | |
| } | |
| if (picture) { | |
| fOK = process_line(picture) ? 0 : 1; // cppcheck-suppress knownConditionTrueFalse | |
| } else { | |
| if (optind < argc) { | |
| for (int i = optind; i < argc; ++i) { | |
| fOK = process_file(argv[i]); | |
| } | |
| } else { | |
| fOK = process_file(nullptr); | |
| } | |
| } | |
| return fOK ? 0 : 1; | |
| } | |
| #endif // COBOL_PICTURE |